一. 整合 google.guava
Controller:
package com..web; import com..anno.RateLimitAnno; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class LimiterController { @RateLimitAnno @RequestMapping("/limiter") public String limiter(HttpServletRequest request){ StringBuilder sb = new StringBuilder("{"); //正常返回 sb.append("'result':'0000','msg':'成功'"); return sb.append("}").toString(); } }
自定義注解:
package com..anno; import java.lang.annotation.*; @Inherited // 允許子類繼承 元注解 @Documented // 被 javadoc工具記錄 @Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE}) //注解可能出現在Java程序中的語法位置 @Retention(RetentionPolicy.RUNTIME) //注解保留時間,保留至運行時 public @interface RateLimitAnno { }
具體限流實現:
package com..util; import com.google.common.util.concurrent.RateLimiter; 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.context.annotation.Scope; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component @Scope @Aspect public class RateLimitAspect { final double permitsPerSecond = 5.0; //每秒生成5個令牌 final long warmupPeriod = 1; //在warmupPeriod時間內RateLimiter會增加它的速率,在抵達它的穩定速率或者最大速率之前 final TimeUnit timeUnit = TimeUnit.SECONDS; //參數warmupPeriod 的時間單位 /* * 創建一個穩定輸出令牌的RateLimiter,保證了平均每秒不超過qps個請求 * 當請求到來的速度超過了qps,保證每秒只處理qps個請求 * 當這個RateLimiter使用不足(即請求到來速度小於qps),會囤積最多qps個請求 * * 創建的是SmoothBursty 實例 平滑穩定 */ RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond); /** * * 根據指定的穩定吞吐率和預熱期來創建RateLimiter, * 這里的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少查詢), * 在這段預熱時間內,RateLimiter每秒分配的許可數會平穩地增長直到預熱期結束時達到其最大速率(只要存在足夠請求數來使其飽和)。 * 同樣地,如果RateLimiter 在warmupPeriod時間內閑置不用,它將會逐步地返回冷卻狀態。 * 也就是說,它會像它第一次被創建般經歷同樣的預熱期。 * 返回的RateLimiter 主要用於那些需要預熱期的資源,這些資源實際上滿足了請求(比如一個遠程服務), * 而不是在穩定(最大)的速率下可以立即被訪問的資源。 * 返回的RateLimiter 在冷卻狀態下啟動(即預熱期將會緊跟着發生),並且如果被長期閑置不用,它將回到冷卻狀態 * * 創建的是SmoothWarmingUp實例 平滑預熱 */ RateLimiter rl = RateLimiter.create(permitsPerSecond,warmupPeriod,timeUnit); //設置業務切入點為標注了自定義注解的位置 @Pointcut("@annotation(com.wondersgroup.anno.RateLimitAnno)") public void aspectService(){ } //統計 int countSuccess,countFail = 0; //環繞通知 @Around("aspectService()") public Object aroundMsg(ProceedingJoinPoint joinPoint){ Object obj = null; boolean flag = rateLimiter.tryAcquire(); // 在無延遲下的情況下獲得 // 從RateLimiter獲取一個許可,該方法會被阻塞直到獲取到請求。 // 如果存在等待的情況的話,告訴調用者獲取到該請求所需要的睡眠時間。該方法等同於acquire(1)。 //double waitTime = rabuyGoodsteLimiter.acquire(); 我非要得到令牌才返回 try{ if(flag){ //如果獲取了令牌,則可以繼續執行業務層面的邏輯 obj = joinPoint.proceed(); countSuccess++;//並發時,統計不准確!!! }else{ obj = "{'result':'0001','msg':'當前系統繁忙,請重試...'}"; //未獲取到令牌,直接返回 countFail++; } }catch(Throwable ex){ ex.printStackTrace(); } System.out.println(flag +" : "+ obj + " success:" + countSuccess + " , fail:" + countFail); return obj; } }
運行項目,訪問 http://localhost:8080/limiter
結果:
二. 整合 Redisson
package com..distributed; import org.redisson.Redisson; import org.redisson.api.*; import org.redisson.config.Config; import java.util.Random; import java.util.concurrent.CountDownLatch; public class RedisRateLimiter { static RedissonClient redisson = null; static RRateLimiter myLimiter; static { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379").setConnectionMinimumIdleSize(10); //創建Redisson客戶端 redisson = Redisson.create(config); /** * 基於Redis的分布式限流器可以用來在分布式環境下現在請求方的調用頻率。 * 既適用於不同Redisson實例下的多線程限流,也適用於相同Redisson實例下的多線程限流。 * 該算法不保證公平性。 */ myLimiter = redisson.getRateLimiter("my"); /** * Total rate for all RateLimiter instances * 作用在所有的RRateLimiter實例 * OVERALL * * Total rate for all RateLimiter instances working with the same Redisson instance * 作用在同一個Redisson實例創建的 RRateLimiter上面。 * PER_CLIENT * * return : 設置是否成功。 對同一個redis服務端,只需要設置一次。如果redis重啟需要重新設置 */ boolean bl = myLimiter.trySetRate(RateType.PER_CLIENT, 5, 1, RateIntervalUnit.SECONDS); } //結合redis,實現分布式的qpi接口限流 public static void test() { CountDownLatch cd = new CountDownLatch(1); Random rd = new Random(); for (int i = 0; i < 20; i++) { new Thread(() -> { try { cd.await(); //使得當前線程阻塞 Thread.sleep(rd.nextInt(1000)); //模擬20個請求的並發,有一點點先后順序的差異 } catch (InterruptedException e) { e.printStackTrace(); } finally { //獲取令牌 System.out.println(Thread.currentThread().getName() + " : " + myLimiter.tryAcquire()); } }).start(); } cd.countDown(); } public static void main(String[] args) { test(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //正常退出 status為0時為正常退出程序,也就是結束當前正在運行中的java虛擬機 System.exit(0); } }
運行 main 函數
結果:
pom 配置文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.gupao.ls</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency> <!--Redisson插件--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.10.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency> <!--guava JAR--> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>