限流是对系统的出入流量进行控制,防止大流量出入,导致资源不足,系统不稳定。
常见限流方式
1、限制瞬时并发数
2、限制某个接口的时间窗最大请求数
1.限制瞬时并发数
可以使用Guava RateLimiter 提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。
2.限制某个接口的时间窗最大请求数
即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用量。如一些基础服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务调用,但是怕因为更新量比较大将基础服务打挂,这时我们要对每秒/每分钟的调用量进行限速;一种实现方式如下所示:
LoadingCache<Long, AtomicLong> counter =
CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader<Long, AtomicLong>() {
@Override
public AtomicLong load(Long seconds) throws Exception {
return new AtomicLong(0);
}
});
long limit = 1000;
while(true) {
//得到当前秒
long currentSeconds = System.currentTimeMillis() / 1000;
if(counter.get(currentSeconds).incrementAndGet() > limit) {
System.out.println("限流了:" + currentSeconds);
continue;
}
//业务处理
}
使用Guava的Cache来存储计数器,过期时间设置为2秒(保证1秒内的计数器是有的),然后我们获取当前时间戳然后取秒数来作为KEY进行计数统计和限流.
当然这个是单机的,多机集群环境下可以使用Redis
基于 REDIS 实现,存储两个 KEY,一个用于计时,一个用于计数。请求每调用一次,计数器增加 1,若在计时器时间内计数器未超过阈值,则可以处理任务。
限流算法
漏桶算法
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求.
优点:
漏桶算法能强行限制数据的传输速率.
缺点:
对于存在突发特性的流量来说缺乏效率.
因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率.
令牌桶算法
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.
优点
可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率.
缺点
实现起来相对复杂
时间窗口函数
就类似我们开头举的那个例子
比如考虑一个固定长度的时间窗口,比如10s、1min、1h等, 在这个时间窗口内统计请求次数, 但请求次数超过阈值就触发限流。这个算法有两点第一时间窗口的长度是固定的并对应一个计数器, 第二每个时间窗口开始时计数器清零。
优点:
实现简单
时间窗口固定, 每个窗口开始时计数为零,这样后面的请求不会受到之前的影响,做到了前后请求隔离
缺点:
同样是由于时间窗口固定,两个时间窗口之间没有任何联系, 所以调用者可以在一个时间窗口的结束到下一个时间窗口的开始这个非常短的时间段内发起两倍于阈值的请求。所以Fixed-Window算法无法限制窗口间突发流量
无法平滑限流, 比如限流10万次/小时, 那调用者可以在1s内调用10万次而不触发限流
网友评论