文章目录
一、限流
1.1 常见限流方式
1.1.1 固定窗口、滑动窗口、漏斗、令牌桶
四种常见限流方式
1.1.2 令牌桶限流原理-公式
令牌桶
1.2Guava令牌桶使用方式
Guava-RateLimiter
1.3 其他限流
流控规则配置
集群非精确 + 720次/s (限流周期1s、限流次数720)、阻塞类型:不限时阻塞 + 突发流量1s(缓存一定时间的阈值,应对流量蜂刺)、返回码429、返回信息:导出查询接口被限流
补充详解策略
序号 | 策略类型 | 含义 | 备注 | 极端场景 |
---|---|---|---|---|
1 | 集群非精确限流 | 限流策略:5s/一个请求。5台机器平均每个机器0.2个请求/s | 令牌是会先通过当前的,再阻塞未来的,所以限流不准 | 用户A和B,同一时刻或者5s之内,两次访问请求都可以打进去接口 |
2 | 集群精确限流1.0 | 限流策略:5s/一个请求 ,即整个集群,5s内只允许通过一个请求 | 5s内,整个集群只会通过1次请求 |
阻塞类型
序号 | 策略类型 | 使用场景 | 极端场景 | 备注 |
---|---|---|---|---|
1 | 限时阻塞 | 集群非精确限流 | ||
2 | 不限时阻塞 | 会阻塞线程100qps,5台机器。每台处理20QPS300请求打过来,极端场景全部打到同一个机器A。A,1s只能处理20请求,300请求需要15s,也就是,最后一个线程可能等待15s才能获得令牌执行 | 线程因为限流线程等待了近10s |
突发流量
1、定义:
在突发模式中,Guava限流器的桶中令牌是有一个有效期的,有效期的作用是让限流器具有一定的“弹性”,可以根据空闲情况临时超额放行一些请求用于平滑处理突发流量。
2、设置值:1
即保留上1s内,剩下的所有令牌。给下一秒用。
eg:1s生产100令牌。12:00:00没有请求进来,12:00:01的时候,现有可用令牌数量 = 本秒的100 + 上一秒的100 = 200。所以,即时限制了100QPS,在12:00:01的时候,也可能有200个请求打进来
踩坑1: 使用限流器后实际流量总是超过配置阈值
**解释:**这种情况只可能发生下突发模式下,是由2.3小节中介绍的突发流量处理机制导致的。突发模式限流器在向请求发放令牌包括存量令牌与新令牌,新令牌的生成速度等于限流速度,而超额部分的请求来自于存量令牌。在实际流量超过阈值不多的情况下,令牌桶中的令牌需要很长时间才能被耗尽。
踩坑2:在客户端匀速调用的场景中,服务端使用了限流器后发现实际流量无法达到阈值上限【很少见】
**解释:**这种情况是非常偶然的,实际是由于限流配置不当导致的。假设某个客户端以50QPS的速度发出请求,即每20ms一个,服务端的限流器配置为30QPS,且不支持突发流量(突发模式下设置突发时间为0,或者使用了预热模式)。这种情况下服务端会严格按照每30+ms一个的速度接收请求,因此,客户端在20ms之后发出第二个请求时,服务端尚未满足30ms的间隔时间,就出现了每两个请求就有一个被拒绝的现象。读者可参考下图理解这一现象。
**建议:**在遇到这种问题时首先考虑限流阈值是否合理。其次,如果在服务端使用突发模式限流,尽量不要把突发时间(maxBrustSeconds)设置为0。如果使用预热模式限流,应该参考服务容量,配置一个足够大的限流阈值。
限流并且拒绝流量请求,友好提示限流了
自定义blockHandle处理方法
方法的入参和出参 和 限流方法的一致
public ExportDataTResponse blockHandler(SkuPreSellTRequest request, Operator operator) {LOGGER.info("限流导出request:{}, operator:{}", GsonUtil.toJsonString(request), GsonUtil.toJsonString(operator));ExportDataTResponse response = new ExportDataTResponse();response.setCode(429);response.setMsg(String.format("当前有用户正在操作导出,请 3 s后重试");response.setData("");return response;}
二、 熔断
1.1 Thfirt熔断器
1、默认配置
- 请求试探窗口:5s(心跳机制)
- 恢复策略:正常(立即、限时)
- 降级方法:自定义降级方法(返回常量、抛出异常、脚本)
@Degrade(key = "自定义key", fallBackMethod = "fallBack")public int getPrice(int param) {//方法内部不要捕获异常,熔断器通过异常判断调用结果int n = random.nextInt(10000);int m = 1;if (n > 9900) {m = 1 / 0;}return m;}// 降级方法,参数和主方法保持一致,注意降级方法必须是public,否则无法被cglib代理增强public int fallBack(int param) {return 0;}
- 统计窗口:10s
- 请求总数20
- 失败率:50%
- 失败数:2
三、 线程池隔离
3.1 背景
- 如果某个接口的QPS过高,可能会影响服务提供其他功能接口。
- 因为thrift单机默认256个工作线程,如果某接口的性能不好,QPS又高,则某个时间点,工作线程可能都被这个接口占用了,导致其他接口不可用。这时候我们就可以使用线程池隔离,动态!!!的给这个接口分配线程池。当超过一定的工作线程,则采用拒绝策略或直接抛出异常,再结合熔断降级方法,10s内20次调用超过50%的失败率,则这个接口会被降级,调用降级方法fallbackMethod。
- 然后5s之后,心跳机制会去验证这个接口是否恢复正常,如果恢复正常,则接口限时|正常 节奏恢复可用,否则再等5s
3.2 使用
- 线程池隔离 + 降级熔断方法
- 同理,限流也可以接口熔断降级方法一起使用
@ThreadPoolExecute(key = "method.querySkuStock", coreSize = 15, maxSize = 30, maxQueueSize = 500, rejectHandler =ThreadPoolExecutor.CallerRunsPolicy.class)
@Degrade(rhinoKey = "asyncMethod", fallBackMethod = "fallbackMethod")
public Future<String> asyncMethod() {return new AsyncResult<String>() {@Overridepublic String invoke() throws Exception {TimeUnit.MILLISECONDS.sleep(500);return "asyncMethod";}};
}public String fallbackMethod(Throwable e) {//do fallback businessreturn "fallback";
}