高性能限流器 Guava RateLimiter

作者:不才陈某

今天来聊一聊Guava RateLimiter 是如何解决高并发场景下的限流问题的。

Guava 是 Google 开源的 Java 类库,提供了一个工具类 RateLimiter。我们先来看看 RateLimiter 的使用,让你对限流有个感官的印象。假设我们有一个线程池,它每秒只能处理两个任务,如果提交的任务过快,可能导致系统不稳定,这个时候就需要用到限流。

在下面的示例代码中,我们创建了一个流速为 2 个请求 / 秒的限流器,这里的流速该怎么理解呢?直观地看,2 个请求 / 秒指的是每秒最多允许 2 个请求通过限流器,其实在 Guava 中,流速还有更深一层的意思:是一种匀速的概念,2 个请求 / 秒等价于 1 个请求 /500 毫秒。

在向线程池提交任务之前,调用 acquire() 方法就能起到限流的作用。通过示例代码的执行结果,任务提交到线程池的时间间隔基本上稳定在 500 毫秒。

复制//限流器流速:2个请求/秒

RateLimiter limiter =

RateLimiter.create(2.0);

//执行任务的线程池

ExecutorService es = Executors

.newFixedThreadPool(1);

//记录上一次执行时间

prev = System.nanoTime();

//测试执行20次

for (int i=0; i<20; i++){

//限流器限流

limiter.acquire();

//提交任务异步执行

es.execute(()->{

long cur=System.nanoTime();

//打印时间间隔:毫秒

System.out.println(

(cur-prev)/1000_000);

prev = cur;

});

}

输出结果:

...

500

499

499

500

499

经典限流算法:令牌桶算法

Guava 的限流器使用上还是很简单的,那它是如何实现的呢?Guava 采用的是令牌桶算法,其核心是要想通过限流器,必须拿到令牌。也就是说,只要我们能够限制发放令牌的速率,那么就能控制流速了。令牌桶算法的详细描述如下:

令牌以固定的速率添加到令牌桶中,假设限流的速率是 r/ 秒,则令牌每 1/r 秒会添加一个;假设令牌桶的容量是 b ,如果令牌桶已满,则新的令牌会被丢弃;请求能够通过限流器的前提是令牌桶中有令牌。这个算法中,限流的速率 r 还是比较容易理解的,但令牌桶的容量 b 该怎么理解呢?b 其实是 burst 的简写,意义是限流器允许的最大突发流量。比如 b=10,而且令牌桶中的令牌已满,此时限流器允许 10 个请求同时通过限流器,当然只是突发流量而已,这 10 个请求会带走 10 个令牌,所以后续的流量只能按照速率 r 通过限流器。

令牌桶这个算法,如何用 Java 实现呢?很可能你的直觉会告诉你生产者 - 消费者模式:一个生产者线程定时向阻塞队列中添加令牌,而试图通过限流器的线程则作为消费者线程,只有从阻塞队列中获取到令牌,才允许通过限流器。

这个算法看上去非常完美,而且实现起来非常简单,如果并发量不大,这个实现并没有什么问题。可实际情况却是使用限流的场景大部分都是高并发场景,而且系统压力已经临近极限了,此时这个实现就有问题了。问题就出在定时器上,在高并发场景下,当系统压力已经临近极限的时候,定时器的精度误差会非常大,同时定时器本身会创建调度线程,也会对系统的性能产生影响。

那还有什么好的实现方式呢?当然有,Guava 的实现就没有使用定时器,下面我们就来看看它是如何实现的。

Guava 如何实现令牌桶算法

Guava 实现令牌桶算法,用了一个很简单的办法,其关键是记录并动态计算下一令牌发放的时间。

下面我们以一个最简单的场景来介绍该算法的执行过程。假设令牌桶的容量为 b=1,限流速率 r = 1 个请求 / 秒,如下图所示,如果当前令牌桶中没有令牌,下一个令牌的发放时间是在第 3 秒,而在第 2 秒的时候有一个线程 T1 请求令牌,此时该如何处理呢?

线程 T1 请求令牌示意图

对于这个请求令牌的线程而言,很显然需要等待 1 秒,因为 1 秒以后(第 3 秒)它就能拿到令牌了。此时需要注意的是,下一个令牌发放的时间也要增加 1 秒,为什么呢?因为第 3 秒发放的令牌已经被线程 T1 预占了。处理之后如下图所示。

线程 T1 请求结束示意图

假设 T1 在预占了第 3 秒的令牌之后,马上又有一个线程 T2 请求令牌,如下图所示。

线程 T2 请求结束示意图

上面线程 T1、T2 都是在下一令牌产生时间之前请求令牌,如果线程在下一令牌产生时间之后请求令牌会如何呢?假设在线程 T1 请求令牌之后的 5 秒,也就是第 7 秒,线程 T3 请求令牌,如下图所示。

线程 T3 请求令牌示意图

由于在第 5 秒已经产生了一个令牌,所以此时线程 T3 可以直接拿到令牌,而无需等待。在第 7 秒,实际上限流器能够产生 3 个令牌,第 5、6、7 秒各产生一个令牌。由于我们假设令牌桶的容量是 1,所以第 6、7 秒产生的令牌就丢弃了,其实等价地你也可以认为是保留的第 7 秒的令牌,丢弃的第 5、6 秒的令牌,也就是说第 7 秒的令牌被线程 T3 占有了,于是下一令牌的的产生时间应该是第 8 秒,如下图所示。

线程 T3 请求结束示意图

通过上面简要地分析,你会发现,我们只需要记录一个下一令牌产生的时间,并动态更新它,就能够轻松完成限流功能。我们可以将上面的这个算法代码化,示例代码如下所示,依然假设令牌桶的容量是 1。关键是 reserve() 方法,这个方法会为请求令牌的线程预分配令牌,同时返回该线程能够获取令牌的时间。其实现逻辑就是上面提到的:如果线程请求令牌的时间在下一令牌产生时间之后,那么该线程立刻就能够获取令牌;反之,如果请求时间在下一令牌产生时间之前,那么该线程是在下一令牌产生的时间获取令牌。由于此时下一令牌已经被该线程预占,所以下一令牌产生的时间需要加上 1 秒。

复制class SimpleLimiter {

//下一令牌产生时间

long next = System.nanoTime();

//发放令牌间隔:纳秒

long interval = 1000_000_000;

//预占令牌,返回能够获取令牌的时间

synchronized long reserve(long now){

//请求时间在下一令牌产生时间之后

//重新计算下一令牌产生时间

if (now > next){

//将下一令牌产生时间重置为当前时间

next = now;

}

//能够获取令牌的时间

long at=next;

//设置下一令牌产生时间

next += interval;

//返回线程需要等待的时间

return Math.max(at, 0L);

}

//申请令牌

void acquire() {

//申请令牌时的时间

long now = System.nanoTime();

//预占令牌

long at=reserve(now);

long waitTime=max(at-now, 0);

//按照条件等待

if(waitTime > 0) {

try {

TimeUnit.NANOSECONDS

.sleep(waitTime);

}catch(InterruptedException e){

e.printStackTrace();

}

}

}

}如果令牌桶的容量大于 1,又该如何处理呢?按照令牌桶算法,令牌要首先从令牌桶中出,所以我们需要按需计算令牌桶中的数量,当有线程请求令牌时,先从令牌桶中出。具体的代码实现如下所示。我们增加了一个 resync() 方法,在这个方法中,如果线程请求令牌的时间在下一令牌产生时间之后,会重新计算令牌桶中的令牌数,新产生的令牌的计算公式是:(now-next)/interval,你可对照上面的示意图来理解。reserve() 方法中,则增加了先从令牌桶中出令牌的逻辑,不过需要注意的是,如果令牌是从令牌桶中出的,那么 next 就无需增加一个 interval 了。

复制class SimpleLimiter {

//当前令牌桶中的令牌数量

long storedPermits = 0;

//令牌桶的容量

long maxPermits = 3;

//下一令牌产生时间

long next = System.nanoTime();

//发放令牌间隔:纳秒

long interval = 1000_000_000;

//请求时间在下一令牌产生时间之后,则

// 1.重新计算令牌桶中的令牌数

// 2.将下一个令牌发放时间重置为当前时间

void resync(long now) {

if (now > next) {

//新产生的令牌数

long newPermits=(now-next)/interval;

//新令牌增加到令牌桶

storedPermits=min(maxPermits,

storedPermits + newPermits);

//将下一个令牌发放时间重置为当前时间

next = now;

}

}

//预占令牌,返回能够获取令牌的时间

synchronized long reserve(long now){

resync(now);

//能够获取令牌的时间

long at = next;

//令牌桶中能提供的令牌

long fb=min(1, storedPermits);

//令牌净需求:首先减掉令牌桶中的令牌

long nr = 1 - fb;

//重新计算下一令牌产生时间

next = next + nr*interval;

//重新计算令牌桶中的令牌

this.storedPermits -= fb;

return at;

}

//申请令牌

void acquire() {

//申请令牌时的时间

long now = System.nanoTime();

//预占令牌

long at=reserve(now);

long waitTime=max(at-now, 0);

//按照条件等待

if(waitTime > 0) {

try {

TimeUnit.NANOSECONDS

.sleep(waitTime);

}catch(InterruptedException e){

e.printStackTrace();

}

}

}

}

总结

经典的限流算法有两个,一个是令牌桶算法(Token Bucket),另一个是漏桶算法(Leaky Bucket)。令牌桶算法是定时向令牌桶发送令牌,请求能够从令牌桶中拿到令牌,然后才能通过限流器;

而漏桶算法里,请求就像水一样注入漏桶,漏桶会按照一定的速率自动将水漏掉,只有漏桶里还能注入水的时候,请求才能通过限流器。令牌桶算法和漏桶算法很像一个硬币的正反面,所以你可以参考令牌桶算法的实现来实现漏桶算法。

上面我们介绍了 Guava 是如何实现令牌桶算法的,我们的示例代码是对 Guava RateLimiter 的简化,Guava RateLimiter 扩展了标准的令牌桶算法,比如还能支持预热功能。对于按需加载的缓存来说,预热后缓存能支持 5 万 TPS 的并发,但是在预热前 5 万 TPS 的并发直接就把缓存击垮了,所以如果需要给该缓存限流,限流器也需要支持预热功能,在初始阶段,限制的流速 r 很小,但是动态增长的。预热功能的实现非常复杂,Guava 构建了一个积分函数来解决这个问题,如果你感兴趣,可以继续深入研究。

来源: 码猿技术专栏

原文地址:https://www.toutiao.com/article/7111973588177797673/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


所以很多人都会选择将这些生活琐事来交给智能化产品,在众多产品中,扫拖机器人所给我们带来的便利性最强,扫地、拖地全都一气呵成,不需要人为过多干预,不过目前大多数扫拖机器人对于双手解放得不彻底。而石头作为
“昨天的经历都将成为明天的力量。” 将绝大部分精力都奉献给璃月港的刻晴,可以说是《原神》中的高人气角色了,虽然是常驻角色,并且对于普通玩家来说……刻师傅别刮了……不过作为开服就存在的角色,还有独特的剑法
最近,全球知名的通信产业盛会MWC 2024(2024世界移动通信大会)正式召开,其中,联发科以“连接AI宇宙”(Connecting the AI-verse)为主题,为大众展示出一系列在AI和移动通信技术等领域的最新突破,吸引了大量行业
今年上半年有很多值得关注的机型,其中华为最新的影像旗舰华为P70 Art也自然受到了业界不少的关注目光,目前关于这款机型的轮廓图已经在网上曝光。
目前,2024世界移动通信大会(MWC)正在西班牙巴塞罗那举行,值得一提的是,此次大会参展中国厂商非常多,包括华为、中兴、小米、荣耀等等多家厂商均在其列。
就在去年,真我推出了11 Pro+,用一个2亿像素传感器和zoom变焦功能,开启了中端手机影像的长焦大战,而后友商才姗姗来迟的跟进了2亿像素传感器。
【手机之家新闻】一年一度的MWC已经于当地时间2月26日在巴塞罗那正式开展,在本次MWC2024上全球各大厂商齐聚一堂,展出自家最新的技术与产品,其中中兴就参展本次MWC2024,并且展出了诸多面向企业端的产品,而旗下的
近日,联发科在MWC 2024(2024 世界移动通信大会)上展出了一系列令人瞩目的AI和移动通信技术突破,以“连接AI宇宙”(Connecting the AI-verse)的展厅吸引了无数业界精英和媒体的目光。特别是其现场的生成式AI技术
虽然目前国内已经有不少厂商入局折叠屏产品,但是努比亚却迟迟没有入局。不过在近日举办的MWC 2024展会上,努比亚发布了自家首款折叠屏手机——努比亚Flip,预计国内很快也会上市。
MWC 2024正在西班牙巴塞罗那举办,和往年一样,荣耀这次依旧携众多新产品、新技术参会。荣耀Magic6 Pro、荣耀Magic V2 RSR保时捷设计的机型在海外正式发布,并且还展示了魔法大模型、任意门等诸多新技术。
MWC 2024正在西班牙巴塞罗那如火如荼地举行,其中小米也参加了今年的大会,在会上发布了在国内大受欢迎的小尺寸旗舰——小米14。值得一提的是,高通公司CEO安蒙甚至亲临发布会现场为这款机型助阵。
《原神》是一直以来在机圈深受欢迎的游戏,在充满幻想的提瓦特大陆上,你可以邂逅不少性格迥异、能力独特的伙伴。而一加Ace系列一直就拥有非常强烈的电竞属性,也是畅玩《原神》的热门机型,而在本月,一加Ace 3将推
有不少网友发现,今年新机的发布时间相对于往年大幅提前,很多厂商在春节之前密集发布了自己最新的中高端机型,给人一种年后没什么新机可发了的感觉。不过魅族全新的大杯机型——魅族21 PRO非常值得期待,魅族科技也
2022年7月,小米12S Ultra正式发布,这款产品率先将1英寸大底主摄引入到移动影像领域,同时凭借鲜明的徕卡影调给人留下深刻的印象,同时这款产品也被视为了影像旗舰地位的机型。如果从那时算起,到现在差不多已经快过
随着智能手机的日益普及和智能化进程的加速,智能穿戴设备成为了人们关注的焦点。各大智能手机厂商纷纷进军智能穿戴市场,试图在这一新兴领域抢占先机。
早在去年秋天,HyperOS操作系统发布的时候,小米便勾勒出了“人车家全生态”的美好蓝图,而在这其中,小米的多终端统一战略是核心,目前已经有不少小米产品预装或者接受到了HyperOS操作系统的推送,在过去几个月的时
今年雷军将把更多的精力放在小米汽车上,所以接下来的手机业务将由刚刚兼任小米品牌总经理卢伟冰接管。同时雷军也在微博上表示小米2024年开年旗舰——小米14 Ultra即将在近期发布,并且将有卢伟冰进行讲解。另外,卢
新的一年有龙则灵,有愿必达。自1月19日起,荣耀加码“新年荣耀,一起成龙”年货节,在全国荣耀线下门店上线了“新年许愿处”、“龙运当头”等趣味活动,吸引大批消费者到店打卡许愿,戴龙头迎好运。与此同时,为了回
小米在官网微博中已经透露了关于小米14 Ultra信息,所以新机上市应该不会太晚。根据德国莱茵的官方消息,目前小米14 Ultra(型号为24030PN60G)获得了莱茵无频闪认证,表明这款手机可以有效减轻屏幕给用户带来的视觉疲
2月22日,上海广播电视台与华为举办鸿蒙合作签约仪式,宣布其官方客户端看看新闻APP将基于HarmonyOS NEXT鸿蒙星河版启动鸿蒙原生应用开发,为用户提供更加极致的新闻资讯服务体验。此次合作标志着上海广播电视台成为全国