生活中的遇到哪些突发流量?

  • 双11、618大促

  • 电商秒杀活动

  • 微博突发新闻

  • ……

限流是什么?

  • 通常我们说的限流指的是限制达到系统的并发请求数 ,使得系统能够正常的处理部分请求,来保证系统的稳定性。

  • 限流也称为流控(流量控制)。

为什么要限流?

内部原因

  • 服务的处理能力有限,拦截掉处理能力之外的请求

  • 均衡客户端对服务端资源的调用,防止一些请求无效

外部原因

  • 突发情况

  • 非正常的爬虫

其他原因

  • 业务范畴(支付一定费用,提高请求量)

常见的限流算法(两窗两桶)

固定窗口限流

限制在指定时间间隔内的访问量,时间窗口一过,计数器会重置。

规则:

  • 单位时间内,请求次数小于阈值,允许访问且计数器+1

  • 请求次数大于阈值,拒绝访问

  • 时间窗口一过,计数器清零


public boolean test(){

if(当前时间 - 上一次访问时间 > 时间窗口){

计算器 = 0;

上一次访问时间 = 当前时间;

}

if(计算器 < 阈值){

计数器++;

return true;

}

return false;

}

固定窗口临界问题?

滑动窗口限流

滑动窗口限流解决固定窗口临界值的问题,可以保证在任意时间窗口内都不会超过阈值。
相对于固定窗口,滑动窗口除了需要引入计数器之外还需要记录时间窗口内每个请求到达的时间点,因此对内存的占用会比较多。
规则(假设时间窗口1秒):

  • 记录每次请求的时间

  • 统计每次请求的时间且往前推1秒,获取在这个时间窗口内的请求数,1秒前的数据可以删除

  • 统计的请求数小于阈值就记录这个请求的时间,并允许通过,反之拒绝


public boolean test(){

//根据时间获取时间窗口内的请求数

计数器 = 获取时间窗口内的计数(当前时间);

if(计数器 < 阈值){

记录当前时间();

return ture;

}

return false;

}

滑动窗口与固定窗口都无法解决短时间内集中流量的突击。

漏桶算法

水滴持续滴入漏桶中,底部定速流出。如果水滴滴入的速率大于流出的速率,当存水超过桶的大小的时候就会溢出。

规则:

  • 请求来了放入桶中

  • 桶内请求满了拒绝请求

  • 服务定时从桶内拿请求处理


public boolean test(){

//时间差内流出的水量=(当前时间-上次注水时间)*流出的速率

//桶内余量 = max(0,桶内余量-时间差内流出的水量)

if(桶内余量 + 1 <= 桶的容量){

上次注水时间 = 当前时间;

桶内余量++;

return true;

}else{

return false;

}

}

特点:宽进严出,无论请求的速率有多大,都按照固定的速率流出。

漏桶算法缺点:面对突击流量,无法加速处理。

令牌桶算法

令牌桶其实和漏桶的原理类似,只不过漏桶是定速地流出,而令牌桶是定速地往桶里塞入令牌,然后请求只有拿到了令牌才能通过,之后再被服务器处理。

当然令牌桶的大小也是有限制的,假设桶里的令牌满了之后,定速生成的令牌会丢弃。

规则:

  • 定速的往桶里放令牌

  • 令牌数量超过桶的限制,则丢弃

  • 请求来了先向桶内索要令牌,索要成功则允许被处理,反之拒绝


public boolean test(){

//时间差内生成令牌数量=(当前时间-上次获取令牌的时间)*放入令牌的速率

//桶内余量 = min(桶的容量,桶内余量+时间差内生成令牌数量)

if(桶内余量 >= 1){

上次获取令牌的时间=当前时间;

桶内余量--;

return true;

}else{

return false;

}

}

限流算法小结

根据上述内容:“桶”的算法比“窗”的算法好许多,那是不是“窗”的算法可以直接抛弃?非也!

当令牌桶没有预热时,服务刚上线时,系统没有任务负载时,就会有“误杀”情况。

其实每种算法都有各自用途,只不过漏桶和令牌桶比较适合阻塞式限流场景,即没令牌我就等着。而基于时间窗口的限流比较适合对时间敏感的场景,请求过不了,立马给反馈。

单机限流与分布式限流

单机限流和分布式限流的区别其实就在于 “阈值” 存放的位置。

单机限流的话,直接在单台服务器上实现就好了,但是现在我们往往都是集群部署,因此需要多台机器协同提供限流功能。

我们可以将计数器、令牌对应的数量放置在redis中。每次请求时都要去redis判断下是否允许通过,在性能上有一定的损耗,所以此处有个优化点是“批量”,一次不是取一个,而是取一批,减少对redis的请求。

不过值得注意一点,批量获取会导致一定范围内的限流存在误差。比如你取10个令牌,此时不用,等到下一次再用,那么同一时刻集群机器处理量会超过阈值。

限流难点

限流的阈值如何定值是个难点。

定大了,服务受不了;定小了,存在“误杀”情况,且资源利用没有最大化,对用户体验不好。

个人建议:服务上线后,暂不限流,采用日志记录方式,对请求进行记录,根据日志分析,多次尝试调整阈值,推算出单机、集群的总处理能力,这样设置一个合理的阈值。

限流组件

  • Google Guava 提供的限流工具类 RateLimiter,是基于令牌桶实现的,并且扩展了算法,支持预热功能。

  • 阿里开源的限流框架 Sentinel 中的匀速排队限流策略,就采用了漏桶算法。

  • kong中提供了rate-limiting与rate-limiting-advanced限流插件。

  • Nginx 中的限流模块 limit_req_zone,采用了漏桶算法。

  • ……