在一个高并发系统中对流量的把控是很是重要的,当巨大的流量直接请求到咱们的服务器上没多久就可能形成接口不可用,不处理的话甚至会形成整个应用不可用。java
好比最近就有个这样的需求,我做为客户端要向kafka
生产数据,而kafka
的消费者则再源源不断的消费数据,并将消费的数据所有请求到web服务器
,虽然说作了负载(有4台web服务器
)但业务数据的量也是巨大的,每秒钟可能有上万条数据产生。若是生产者直接生产数据的话极有可能把web服务器
拖垮。git
对此就必需要作限流处理,每秒钟生产必定限额的数据到kafka
,这样就能极大程度的保证web
的正常运转。github
其实无论处理何种场景,本质都是下降流量保证应用的高可用。web
对于限流常见有两种算法:redis
漏桶算法比较简单,就是将流量放入桶中,漏桶同时也按照必定的速率流出,若是流量过快的话就会溢出(漏桶并不会提升流出速率
)。溢出的流量则直接丢弃。算法
以下图所示:spring
这种作法简单粗暴。springboot
漏桶算法
虽然说简单,但却不能应对实际场景,好比忽然暴增的流量。服务器
这时就须要用到令牌桶算法
:网络
令牌桶
会以一个恒定的速率向固定容量大小桶中放入令牌,当有流量来时则取走一个或多个令牌。当桶中没有令牌则将当前请求丢弃或阻塞。
相比之下令牌桶能够应对必定的突发流量.
对于令牌桶的代码实现,能够直接使用Guava
包中的RateLimiter
。
@Override
public BaseResponse<UserResVO> getUserByFeignBatch(@RequestBody UserReqVO userReqVO) {
//调用远程服务
OrderNoReqVO vo = new OrderNoReqVO() ;
vo.setReqNo(userReqVO.getReqNo());
RateLimiter limiter = RateLimiter.create(2.0) ;
//批量调用
for (int i = 0 ;i< 10 ; i++){
double acquire = limiter.acquire();
logger.debug("获取令牌成功!,消耗=" + acquire);
BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);
logger.debug("远程返回:"+JSON.toJSONString(orderNo));
}
UserRes userRes = new UserRes() ;
userRes.setUserId(123);
userRes.setUserName("张三");
userRes.setReqNo(userReqVO.getReqNo());
userRes.setCode(StatusEnum.SUCCESS.getCode());
userRes.setMessage("成功");
return userRes ;
}复制代码
详见此。
调用结果以下:
代码能够看出以每秒向桶中放入两个令牌,请求一次消耗一个令牌。因此每秒钟只能发送两个请求。按照图中的时间来看也确实如此(返回值是获取此令牌所消耗的时间,差很少也是每500ms一个)。
使用RateLimiter
有几个值得注意的地方:
容许先消费,后付款
,意思就是它能够来一个请求的时候一次性取走几个或者是剩下全部的令牌甚至多取,可是后面的请求就得为上一次请求买单,它须要等待桶中的令牌补齐以后才能继续获取令牌。
针对于单个应用的限流RateLimiter
够用了,若是是分布式环境能够借助redis
来完成。具体实如今接下来讨论。
博客:crossoverjie.top。