飞道的博客

Guava系列之限流RateLimiter

442人阅读  评论(0)

在互联网高并发场景下,限流是用来保证系统稳定性的一种手段,当系统遭遇瞬时流量激增时,可能会由于系统资源耗尽导致宕机。而限流可以把一小部分流量拒绝掉,保证大部分流量可以正常访问,从而保证系统只接收承受范围以内的请求,多余的请求给拒绝掉。

举个例子,节假日很多人都会出去玩,我们知道每个地铁站单位时间内可承受的运输能力是有限的,也就是每趟车承载的人数是有上限的,当达到这个上限以后,上不去的人就只能排队等待,当排队等待的人已经到地铁站口了,此时保安就会限制再进入地铁站了。此时的保安就是起到限流的作用

我们常用的限流算法有:漏桶算法、令牌桶算法

漏桶算法

漏桶算法很形象,我们可以想像有一个大桶,大桶底部有一个固定大小的洞,Web请求就像水一样,先进入大桶,然后以固定的速率从底部漏出来,无论进入桶中的水多么迅猛,漏桶算法始终以固定的速度来漏水

对应到Web请求就是

  • 当桶中无水时表示当前无请求等待,可以直接处理当前的请求
  • 当桶中有水时表示当前有请求正在等待处理,此时新来的请求也是需要进行等待处理
  • 当桶中水已经装满,并且进入的速率大于漏水的速率,水就会溢出来,此时系统就会拒绝新来的请求

令牌桶算法

令牌桶跟漏桶算法有点不一样,令牌桶算法也有一个大桶,桶中装的都是令牌,有一个固定的“人”在不停的往桶中放令牌,每个请求来的时候都要从桶中拿到令牌,要不然就无法进行请求操作

  • 当没有请求来时,桶中的令牌会越来越多,一直到桶被令牌装满为止,多余的令牌会被丢弃
  • 当请求的速率大于令牌放入桶的速率,桶中的令牌会越来越少,直止桶变空为止,此时的请求会等待新令牌的产生

漏桶算法 VS 令牌桶算法

  • 漏桶算法是桶中有水就需要等待,桶满就拒绝请求。而令牌桶是桶变空了需要等待令牌产生
  • 漏桶算法漏水的速率固定,令牌桶算法往桶中放令牌的速率固定
  • 令牌桶可以接收的瞬时流量比漏桶大,比如桶的容量为100,令牌桶会装满100个令牌,当有瞬时80个并发过来时可以从桶中迅速拿到令牌进行处理,而漏桶的消费速率固定,当瞬时80个并发过来时,可能需要进行排队等待

Guava中的限流实现RateLimiter

Guava中的限流使用的是令牌桶算法,RateLimiter提供了两种限流实现:

  • 平滑突发限流(SmoothBursty)
  • 平滑预热限流(SmoothWarmingUp)

什么是平滑突发限流?

每秒以固定的速率输出令牌,以达到平滑输出的效果

        //每秒5个令牌
        RateLimiter rateLimiter = RateLimiter.create(5);
        while(true){
            System.out.println("time:" + rateLimiter.acquire() + "s");
        }

输出结果:

time:0.0s
time:0.196802s
time:0.186141s
time:0.199164s
time:0.199221s
time:0.199338s
time:0.199493s

平均每个0.2秒左右,很均匀

当产生令牌的速率大于取令牌的速率时,是不需要等待令牌时间的

        //每秒5个令牌
        RateLimiter rateLimiter = RateLimiter.create(5);
        while(true){
            System.out.println("time:" + rateLimiter.acquire() + "s");
            //线程休眠,给足够的时间产生令牌
            Thread.sleep(1000);
        }

输出结果:

time:0.0s
time:0.0s
time:0.0s
time:0.0s
time:0.0s

由于令牌可以积累,所以我一次可以取多个令牌,只要令牌充足,可以快速响应

        //每秒5个令牌
        RateLimiter rateLimiter = RateLimiter.create(5);
        while(true){
            //一次取出5个令牌也可以快速响应
            System.out.println("time:" + rateLimiter.acquire(5) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
        }

输出结果:

time:0.0s
time:0.990702s
time:0.190547s
time:0.195084s
time:0.199338s
time:0.999403s

平滑预热限流?

平滑预热限流带有预热期的平滑限流,它启动后会有一段预热期,逐步将令牌产生的频率提升到配置的速率

这种方式适用于系统启动后需要一段时间来进行预热的场景

比如,我设置的是每秒5个令牌,预热期为5秒,那么它就不会是0.2左右产生一个令牌。在前5秒钟它不是一个均匀的速率,5秒后恢复均匀的速率

        //每秒5个令牌,预热期为5秒
        RateLimiter rateLimiter = RateLimiter.create(5,5, TimeUnit.SECONDS);
        while(true){
            //一次取出5个令牌也可以快速响应
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("time:" + rateLimiter.acquire(1) + "s");
            System.out.println("-----------");
        }

输出结果:

time:0.0s
time:0.57874s
time:0.539854s
time:0.520102s
time:0.485491s
-----------
time:0.455969s
time:0.423133s
time:0.391189s
time:0.359336s
time:0.327898s
-----------
time:0.295409s
time:0.263682s
time:0.231618s
time:0.202781s
time:0.198914s
-----------
time:0.198955s
time:0.199052s
time:0.199183s
time:0.199296s
time:0.199468s
-----------
time:0.199493s
time:0.199687s
time:0.198785s
time:0.198893s
time:0.199373s
-----------

从输出结果可以看出来,前面的速率不均匀,到后面就逐渐稳定在0.2秒左右了

当然,Guava的RateLimiter只适用于单机的限流,在互联网分布式项目中可以借助其他中间件来实现限流的功能

参考:
https://yq.aliyun.com/articles/696317

如果感觉对你有些帮忙,请收藏好,你的关注和点赞是对我最大的鼓励!
如果想跟我一起学习,坚信技术改变世界,请关注Java天堂公众号,我会定期分享自己的学习成果,第一时间推送给您


转载:https://blog.csdn.net/pzjtian/article/details/106863873
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场