SpringBoot如何使用RateLimiter通過AOP方式進(jìn)行限流
使用RateLimiter通過AOP方式進(jìn)行限流
1、引入依賴
<!-- guava 限流 --> <dependency> ?? ? <groupId>com.google.guava</groupId> ?? ? <artifactId>guava</artifactId> ?? ? <version>25.1-jre</version> </dependency>
2、自定義注解
@Target({ElementType.PARAMETER, ElementType.METHOD}) ? ?
@Retention(RetentionPolicy.RUNTIME) ? ?
@Documented ? ?
public ?@interface ServiceLimit {?
?? ? String description() ?default "";
}3、AOP實(shí)現(xiàn)類
@Component
@Scope
@Aspect
public class LimitAspect {
?? ?每秒只發(fā)出5個(gè)令牌,此處是單進(jìn)程服務(wù)的限流,內(nèi)部采用令牌捅算法實(shí)現(xiàn)
?? ?private static ? RateLimiter rateLimiter = RateLimiter.create(5.0);
?? ?
?? ?//Service層切點(diǎn) ?限流
?? ?@Pointcut("@annotation(com.itstyle.seckill.common.aop.ServiceLimit)") ?
?? ?public void ServiceAspect() {
?? ??? ?
?? ?}
?? ?
? ? @Around("ServiceAspect()")
? ? public ?Object around(ProceedingJoinPoint joinPoint) {?
? ? ?? ?Boolean flag = rateLimiter.tryAcquire();
? ? ?? ?Object obj = null;
?? ??? ?try {
?? ??? ??? ?if(flag){
?? ??? ??? ??? ?obj = joinPoint.proceed();
?? ??? ??? ?}
?? ??? ?} catch (Throwable e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?}?
? ? ?? ?return obj;
? ? }?
}4、使用
@Override
@ServiceLimit
@Transactional
?? ?public Result startSeckil(long seckillId,long userId) {
?? ??? ?//todo 操作
?? ?}SpringBoot之限流
限流的基礎(chǔ)算法
令牌桶和漏桶
- 漏桶算法 的實(shí)現(xiàn)往往依賴于隊(duì)列,請求到達(dá)如果隊(duì)列未滿則直接放入隊(duì)列,然后有一個(gè)處理器按照固定頻率從隊(duì)列頭取出請求進(jìn)行處理。如果請求量大,則會(huì)導(dǎo)致隊(duì)列滿,那么新來的請求就會(huì)被拋棄。
- 令牌桶算法 則是一個(gè)存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌數(shù)有最大上限,超出之后就被丟棄或者拒絕。當(dāng)流量或者網(wǎng)絡(luò)請求到達(dá)時(shí),每個(gè)請求都要獲取一個(gè)令牌,如果能夠獲取到,則直接處理,并且令牌桶刪除一個(gè)令牌。如果獲取不到,該請求就要被限流,要么直接丟棄,要么在緩沖區(qū)等待。
令牌桶和漏桶對比
- 令牌桶是按照固定速率往桶中添加令牌,請求是否被處理需要看桶中令牌是否足夠,當(dāng)令牌數(shù)減為零時(shí)則拒絕新的請求;漏桶則是按照常量固定速率流出請求,流入請求速率任意,當(dāng)流入的請求數(shù)累積到漏桶容量時(shí),則新流入的請求被拒絕;
- 令牌桶限制的是平均流入速率,允許突發(fā)請求,只要有令牌就可以處理,支持一次拿3個(gè)令牌,4個(gè)令牌;漏桶限制的是常量流出速率,即流出速率是一個(gè)固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2,從而平滑突發(fā)流入速率;
- 令牌桶允許一定程度的突發(fā),而漏桶主要目的是平滑流出速率;
Guava RateLimiter
1.依賴
<dependency> ? ? <groupId>com.google.guava</groupId> ? ? <artifactId>guava</artifactId> ? ? <version>28.1-jre</version> ? ? <optional>true</optional> </dependency>
2.示例代碼
@Slf4j
@Configuration
public class RequestInterceptor implements HandlerInterceptor {
// 根據(jù)字符串分不同的令牌桶, 每天自動(dòng)清理緩存
private static LoadingCache<String, RateLimiter> cachesRateLimiter = CacheBuilder.newBuilder()
.maximumSize(1000) //設(shè)置緩存?zhèn)€數(shù)
/**
* expireAfterWrite是在指定項(xiàng)在一定時(shí)間內(nèi)沒有創(chuàng)建/覆蓋時(shí),會(huì)移除該key,下次取的時(shí)候從loading中取
* expireAfterAccess是指定項(xiàng)在一定時(shí)間內(nèi)沒有讀寫,會(huì)移除該key,下次取的時(shí)候從loading中取
* refreshAfterWrite是在指定時(shí)間內(nèi)沒有被創(chuàng)建/覆蓋,則指定時(shí)間過后,再次訪問時(shí),會(huì)去刷新該緩存,在新值沒有到來之前,始終返回舊值
* 跟expire的區(qū)別是,指定時(shí)間過后,expire是remove該key,下次訪問是同步去獲取返回新值;
* 而refresh則是指定時(shí)間后,不會(huì)remove該key,下次訪問會(huì)觸發(fā)刷新,新值沒有回來時(shí)返回舊值
*/
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<String, RateLimiter>() {
@Override
public RateLimiter load(String key) throws Exception {
// 新的字符串初始化 (限流每秒2個(gè)令牌響應(yīng))
return RateLimiter.create(2);
}
});
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("request請求地址path[{}] uri[{}]", request.getServletPath(), request.getRequestURI());
try {
String str = "hello";
// 令牌桶
RateLimiter rateLimiter = cachesRateLimiter.get(str);
if (!rateLimiter.tryAcquire()) {
System.out.println("too many requests.");
return false;
}
} catch (Exception e) {
// 解決攔截器的異常,全局異常處理器捕獲不到的問題
request.setAttribute("exception", e);
request.getRequestDispatcher("/error").forward(request, response);
}
return true;
}
}
3.測試
@RestController
@RequestMapping(value = "user")
public class UserController {
@GetMapping
public Result test2(){
System.out.println("1111");
return new Result(true,200,"");
}
}http://localhost:8080/user/
如果沒有result類,自己可以隨便返回個(gè)字符串
4.測試結(jié)果

其他
創(chuàng)建
RateLimiter提供了兩個(gè)工廠方法:
- 一個(gè)是平滑突發(fā)限流
RateLimiter r = RateLimiter.create(5); //項(xiàng)目啟動(dòng),直接允許5個(gè)令牌
- 一個(gè)是平滑預(yù)熱限流
RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS); //項(xiàng)目啟動(dòng)后3秒后才會(huì)到達(dá)設(shè)置的2個(gè)令牌
缺點(diǎn)
RateLimiter只能用于單機(jī)的限流,如果想要集群限流,則需要引入redis或者阿里開源的sentinel中間件。
TimeUnit.SECONDS);` //項(xiàng)目啟動(dòng)后3秒后才會(huì)到達(dá)設(shè)置的2個(gè)令牌
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Java LinkedHashMap與HashMap的使用
這篇文章主要通過幾個(gè)示例為大家詳細(xì)介紹了Java中LinkedHashMap與HashMap的常見使用和概述,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-10-10
Spring?Boot自動(dòng)配置源碼實(shí)例解析
Spring Boot作為Java領(lǐng)域最為流行的快速開發(fā)框架之一,其核心特性之一就是其強(qiáng)大的自動(dòng)配置機(jī)制,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot自動(dòng)配置源碼的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08
java數(shù)組實(shí)現(xiàn)隊(duì)列及環(huán)形隊(duì)列實(shí)現(xiàn)過程解析
這篇文章主要介紹了java數(shù)組實(shí)現(xiàn)隊(duì)列及環(huán)形隊(duì)列實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
Spring 使用 feign時(shí)設(shè)置header信息的操作
這篇文章主要介紹了Spring 使用 feign時(shí)設(shè)置header信息的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
java實(shí)現(xiàn)雙色球抽獎(jiǎng)算法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)雙色球抽獎(jiǎng)算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
spring?bean標(biāo)簽中的init-method和destroy-method詳解
這篇文章主要介紹了spring?bean標(biāo)簽中的init-method和destroy-method,在很多項(xiàng)目中,經(jīng)常在xml配置文件中看到init-method 或者 destroy-method ,因此整理收集下,方便以后參考和學(xué)習(xí),需要的朋友可以參考下2023-04-04
RandomAccessFile簡介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
RandomAccessFile 是隨機(jī)訪問文件(包括讀/寫)的類。它支持對文件隨機(jī)訪問的讀取和寫入,即我們可以從指定的位置讀取/寫入文件數(shù)據(jù)。這篇文章主要介紹了RandomAccessFile簡介,需要的朋友可以參考下2017-05-05

