【微服务稳定性之限流降级】【二】RateLimiter实战
RateLimiter是guava中concurrent包下的一个限流工具类,使用了令牌桶算法。下面简单的介绍一下。
一、RateLimiter API
|
修饰符 |
方法 |
描述 |
|
double |
acquire() |
从RateLimiter获取一个许可,该方法会被阻塞知道获取到请求,阻塞动作,返回的double是从阻塞到获取到令牌等待的时间。 |
|
double |
acquire(int permits) |
从RateLimiter获取指定许可数,该方法会被阻塞知道获取到请求 |
|
static RateLimiter |
create(double permitsPerSecond) |
根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询) |
|
static RateLimiter |
create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) |
根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数回平稳的增长知道预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和) |
|
double |
getRate() |
返回RateLimiter配置中的稳定速率,该速率单位是每秒多少许可数 |
|
void |
setRate(double permitsPerSecond) |
更新RateLimite的稳定速率,参数permit是PerSecond由构造RateLimiter的工厂方法提供。 |
|
String |
toString() |
返回对象的字符表现形式 |
|
boolean |
tryAcquire() |
从RateLimiter获取许可,如果该许可在无延迟下的情况下立即获取得到的话 |
|
boolean |
tryAcquire(int permits) |
从RateLimiter获取许可数,如果该许可数可以在无延迟的情况下立即获取得到的话 |
|
boolean |
tryAcquire(int permits, long timeout, TimeUnit unit) |
从RateLimiter获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout过期之前获取得到许可数的话,那么立即返回false(无需等待) |
|
boolean |
tryAcquire(long timeout, TimeUnit unit) |
从RateLimiter获取许可如果该许可可以不超过timeout的时间内获取得到的话,或者如果无法在timeout过期之前获取得到许可的话,那么立即返回false(无需等待) |
二、应用示例
需求:我们需要将流量限制在5QPS范围内,为了这个目的我们来看看该怎么弄。
定义一个interface,AccessLimitService:
public interface AccessLimitService {
boolean tryAcquire();
double getRate();
}
定义实现类AccessLimitServiceImpl:
@Service
public class AccessLimitServiceImpl implements AccessLimitService {
//每秒发出5个令牌
private RateLimiter rateLimiter = RateLimiter.create(5.0);
/**
* 尝试获取令牌
* @Return
*/
public boolean tryAcquire() {
//这里使用的是tryAcquire而不是acquire方法,快速失败避免耗尽所有资源
return rateLimiter.tryAcquire();
}
public double getRate() {
return rateLimiter.getRate();
}
}
接口类:
@Slf4j
@RestController
@RequestMapping("/flow/limit")
public class LimitController extends BaseController {
@Autowired
private AccessLimitService accessLimitService;
@GetMapping(value = "/get")
public String getInfo() {
return "ok";
}
@GetMapping(value = "/access")
public String access() {
//尝试获取令牌,如果没有请求到立即返回false
if (accessLimitService.tryAcquire()) {
return "access success [" + System.currentTimeMillis() + "]";
} else {
System.out.println(accessLimitService.getRate());
return "access limit [" + System.currentTimeMillis() + "]";
}
}
}
单元测试:
import com.google.common.util.concurrent.RateLimiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FlowLimitTest {
private RestTemplate restTemplate;
private String ip_dev = "localhost:8101";
private RateLimiter rateLimiter;
ExecutorService fixedThreadPool;
@Before
public void setup() {
restTemplate = new RestTemplate();
((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setConnectTimeout(60000);
((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setReadTimeout(60000);
}
@After
public void destroy() {
}
@Test
public void flowLimitTest() throws Exception {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
/**
* access success [1557057930408]
access limit [1557057930399]
access limit [1557057930399]
access success [1557057930409]
access success [1557057930408]
access success [1557057930409]
access success [1557057930408]
access limit [1557057930399]
access success [1557057930409]
access limit [1557057930399]
*/
for (int i = 0; i < 10; i++) {
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(requestApi());
}
});
}
fixedThreadPool.shutdown();
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
private String requestApi() {
StringBuilder url = new StringBuilder();
url.append("http://").append(ip_dev).append("/flow/limit/access");
HttpEntity<String> httpEntity = new HttpEntity<String>(null, null);
ResponseEntity<String> result = null;
try {
result = restTemplate.exchange(url.toString(), HttpMethod.GET, httpEntity, String.class);
if (result.getStatusCode().equals(HttpStatus.OK)) {
return result.getBody();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
可以看到结果,10个请求,6个通过,4个被限制,通过进一步测试发现create大小为n的流量,但是在并发的过程中实际可以通过n+1个请求,其他的则被限制:
access success [1557057930408]
access limit [1557057930399]
access limit [1557057930399]
access success [1557057930409]
access success [1557057930408]
access success [1557057930409]
access success [1557057930408]
access limit [1557057930399]
access success [1557057930409]
access limit [1557057930399]
RateLimiter是比较常用的限流的服务,但是在微服务的场景中只有限流是不够的,同时还要保证在下游系统不可用的时,本身系统的可用性,那么就要考虑到熔断和降级了,后面将说到这些。
Author:忆之独秀
Email:leaguenew@qq.com
注明出处:https://blog.csdn.net/lavorange/article/details/95742831
更多推荐



所有评论(0)