基于分布式环境下限流系统的设计


前提

业务背景

就拿前些天的双十一的 “抢券活动” 来说,一般是设置整点开始抢的,你想想,淘宝的用户群体非常大,可以达到亿级别,而服务接口每秒能处理的量是有限的,那么这个时候问题就会出现,我们如何通过程序来控制用户抢券呢,于是就必须加上这个限流功能了。

生产环境

1、服务接口所能提供的服务上限(limit)假如是 500次/s

2、用户请求接口的次数未知,QPS可能达到 800次/s,1000次/s,或者更高

3、当服务接口的访问频率超过 500次/s,超过的量将拒绝服务,多出的信息将会丢失

4、线上环境是多节点部署的,但是调用的是同一个服务接口

于是,为了保证服务的可用性,就要对服务接口调用的速率进行限制(接口限流)。

什么是限流?

限流是对系统的出入流量进行控制,防止大流量出入,导致资源不足,系统不稳定。

限流系统是对资源访问的控制组件,控制主要的两个功能:限流策略熔断策略,对于熔断策略,不同的系统有不同的熔断策略诉求,有的系统希望直接拒绝、有的系统希望排队等待、有的系统希望服务降级、有的系统会定制自己的熔断策略,这里只针对限流策略这个功能做详细的设计。

限流算法

1、限制瞬时并发数

Guava RateLimiter 提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

2、限制某个接口的时间窗最大请求数

即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用量。如一些基础服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务调用,但是怕因为更新量比较大将基础服务打挂,这时我们要对每秒/每分钟的调用量进行限速;一种实现方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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进行计数统计和限流,这种方式也是简单粗暴,刚才说的场景够用了。

3、令牌桶

算法描述:

  • 假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中
  • 假设桶中最多可以存放b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃
  • 当流量以速率v进入,从桶中以速率v取令牌,拿到令牌的流量通过,拿不到令牌流量不通过,执行熔断逻辑

属性

  • 长期来看,符合流量的速率是受到令牌添加速率的影响,被稳定为:r
  • 因为令牌桶有一定的存储量,可以抵挡一定的流量突发情况
    • M是以字节/秒为单位的最大可能传输速率。 M>r
    • T max = b/(M-r) 承受最大传输速率的时间
    • B max = T max * M 承受最大传输速率的时间内传输的流量

优点:流量比较平滑,并且可以抵挡一定的流量突发情况

4、Google guava 提供的工具库中 RateLimiter 类(内部也是采用令牌桶算法实现)

最快的方式是使用 RateLimit 类,但是这仅限制在单节点,如果是分布式系统,每个节点的 QPS 是一样的,请求量到服务接口那的话就是 QPS * 节点数 了。所以这种方案在分布式的情况下不适用!

5、基于 Redis 实现,存储两个 key,一个用于计时,一个用于计数。请求每调用一次,计数器增加 1,若在计时器时间内计数器未超过阈值,则可以处理任务。

这种能够很好地解决了分布式环境下多实例所导致的并发问题。因为使用redis设置的计时器和计数器均是全局唯一的,不管多少个节点,它们使用的都是同样的计时器和计数器,因此可以做到非常精准的流控。

代码就不公布了,毕竟涉及公司隐私了。

最后

参考文章:

基于Redis的限流系统的设计

感兴趣的可以看看别人的代码是怎么写的:https://github.com/wukq/rate-limiter

转载请注明文章地址为:http://www.54tianzhisheng.cn/2017/11/18/flow-control/

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 前提
    1. 1.1. 业务背景
    2. 1.2. 生产环境
  2. 2. 什么是限流?
  3. 3. 限流算法
    1. 3.1. 1、限制瞬时并发数
    2. 3.2. 2、限制某个接口的时间窗最大请求数
    3. 3.3. 3、令牌桶
    4. 3.4. 4、Google guava 提供的工具库中 RateLimiter 类(内部也是采用令牌桶算法实现)
    5. 3.5. 5、基于 Redis 实现,存储两个 key,一个用于计时,一个用于计数。请求每调用一次,计数器增加 1,若在计时器时间内计数器未超过阈值,则可以处理任务。
  4. 4. 最后