Java 對IP請求進行限流.


高並發系統下, 有三把利器 緩存 降級 限流.

  • 緩存: 將常用數據緩存起來, 減少數據庫或者磁盤IO
  • 降級: 保護核心系統, 降低非核心業務請求響應
  • 限流: 在某一個時間窗口內對請求進行限速, 保護系統

 本文主要介紹限流, 常見限流算法中又分為計數器算法, 漏桶算法, 令牌桶算法.

計數器算法

比較簡單, 直接用一個map + counter即可實現. 請求來了, 以IP為key,

查詢下之前響應次數, 如果調用次數超出MAX_COUT, 返回失敗, 屬於簡單粗暴型選手.

漏桶算法

請求全部進入漏桶, 漏桶恆定速率輸出反饋. 這樣可以保證數據傳輸平滑,

但是無法預防突發大量請求, 一秒來了100個請求, 都要阻塞排隊, 從小水管輸出數據.

 

令牌桶算法

令牌桶是以固定速度往桶里存令牌, 例如一秒存1000個令牌, 業務請求來了, 直接從桶里獲取令牌響應輸出.

跟漏桶的差異在於, 他可以預存令牌, 如果一秒鍾來了100個請求, 桶里有100個令牌,

那么可以立刻響應給客戶端, 而不是排隊輸出.

 

令牌桶的實現

guava中提供了令牌桶的一個封裝實現RateLimiter, 可以直接調用, 省的我們自己包裝ConcurrentHashMap + Timer.

我們預設的場景是服務器端提供一個API供不同客戶端查詢, 要限流每個IP每秒只能調用兩次該API.

首先要定義一個服務器端的緩存, 定期清理即可, 緩存 IP : 令牌桶

 1     // 根據IP分不同的令牌桶, 每天自動清理緩存
 2     private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder()
 3             .maximumSize(1000)
 4             .expireAfterWrite(1, TimeUnit.DAYS)
 5             .build(new CacheLoader<String, RateLimiter>() {
 6                 @Override
 7                 public RateLimiter load(String key) throws Exception {
 8                     // 新的IP初始化 (限流每秒兩個令牌響應)
 9                     return RateLimiter.create(2);
10                 }
11             });

然后在業務代碼中進行限流調用

 1     private static void login(int i) throws ExecutionException {
 2         // 模擬IP的key
 3         String ip = String.valueOf(i).charAt(0) + "";
 4         RateLimiter limiter = caches.get(ip);
 5 
 6         if (limiter.tryAcquire()) {
 7             System.out.println(i + " success " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
 8         } else {
 9             System.out.println(i + " failed " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
10         }
11     }

模擬客戶端調用

1         for (int i = 0; i < 1000; i++) {
2             // 模擬實際業務請求
3             Thread.sleep(100);
4             login(i);
5         }

完整代碼

 1 public class doLimit {
 2 
 3     // 根據IP分不同的令牌桶, 每天自動清理緩存
 4     private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder()
 5             .maximumSize(1000)
 6             .expireAfterWrite(1, TimeUnit.DAYS)
 7             .build(new CacheLoader<String, RateLimiter>() {
 8                 @Override
 9                 public RateLimiter load(String key) throws Exception {
10                     // 新的IP初始化 (限流每秒兩個令牌響應)
11                     return RateLimiter.create(2);
12                 }
13             });
14 
15     public static void main(String[] args) throws InterruptedException, ExecutionException {
16         for (int i = 0; i < 1000; i++) {
17             // 模擬實際業務請求
18             Thread.sleep(100);
19             login(i);
20         }
21     }
22 
23     private static void login(int i) throws ExecutionException {
24         // 模擬IP的key
25         String ip = String.valueOf(i).charAt(0) + "";
26         RateLimiter limiter = caches.get(ip);
27 
28         if (limiter.tryAcquire()) {
29             System.out.println(i + " success " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
30         } else {
31             System.out.println(i + " failed " + new SimpleDateFormat("HH:mm:ss.sss").format(new Date()));
32         }
33     }
34 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM