深入解析:Guava限频器RateLimiter的使用示例

news/2025/10/5 10:22:02/文章来源:https://www.cnblogs.com/yxysuanfa/p/19126324

1. 背景说明

高并发应用场景有3大利器: 缓存、限流、熔断。

也有说4利器的: 缓存、限流、熔断、降级。

每一种技术都有自己的适用场景,也有很多使用细节和注意事项。

本文主要介绍 Guava 工具库中的限频器(RateLimiter), 也可以称之为限流器。

限流技术可以简单分为两类:

限频器(RateLimiter)的适用场景:

限制客户端每秒访问服务器的次数。

可以在单个接口使用,
也可以对多个接口使用,
甚至我们还可以使用注解与参数,通过AOP切面进行灵活的编程。(本文不介绍)

2. API与方法

guava工具库的MAVEN依赖为:

<properties>
<guava.version>33.1.0-jre</guava.version>
</properties>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>

主要的类结构和方法如下所示:

package com.google.common.util.concurrent
;
public
abstract
class RateLimiter {
// 1. 内部实现创建的是 SmoothBursty 模式的限频器
// permitsPerSecond 参数就是每秒允许的授权数量
public
static RateLimiter create(
double permitsPerSecond)//...
// 2. 内部实现创建的是 SmoothWarmingUp 模式的限频器
// 传入预热的时间: 在预热期间内, 每秒发放的许可数比 permitsPerSecond 少
// 主要用于保护服务端, 避免刚启动就被大量的请求打死。
public
static RateLimiter create(
double permitsPerSecond,
Duration warmupPeriod) // ...
public
static RateLimiter create(
double permitsPerSecond,
long warmupPeriod, TimeUnit unit) //...
// 3. 使用过程中, 支持动态修改每秒限频次数
public
final
void setRate(
double permitsPerSecond) // ...
// 4. 获取许可; 拿不到就死等;
public
double acquire(
)// ...
public
double acquire(
int
permits
)//...
// 5. 尝试获取许可 
public
boolean tryAcquire(
)//...
public
boolean tryAcquire(
int
permits
)//...
// 5.1 重点在这里; 尝试获取许可时, 可以设置一个容许的缓冲时间;
// 使用场景是: 放过短时间内的, 聚簇的, 一定数量的请求;
// 比如: n毫秒内, 接连来了m个请求; 
// 如果这m个请求都需要放过, 就需要设置一定的缓冲时间;
// 参见下文的测试代码;
public
boolean tryAcquire(Duration timeout)//...
public
boolean tryAcquire(
long timeout, TimeUnit unit)//...
public
boolean tryAcquire(
int
permits
, Duration timeout)//...
public
boolean tryAcquire(
int
permits
,
long timeout, TimeUnit unit)//...
}
// 平滑限频器
abstract
class SmoothRateLimiter
extends RateLimiter {
static
final
class SmoothBursty
extends SmoothRateLimiter {
}
// 平滑预热: 顾名思义, 需要一个预热时间才能到达
static
final
class SmoothWarmingUp
extends SmoothRateLimiter {
}
}

3. 示例代码

这部分依次介绍我们的示例代码。

3.1 基础工具方法

下面是一些基础工具方法:

// 睡眠一定的毫秒数
private
static
void sleep(
long millis) {
try {
Thread.sleep(millis)
;
}
catch (InterruptedException e) {
e.printStackTrace(
)
;
}
}
// 打印控制台日志
private
static
void println(String msg) {
System.out.println("[" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"
)
.format(
new Date(
)
) + "]" + msg)
;
}

3.2 测试任务类

下面是一个测试任务类, 内部使用了 RateLimiter#tryAcquire 方法。

private
static
class RateLimiterJob
implements Runnable {
//
CountDownLatch latch;
RateLimiter rateLimiter;
// 结果
StringBuilder resultBuilder =
new StringBuilder(
)
;
AtomicInteger passedCounter =
new AtomicInteger(
)
;
AtomicInteger rejectedCounter =
new AtomicInteger(
)
;
public RateLimiterJob(
int taskCount, RateLimiter rateLimiter) {
this.latch =
new CountDownLatch(taskCount)
;
this.rateLimiter = rateLimiter;
}
@Override
public
void run(
) {
//
boolean passed = rateLimiter.tryAcquire(1
, 5
, TimeUnit.MILLISECONDS
)
;
if (passed) {
passedCounter.incrementAndGet(
)
;
resultBuilder.append("1"
)
;
}
else {
rejectedCounter.incrementAndGet(
)
;
resultBuilder.append("-"
)
;
}
//
latch.countDown(
)
;
}
}

也加上了一些并发控制的手段和统计方法, 以方便我们进行测试:

3.3 测试和统计方法

真正的测试和统计方法为:

private
static ExecutorService executorService = Executors.newFixedThreadPool(8
,
new ThreadFactory(
) {
@Override
public Thread newThread(Runnable r) {
Thread t =
new Thread(r)
;
t.setDaemon(true
)
;
t.setName("RateLimiter-1"
)
;
return t;
}
}
)
;
private
static String metrics(RateLimiter rateLimiter,
int taskCount) {
long startMillis = System.currentTimeMillis(
)
;
// 休息1S
rateLimiter.tryAcquire(
)
;
sleep(1_000
)
;
//
RateLimiterJob job =
new RateLimiterJob(taskCount, rateLimiter)
;
for (
int i = 0
; i < taskCount; i++
) {
sleep(10
)
;
executorService.submit(job)
;
}
// 等待结果
try {
job.latch.await(
)
;
}
catch (InterruptedException e) {
e.printStackTrace(
)
;
}
long costMillis = System.currentTimeMillis(
) - startMillis;
//
String result = job.resultBuilder.toString(
)
;
result = result + "[passed=" + job.passedCounter.get(
) +
", rejected=" + job.rejectedCounter.get(
) + "]"
+ "[耗时=" + costMillis + "ms]"
;
return result;
}

这里创建了一个并发线程池, 用来模拟多个并发请求客户端, 也保证了短时间内有一定的聚簇流量。

metrics 方法, 对 rateLimiter 进行一定数量的任务测试, 并返回统计结果;

3.4 测试两种模式的限频器

下面的代码, 测试两种模式的限频器:

private
static
void testRateLimit(
) {
//
double permitsPerSecond = 20D
;
int taskCount = 100
;
println("========================================"
)
;
// 1. SmoothBursty 模式的限频器: 平滑分配token, 可以看代码实现
RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond)
;
// 111111111111111111111111111-1---1---1--1---1---1---1
// ---1---1---1----1--1---1---1----1--1---1---1---1
// [passed=46, rejected=54][耗时=2346ms]
String result = metrics(rateLimiter, taskCount)
;
println("1. SmoothBursty 模式的限频器.result:==========" + result)
;
println("========================================"
)
;
// 2. SmoothWarmingUp 模式的限频器: 系统需要预热的话,最初的时候,放行的请求会比较少;
rateLimiter = RateLimiter.create(permitsPerSecond, 1
, TimeUnit.SECONDS
)
;
// 1-----------1----------1---------1---------1--------1
// -------1------1-----1-----1----1---1---1---1---
// [passed=14, rejected=86][耗时=2251ms]
result = metrics(rateLimiter, taskCount)
;
println("2. SmoothWarmingUp 模式的限频器.result:==========" + result)
;
println("========================================"
)
;
}

我将输出的内容放在了双斜线注释里面, 1表示放行, -表示拒绝。
可以看到:

  • SmoothBursty 模式, 直接放过了前面的一定量的聚簇流量
  • SmoothWarmingUp 模式, 开始时在预热, 放过的请求较少, 预热完成后正常放行和拒绝。

3.5 测试缓冲时间与等待耗时

下面的方法, 测试 tryAcquire 方法指定缓冲时间时, 会消耗多少时间等待。

private
static
void testRateLimitTimeout(
) {
int permitsPerSecond = 500
;
RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond)
;
//
int timeout = 50
;
int clusterCount = timeout * permitsPerSecond / 1000
;
AtomicInteger passedCount =
new AtomicInteger(0
)
;
long startMillis = System.currentTimeMillis(
)
;
long maxTimeoutMillis = 0
;
for (
int i = 0
; i < clusterCount; i++
) {
long beginMillis = System.currentTimeMillis(
)
;
// 限频时使用缓冲时间区间: 短暂放过聚集在一起的少量(并发)请求数: 
// 放过的数量为: timeout * permitsPerSecond/1000
boolean passed = rateLimiter.tryAcquire(1
, 50
, TimeUnit.MILLISECONDS
)
;
if (passed) {
passedCount.incrementAndGet(
)
;
}
long timeoutMillis = System.currentTimeMillis(
) - beginMillis;
maxTimeoutMillis = Math.max(timeoutMillis, maxTimeoutMillis)
;
}
long costMillis = System.currentTimeMillis(
) - startMillis;
// [2025-04-28 22:49:00]testRateLimitTimeout:
// [clusterCount=25];[passedCount=25]
println("testRateLimitTimeout:[clusterCount=" +
clusterCount + "];[passedCount=" + passedCount.get(
) + "]"
)
;
// [2025-04-28 22:49:00]testRateLimitTimeout:
// 耗时:[costMillis=47][maxTimeoutMillis=3]
println("testRateLimitTimeout:耗时:[costMillis=" +
costMillis + "][maxTimeoutMillis=" + maxTimeoutMillis + "]"
)
;
}

我们的测试条件为: timeout = 50; permitsPerSecond = 500.
放过的聚簇流量公式为: timeout * permitsPerSecond/1000
可以看到, 测试结果里面的日志为:

[clusterCount=25];[passedCount=25]

符合我们的预期和计算。

等待耗时时间最大为 maxTimeoutMillis=3, 这个等待时间还可以接受:

耗时:[costMillis=47][maxTimeoutMillis=3]

我们使用时根据需要配置相关参数即可。

4. 完整的测试代码

完整的测试代码如下所示:

import com.google.common.util.concurrent.RateLimiter
;
import java.text.SimpleDateFormat
;
import java.util.Date
;
import java.util.concurrent.*
;
import java.util.concurrent.atomic.AtomicInteger
;
// 测试限频:
public
class RateLimiterTimeoutTest {
private
static ExecutorService executorService =
Executors.newFixedThreadPool(8
,
new ThreadFactory(
) {
@Override
public Thread newThread(Runnable r) {
Thread t =
new Thread(r)
;
t.setDaemon(true
)
;
t.setName("RateLimiter-1"
)
;
return t;
}
}
)
;
// 测试性能
public
static
void main(String[] args) {
testRateLimitTimeout(
)
;
testRateLimit(
)
;
}
private
static
void testRateLimitTimeout(
) {
int permitsPerSecond = 500
;
RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond)
;
//
int timeout = 50
;
int clusterCount = timeout * permitsPerSecond / 1000
;
AtomicInteger passedCount =
new AtomicInteger(0
)
;
long startMillis = System.currentTimeMillis(
)
;
long maxTimeoutMillis = 0
;
for (
int i = 0
; i < clusterCount; i++
) {
long beginMillis = System.currentTimeMillis(
)
;
// 限频时使用缓冲时间区间: 短暂放过聚集在一起的少量(并发)请求数: 
// 放过的数量为: timeout * permitsPerSecond/1000
boolean passed = rateLimiter.tryAcquire(1
, 50
, TimeUnit.MILLISECONDS
)
;
if (passed) {
passedCount.incrementAndGet(
)
;
}
long timeoutMillis = System.currentTimeMillis(
) - beginMillis;
maxTimeoutMillis = Math.max(timeoutMillis, maxTimeoutMillis)
;
}
long costMillis = System.currentTimeMillis(
) - startMillis;
// [2025-04-28 22:49:00]testRateLimitTimeout:
// [clusterCount=25];[passedCount=25]
println("testRateLimitTimeout:[clusterCount=" +
clusterCount + "];[passedCount=" + passedCount.get(
) + "]"
)
;
// [2025-04-28 22:49:00]testRateLimitTimeout:
// 耗时:[costMillis=47][maxTimeoutMillis=3]
println("testRateLimitTimeout:耗时:[costMillis=" +
costMillis + "][maxTimeoutMillis=" + maxTimeoutMillis + "]"
)
;
}
private
static
void testRateLimit(
) {
//
double permitsPerSecond = 20D
;
int taskCount = 100
;
println("========================================"
)
;
// 1. SmoothBursty模式的限频器: 平滑分配token, 可以看代码实现
RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond)
;
// 111111111111111111111111111-1---1---1--1---1---1---1
// ---1---1---1----1--1---1---1----1--1---1---1---1
// [passed=46, rejected=54][耗时=2346ms]
String result = metrics(rateLimiter, taskCount)
;
println("1. SmoothBursty 模式的限频器.result:==========" + result)
;
println("========================================"
)
;
// 2. SmoothWarmingUp模式的限频器: 系统需要预热的话,最初的时候,放行的请求会比较少;
rateLimiter = RateLimiter.create(permitsPerSecond, 1
, TimeUnit.SECONDS
)
;
// 1-----------1----------1---------1---------1--------1
// -------1------1-----1-----1----1---1---1---1---
// [passed=14, rejected=86][耗时=2251ms]
result = metrics(rateLimiter, taskCount)
;
println("2. SmoothWarmingUp 模式的限频器.result:==========" + result)
;
println("========================================"
)
;
}
private
static String metrics(RateLimiter rateLimiter,
int taskCount) {
long startMillis = System.currentTimeMillis(
)
;
// 休息1S
rateLimiter.tryAcquire(
)
;
sleep(1_000
)
;
//
RateLimiterJob job =
new RateLimiterJob(taskCount, rateLimiter)
;
for (
int i = 0
; i < taskCount; i++
) {
sleep(10
)
;
executorService.submit(job)
;
}
// 等待结果
try {
job.latch.await(
)
;
}
catch (InterruptedException e) {
e.printStackTrace(
)
;
}
long costMillis = System.currentTimeMillis(
) - startMillis;
//
String result = job.resultBuilder.toString(
)
;
result = result + "[passed=" + job.passedCounter.get(
) +
", rejected=" + job.rejectedCounter.get(
) + "]"
+ "[耗时=" + costMillis + "ms]"
;
return result;
}
private
static
void sleep(
long millis) {
try {
Thread.sleep(millis)
;
}
catch (InterruptedException e) {
e.printStackTrace(
)
;
}
}
private
static
void println(String msg) {
System.out.println("[" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"
)
.format(
new Date(
)
) + "]" + msg)
;
}
private
static
class RateLimiterJob
implements Runnable {
//
CountDownLatch latch;
RateLimiter rateLimiter;
// 结果
StringBuilder resultBuilder =
new StringBuilder(
)
;
AtomicInteger passedCounter =
new AtomicInteger(
)
;
AtomicInteger rejectedCounter =
new AtomicInteger(
)
;
public RateLimiterJob(
int taskCount, RateLimiter rateLimiter) {
this.latch =
new CountDownLatch(taskCount)
;
this.rateLimiter = rateLimiter;
}
@Override
public
void run(
) {
//
boolean passed = rateLimiter.tryAcquire(1
, 5
, TimeUnit.MILLISECONDS
)
;
if (passed) {
passedCounter.incrementAndGet(
)
;
resultBuilder.append("1"
)
;
}
else {
rejectedCounter.incrementAndGet(
)
;
resultBuilder.append("-"
)
;
}
//
latch.countDown(
)
;
}
}
}

测试代码总的只有100多行, 并不是很复杂。

5. 简单小结

本文简单介绍了Guava限频器(RateLimiter)的用法。
使用要点是 tryAcquire 时需要给一定量的缓冲时间, 避免聚簇的少量请求被误拦截。

我们的测试条件为: timeout = 50; permitsPerSecond = 500.
放过的聚簇流量公式为: timeout * permitsPerSecond/1000

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/928121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025石材加工厂家最新品牌推荐排行榜:大祥工艺,业务覆盖东北,辽宁盖州,专业浮雕雕刻高级技师

石材加工行业在近年来呈现出快速发展的态势,但同时也面临着诸多问题。市场上石材加工企业数量众多,规模大小不一,导致产品质量参差不齐,部分企业为了追求短期利益,使用劣质原材料或简化加工工艺,使得石材产品的耐…

centos7升级降级内核 centos升级降级内核 centos升级内核 centos降级内核

centos7升级降级内核 centos升级降级内核 centos升级内核 centos降级内核# 强制安装旧版 kernel-headersrpm -ivh --force kernel*.el7.x86_64.rpm rpm -q kernel设置默认启动项 # 查看 GRUB 菜单中的名称awk -F\ /…

详细介绍:MySQL高可用集群

详细介绍:MySQL高可用集群2025-10-05 10:17 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; …

Photoshop 在线网页版?是的,它来了!免费使用指南

还在烦恼电脑空间不够,装不下庞大的PS软件?或是突然需要处理图片,却发现设备里没有安装修图工具?别着急,今天推荐一款超实用的工具——在线PS,浏览器一点即开,轻松获得接近专业级的修图体验! ✨ 为什么选择在线…

最好的翻译论文网站

最好的翻译论文网站https://www.yiyibooks.cn/arxiv/2403.14621v1/index.html

基于Python+Vue开发的母婴商城管理系统源码+运行步骤

项目简介该项目是基于Python+Vue开发的母婴商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于…

2025防火隔断品牌最新推荐排行榜:甲级防火玻璃隔断厂家深度解析,精选优质品牌助力采购决策!

当前建筑行业对防火安全的要求日益严格,防火隔断作为关键防护设施,市场需求持续攀升,但行业现状却让采购者面临诸多困扰。不少品牌缺乏核心技术,产品耐火性能不达标,无法在火灾中有效阻隔火势与有毒烟气;部分新兴…

福建建筑人才网官方网站国外做游戏的视频网站有哪些问题

由ASCII码表的输出程序,我们可以认识到使用循环语句处理一组连续的数据有着巨大的优势。在更普遍的情况下,数据由一组离散的数值组成,如一组学生的考试成绩。对于这些数据的处理,有效的方式是使用循环。但前提是数据可以在循环中有序的访问。ASCII码表输出程序中,循环变量…

北京高端网站设计原画零基础自学

今天给大家分享一下SQLServer常用的配置函数知识&#xff0c;希望对初学者能有所帮助&#xff01;1、DATEFIRST Datefirst返回值tinyint说明:datefirst指一周中的第一天&#xff0c;英语中第一天为星期日示例&#xff1a;SELECT DATEFIRST AS 1st Day, DATEPART(dw, GETDATE())…

做网站较好的框架安阳做网站推广最好的公司

这里介绍MySQL数据库和Navicat的使用 1.下载MySQL数据库及MySQL客户端管理工具Navicat 登录www.mysql.com下载MySQL 登录www.navicat.com.cn/download下载客户端管理工具 2.启动MySQL数据库服务器 以管理员身份打开命令提示窗口 找到mysql的bin目录 输入初始化命令mysqld…

机器学习Day5-模型诊断 - 详解

机器学习Day5-模型诊断 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

鲲鹏Arm+麒麟V10 K8s 离线部署教程 - 教程

鲲鹏Arm+麒麟V10 K8s 离线部署教程 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "…

高端企业网站公司企业培训师资格证报考2023

前言 在今天的技术世界中&#xff0c;Linux已成为广泛使用的操作系统之一&#xff0c;而对于运维人员和开发人员来说&#xff0c;磁盘分区挂载是一个至关重要的任务。正确地管理和配置磁盘分区挂载可以极大地提升系统的性能和可靠性&#xff0c;同时也能确保数据的安全性。 通…

完整教程:前端八股之CSS

完整教程:前端八股之CSS2025-10-05 10:00 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; fo…

线段树模板1

#include<bits/stdc++.h> using namespace std; const int maxn=1e6+10; #define ll long longll n,m,sum[maxn<<2],add[maxn<<2];void pushup(ll id){sum[id]=sum[id<<1]+sum[id<<1|1]…

这些行业软件你用过哪个

这些行业软件你都知道吗 Altair HW FEKO 2022.1.2 x64 Altair PSIM Professional 2022.1 x64 with Full License Altair PSIM Professional 2023 with Full Features Altair S-CALC, S-CONCRETE, S-FOUNDATION 2023 a…

提供远程服务

3DVista Virtual Tour Suite 2023.0.13 3DVista Virtual Tour Suite 2023.2.10 3DVista Virtual Tour Suite 2025 AGI STK 12.6 全部模块可用 Alteryx Intelligence Suite 2023.2 Ansys Systems Tool Kit (STK) P Ansy…

分享一些软件资讯

├── 3DCoat 2022.24.7z ├── Flow.Science.Flow-3D.v11.2.Update2.Linux.x64-SSQ.rar ├── v:camcenter ├── 6SigmaDCX 12 ├── 6sigmaDCX 15.0+16.3 ├── 6sigmaET 13.1 14 16.3 17.1 ├── MSC Apex…

快速将网站seo南宁网站建设哪个好

可以设置服主与管理员 控制台中设置&#xff08;需游戏账号在线&#xff09; 服主 添加&#xff1a;在控制台中输入ownerid空格SteamID 删除&#xff1a;在控制台中输入removeowner空格SteamID 管理员 添加&#xff1a;在控制台中输入moderatorid空格SteamID 删除&#…

怎样建一个自己公司的网站wordpress discuz插件

1、简介 Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。 只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符&#xff01; BindingKey 一般都是有一个或多个单词组成&#xff0c;多个单词之间以.分割&#x…