基于Java編寫一個限流工具類RateLimiter
限流工具類RateLimiter
原理:令牌桶算法
有一個桶,桶的容量固定。系統(tǒng)以恒定的速度往桶里放令牌,令牌數(shù)不超過桶的容量。
用戶發(fā)送請求進來,需要先從桶里獲取一個令牌才能通過,獲取后桶里令牌數(shù)減一。如果系統(tǒng)兩秒一個往桶里放令牌,用戶請求一秒一次,那么當(dāng)令牌被取空后,操作就需要等待,會被限流。
可以應(yīng)對突發(fā)流量,當(dāng)桶里有足夠多的令牌,可以一次處理多個請求
1.導(dǎo)入guava依賴包
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
RateLimiter的集個核心方法:create()、tryAcquire()
- acquire() 獲取一個令牌, 會阻塞當(dāng)前線程,直到獲取到一個令牌。該方法返回值類型為
double,表示當(dāng)前線程需要等待的時間(單位:秒),這個時間取決于令牌桶中令牌的剩余數(shù)量和發(fā)放速率。當(dāng)執(zhí)行rateLimiter.acquire()方法時,如果令牌桶中還有剩余的令牌,則該方法會立即返回,返回值為 0,表示當(dāng)前線程無需等待即可獲取到一個令牌。如果令牌桶中沒有令牌,那么該方法就會阻塞當(dāng)前線程,直到令牌桶中有令牌可用或者線程被中斷。 - acquire(int permits) 獲取指定數(shù)量的令牌, 該方法也會阻塞, 返回值為獲取到這 N 個令牌花費的時間
- tryAcquire() 判斷時候能獲取到令牌, 如果不能獲取立即返回 false
- tryAcquire(int permits) 獲取指定數(shù)量的令牌, 如果不能獲取立即返回 false
- tryAcquire(long timeout, TimeUnit unit) 判斷能否在指定時間內(nèi)獲取到令牌, 如果不能獲取立即返回 false
- tryAcquire(int permits, long timeout, TimeUnit unit) 判斷能否在指定時間內(nèi)獲取到指定數(shù)量的令牌, 如果不能獲取立即返回 false
- create(double permitsPerSecond) 創(chuàng)建每秒放入指定數(shù)量的令牌桶。SmoothBursty模式
- create(5.0, 1, TimeUnit.SECONDS) 每秒5個令牌,預(yù)熱時間1秒,SmoothWarmingUp模式
2.代碼
public void limitTest(){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
//創(chuàng)建令牌桶,一秒一個,容量為1
RateLimiter rateLimiter = RateLimiter.create(1);
//獲取放令牌的速率
System.out.println("放令牌的速率:"+rateLimiter.getRate());
for (int i=0;i<5;i++){
//獲取令牌,會阻塞,返回等待的時間
double acquire = rateLimiter.acquire();
System.out.println("第"+i+" 個令牌獲取到的時間:"+LocalDateTime.now().format(dtf)+",等待時間:"+acquire);
}
//是否會立即獲取到令牌
boolean tryAcquire = rateLimiter.tryAcquire();
}

3.AOP+RateLimiter+注解,實現(xiàn)限流
1.創(chuàng)建注解
public @interface Limit {
// 資源主鍵
String key() default "";
//最多訪問次數(shù),代表請求總數(shù)量
double permitsPerSeconds();
// 時間:即timeout時間內(nèi),只允許有permitsPerSeconds個請求總數(shù)量訪問,超過的將被限制不能訪問
long timeout();
//時間類型,默認秒
TimeUnit timeUnit() default TimeUnit.SECONDS;
//提示信息
String msg() default "系統(tǒng)繁忙,請稍后重試";
}
2.AOP切面
@Aspect
@Component
public class LimitAop {
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
@Around("@annotation(com.limit.Limit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//拿limit的注解
Limit limit = method.getAnnotation(Limit.class);
if (limit != null) {
// key作用:不同的接口,不同的流量控制,相當(dāng)于每個接口有一個對應(yīng)的令牌桶
String key = limit.key();
RateLimiter rateLimiter;
//驗證緩存是否有命中key
if (!limitMap.containsKey(key)) {
//創(chuàng)建令牌桶
rateLimiter = RateLimiter.create(limit.permitsPerSeconds());
limitMap.put(key, rateLimiter);
log.info("新建了令牌桶={},容量={}", key, limit.permitsPerSeconds());
}
rateLimiter = limitMap.get(key);
//拿一個令牌,拿不到會一直阻塞
double acquire = rateLimiter.acquire(1);
log.info("{},獲取令牌時間{}", key,LocalDateTime.now().format(dtf));
/*
//是否能立即拿到令牌,不能則桶里沒有,還沒到一秒鐘,進行限流
boolean acquire = rateLimiter.tryAcquire();
if (!acquire) {
log.info("令牌桶={},獲取令牌失敗", key);
throw new RuntimeException(limit.msg());
}*/
}
return joinPoint.proceed();
}
}
3.注解使用
@GetMapping("/limitTest")
@Limit(key = "limitTest",permitsPerSeconds = 1,timeout = 1,msg = "觸發(fā)接口限流,請重試")
public void limitTest(){
//其它邏輯代碼
//這里打印調(diào)用該接口執(zhí)行的時間,以便觀察限流
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(LocalDateTime.now().format(dtf));
}
日志情況:
無論請求速度多快,一秒后才能處理請求。因為rateLimiter.acquire(1)拿不到,一直等待,會阻塞。 其它獲取令牌的方法感興趣可以按照上文方法詳情自行測試。

到此這篇關(guān)于基于Java編寫一個限流工具類RateLimiter的文章就介紹到這了,更多相關(guān)Java限流工具類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
純Java環(huán)境下實現(xiàn)將在線地圖路網(wǎng)數(shù)據(jù)轉(zhuǎn)為GeoJSON與WKT
這篇文章主要為大家詳細介紹了如何純Java環(huán)境下實現(xiàn)將在線地圖路網(wǎng)數(shù)據(jù)轉(zhuǎn)為GeoJSON與WKT,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2026-02-02
解決@JsonInclude(JsonInclude.Include.NON_NULL)不起作用問題
這篇文章主要介紹了解決@JsonInclude(JsonInclude.Include.NON_NULL)不起作用問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
Spring Security OAuth 個性化token的使用
這篇文章主要介紹了Spring Security OAuth 個性化token的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-02-02
Spring+quartz實現(xiàn)定時發(fā)送郵件功能實例
spring提供的定時發(fā)送郵件功能一直深受廣大web開發(fā)者的喜愛,這篇文章主要介紹了Spring+quartz實現(xiàn)定時發(fā)送郵件功能實例,有興趣的可以了解一下。2017-03-03

