Guava RateLimiter 實現(xiàn) API 限流,這才是正確的姿勢!
點擊關注公眾號,Java干貨及時送達
Guava提供的RateLimiter可以限制物理或邏輯資源的被訪問速率,咋一聽有點像java并發(fā)包下的Samephore,但是又不相同,RateLimiter控制的是速率,Samephore控制的是并發(fā)量。
???com.google.guava
???guava
???23.0
public?static?void?main(String[]?args)?{
????String?start?=?new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss").format(new?Date());
????RateLimiter?limiter?=?RateLimiter.create(1.0);?//?這里的1表示每秒允許處理的量為1個
????for?(int?i?=?1;?i?<=?10;?i++)?{?
????????limiter.acquire();//?請求RateLimiter,?超過permits會被阻塞
????????System.out.println("call?execute.."?+?i);
????}
????String?end?=?new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss").format(new?Date());
????System.out.println("start?time:"?+?start);
????System.out.println("end?time:"?+?end);
}

另外,Java 多線程系列面試題和答案全部整理好了,微信搜索Java技術棧,在后臺發(fā)送:面試,可以在線閱讀。
實際項目中使用
@Service
public?class?GuavaRateLimiterService?{
????/*每秒控制5個許可*/
????RateLimiter?rateLimiter?=?RateLimiter.create(5.0);
?
????/**
?????*?獲取令牌
?????*
?????*?@return
?????*/
????public?boolean?tryAcquire()?{
????????return?rateLimiter.tryAcquire();
????}
????
}
??@Autowired
????private?GuavaRateLimiterService?rateLimiterService;
????
????@ResponseBody
????@RequestMapping("/ratelimiter")
????public?Result?testRateLimiter(){
????????if(rateLimiterService.tryAcquire()){
????????????return?ResultUtil.success1(1001,"成功獲取許可");
????????}
????????return?ResultUtil.success1(1002,"未獲取到許可");
????}
jmeter起10個線程并發(fā)訪問接口,測試結果如下:

可以發(fā)現(xiàn),10個并發(fā)訪問總是只有6個能獲取到許可,結論就是能獲取到RateLimiter.create(n)中n+1個許可,總體來看Guava的RateLimiter是比較優(yōu)雅的。本文就是簡單的提了下RateLimiter的使用。
翻閱發(fā)現(xiàn)使用上述方式使用RateLimiter的方式不夠優(yōu)雅,盡管我們可以把RateLimiter的邏輯包在service里面,controller直接調用即可,但是如果我們換成:自定義注解+切面 的方式實現(xiàn)的話,會優(yōu)雅的多,詳細見下面代碼:
import?java.lang.annotation.*;
?
/**
?*?自定義注解可以不包含屬性,成為一個標識注解
?*/
@Inherited
@Documented
@Target({ElementType.METHOD,?ElementType.FIELD,?ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public?@interface?RateLimitAspect?{
???
}
import?com.google.common.util.concurrent.RateLimiter;
import?com.simons.cn.springbootdemo.util.ResultUtil;
import?net.sf.json.JSONObject;
import?org.aspectj.lang.ProceedingJoinPoint;
import?org.aspectj.lang.annotation.Around;
import?org.aspectj.lang.annotation.Aspect;
import?org.aspectj.lang.annotation.Pointcut;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.context.annotation.Scope;
import?org.springframework.stereotype.Component;
?
import?javax.servlet.ServletOutputStream;
import?javax.servlet.http.HttpServletResponse;
import?java.io.IOException;
?
@Component
@Scope
@Aspect
public?class?RateLimitAop?{
?
????@Autowired
????private?HttpServletResponse?response;
?
????private?RateLimiter?rateLimiter?=?RateLimiter.create(5.0);?//比如說,我這里設置"并發(fā)數(shù)"為5
?
????@Pointcut("@annotation(com.simons.cn.springbootdemo.aspect.RateLimitAspect)")
????public?void?serviceLimit()?{
?
????}
?
????@Around("serviceLimit()")
????public?Object?around(ProceedingJoinPoint?joinPoint)?{
????????Boolean?flag?=?rateLimiter.tryAcquire();
????????Object?obj?=?null;
????????try?{
????????????if?(flag)?{
????????????????obj?=?joinPoint.proceed();
????????????}else{
????????????????String?result?=?JSONObject.fromObject(ResultUtil.success1(100,?"failure")).toString();
????????????????output(response,?result);
????????????}
????????}?catch?(Throwable?e)?{
????????????e.printStackTrace();
????????}
????????System.out.println("flag="?+?flag?+?",obj="?+?obj);
????????return?obj;
????}
????
????public?void?output(HttpServletResponse?response,?String?msg)?throws?IOException?{
????????response.setContentType("application/json;charset=UTF-8");
????????ServletOutputStream?outputStream?=?null;
????????try?{
????????????outputStream?=?response.getOutputStream();
????????????outputStream.write(msg.getBytes("UTF-8"));
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}?finally?{
????????????outputStream.flush();
????????????outputStream.close();
????????}
????}
}
import?com.simons.cn.springbootdemo.aspect.RateLimitAspect;
import?com.simons.cn.springbootdemo.util.ResultUtil;
import?org.springframework.stereotype.Controller;
import?org.springframework.web.bind.annotation.RequestMapping;
import?org.springframework.web.bind.annotation.ResponseBody;
?
/**
?*?類描述:RateLimit限流測試(基于?注解+切面?方式)
?*?創(chuàng)建人:simonsfan
?*/
@Controller
public?class?TestController?{
?
????@ResponseBody
????@RateLimitAspect?????????//可以非常方便的通過這個注解來實現(xiàn)限流
????@RequestMapping("/test")
????public?String?test(){
????????return?ResultUtil.success1(1001,?"success").toString();
????}
這樣通過自定義注解@RateLimiterAspect來動態(tài)的加到需要限流的接口上,個人認為是比較優(yōu)雅的實現(xiàn)吧。推薦一個 Spring Boot 基礎教程及實戰(zhàn)示例:https://www.javastack.cn/categories/Spring-Boot/
壓測結果:

可以看到,10個線程中無論壓測多少次,并發(fā)數(shù)總是限制在6,也就實現(xiàn)了限流。另外,關注公眾號Java技術棧,在后臺回復:面試,可以獲取我整理的 Java 多線程系列面試題和答案,非常齊全。
作者:飯一碗
來源:https://blog.csdn.net/fanrenxiang/article/details/80949079
往 期 推 薦
3、在 IntelliJ IDEA 中這樣使用 Git,賊方便了!
點分享
點收藏
點點贊
點在看





