成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇帶給你 Sentinel 和常用流控算法

開發 前端 算法
本文主要講述常見的幾種限流算法:計數器算法、漏桶算法、令牌桶算法。然后結合我對 Sentinel 1.8.0 的理解,給大家分享 Sentinel 在源碼中如何使用這些算法進行流控判斷。

[[401361]]

本文主要講述常見的幾種限流算法:計數器算法、漏桶算法、令牌桶算法。然后結合我對 Sentinel 1.8.0 的理解,給大家分享 Sentinel 在源碼中如何使用這些算法進行流控判斷。

計數器限流算法

我們可以直接通過一個計數器,限制每一秒鐘能夠接收的請求數。比如說 qps定為 1000,那么實現思路就是從第一個請求進來開始計時,在接下去的 1s 內,每來一個請求,就把計數加 1,如果累加的數字達到了 1000,那么后續的請求就會被全部拒絕。等到 1s 結束后,把計數恢復成 0 ,重新開始計數。

優點:實現簡單

缺點:如果1s 內的前半秒,已經通過了 1000 個請求,那后面的半秒只能請求拒絕,我們把這種現象稱為“突刺現象”。

實現代碼案例:

  1. public class Counter { 
  2.     public long timeStamp = getNowTime(); 
  3.     public int reqCount = 0; 
  4.     public final int limit = 100; // 時間窗口內最大請求數 
  5.     public final long interval = 1000; // 時間窗口ms 
  6.  
  7.     public boolean limit() { 
  8.         long now = getNowTime(); 
  9.         if (now < timeStamp + interval) { 
  10.             // 在時間窗口內 
  11.             reqCount++; 
  12.             // 判斷當前時間窗口內是否超過最大請求控制數 
  13.             return reqCount <= limit; 
  14.         } else { 
  15.             timeStamp = now; 
  16.             // 超時后重置 
  17.             reqCount = 1; 
  18.             return true
  19.         } 
  20.     } 
  21.  
  22.     public long getNowTime() { 
  23.         return System.currentTimeMillis(); 
  24.     } 

滑動時間窗算法

滑動窗口,又稱 Rolling Window。為了解決計數器算法的缺陷,我們引入了滑動窗口算法。下面這張圖,很好地解釋了滑動窗口算法:

在上圖中,整個紅色的矩形框表示一個時間窗口,在我們的例子中,一個時間窗口就是一分鐘。然后我們將時間窗口進行劃分,比如圖中,我們就將滑動窗口 劃成了6格,所以每格代表的是10秒鐘。每過10秒鐘,我們的時間窗口就會往右滑動一格。每一個格子都有自己獨立的計數器counter,比如當一個請求 在0:35秒的時候到達,那么0:30~0:39對應的counter就會加1。

那么滑動窗口怎么解決剛才的臨界問題的呢?我們可以看上圖,0:59到達的100個請求會落在灰色的格子中,而1:00到達的請求會落在橘黃色的格子中。當時間到達1:00時,我們的窗口會往右移動一格,那么此時時間窗口內的總請求數量一共是200個,超過了限定的100個,所以此時能夠檢測出來觸發了限流。

我再來回顧一下剛才的計數器算法,我們可以發現,計數器算法其實就是滑動窗口算法。只是它沒有對時間窗口做進一步地劃分,所以只有1格。

由此可見,當滑動窗口的格子劃分的越多,那么滑動窗口的滾動就越平滑,限流的統計就會越精確。

實現代碼案例:

  1. public class SlideWindow { 
  2.  
  3.     /** 隊列id和隊列的映射關系,隊列里面存儲的是每一次通過時候的時間戳,這樣可以使得程序里有多個限流隊列 */ 
  4.     private volatile static Map<String, List<Long>> MAP = new ConcurrentHashMap<>(); 
  5.  
  6.     private SlideWindow() {} 
  7.  
  8.     public static void main(String[] args) throws InterruptedException { 
  9.         while (true) { 
  10.             // 任意10秒內,只允許2次通過 
  11.             System.out.println(LocalTime.now().toString() + SlideWindow.isGo("ListId", 2, 10000L)); 
  12.             // 睡眠0-10秒 
  13.             Thread.sleep(1000 * new Random().nextInt(10)); 
  14.         } 
  15.     } 
  16.  
  17.     /** 
  18.      * 滑動時間窗口限流算法 
  19.      * 在指定時間窗口,指定限制次數內,是否允許通過 
  20.      * 
  21.      * @param listId     隊列id 
  22.      * @param count      限制次數 
  23.      * @param timeWindow 時間窗口大小 
  24.      * @return 是否允許通過 
  25.      */ 
  26.     public static synchronized boolean isGo(String listId, int count, long timeWindow) { 
  27.         // 獲取當前時間 
  28.         long nowTime = System.currentTimeMillis(); 
  29.         // 根據隊列id,取出對應的限流隊列,若沒有則創建 
  30.         List<Long> list = MAP.computeIfAbsent(listId, k -> new LinkedList<>()); 
  31.         // 如果隊列還沒滿,則允許通過,并添加當前時間戳到隊列開始位置 
  32.         if (list.size() < count) { 
  33.             list.add(0, nowTime); 
  34.             return true
  35.         } 
  36.  
  37.         // 隊列已滿(達到限制次數),則獲取隊列中最早添加的時間戳 
  38.         Long farTime = list.get(count - 1); 
  39.         // 用當前時間戳 減去 最早添加的時間戳 
  40.         if (nowTime - farTime <= timeWindow) { 
  41.             // 若結果小于等于timeWindow,則說明在timeWindow內,通過的次數大于count 
  42.             // 不允許通過 
  43.             return false
  44.         } else { 
  45.             // 若結果大于timeWindow,則說明在timeWindow內,通過的次數小于等于count 
  46.             // 允許通過,并刪除最早添加的時間戳,將當前時間添加到隊列開始位置 
  47.             list.remove(count - 1); 
  48.             list.add(0, nowTime); 
  49.             return true
  50.         } 
  51.     } 
  52.  

在 Sentinel 中 通過 LeapArray 結構來實現時間窗算法, 它的核心代碼如下(只列舉獲取時間窗方法):

  1. /** 
  2.      * 獲取當前的時間窗 
  3.      * 
  4.      * Get bucket item at provided timestamp
  5.      * 
  6.      * @param timeMillis a valid timestamp in milliseconds 
  7.      * @return current bucket item at provided timestamp if the time is valid; null if time is invalid 
  8.      */ 
  9. public WindowWrap<T> currentWindow(long timeMillis) { 
  10.   if (timeMillis < 0) { 
  11.     return null
  12.   } 
  13.  
  14.   int idx = calculateTimeIdx(timeMillis); 
  15.   // Calculate current bucket start time
  16.   // 計算窗口的開始時間,計算每個格子的開始時間 
  17.   long windowStart = calculateWindowStart(timeMillis); 
  18.  
  19.   /* 
  20.          * Get bucket item at given time from the array. 
  21.          * 
  22.          * (1) Bucket is absent, then just create a new bucket and CAS update to circular array. 
  23.          * (2) Bucket is up-to-datethen just return the bucket. 
  24.          * (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets. 
  25.          */ 
  26.   while (true) { 
  27.     WindowWrap<T> old = array.get(idx); 
  28.     // 如果沒有窗格,創建窗格 
  29.     if (old == null) { 
  30.       /* 
  31.                  *     B0       B1      B2    NULL      B4 
  32.                  * ||_______|_______|_______|_______|_______||___ 
  33.                  * 200     400     600     800     1000    1200  timestamp 
  34.                  *                             ^ 
  35.                  *                          time=888 
  36.                  *            bucket is empty, so create new and update 
  37.                  * 
  38.                  * If the old bucket is absent, then we create a new bucket at {@code windowStart}, 
  39.                  * then try to update circular array via a CAS operation. Only one thread can 
  40.                  * succeed to update, while other threads yield its time slice. 
  41.                  */ 
  42.       WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); 
  43.       if (array.compareAndSet(idx, null, window)) { 
  44.         // Successfully updated, return the created bucket. 
  45.         return window; 
  46.       } else { 
  47.         // Contention failed, the thread will yield its time slice to wait for bucket available. 
  48.         Thread.yield(); 
  49.       } 
  50.       // 當前窗格存在,返回歷史窗格 
  51.     } else if (windowStart == old.windowStart()) { 
  52.       /* 
  53.                  *     B0       B1      B2     B3      B4 
  54.                  * ||_______|_______|_______|_______|_______||___ 
  55.                  * 200     400     600     800     1000    1200  timestamp 
  56.                  *                             ^ 
  57.                  *                          time=888 
  58.                  *            startTime of Bucket 3: 800, so it's up-to-date 
  59.                  * 
  60.                  * If current {@code windowStart} is equal to the start timestamp of old bucket, 
  61.                  * that means the time is within the bucket, so directly return the bucket. 
  62.                  */ 
  63.       return old; 
  64.       // 
  65.     } else if (windowStart > old.windowStart()) { 
  66.       /* 
  67.                  *   (old) 
  68.                  *             B0       B1      B2    NULL      B4 
  69.                  * |_______||_______|_______|_______|_______|_______||___ 
  70.                  * ...    1200     1400    1600    1800    2000    2200  timestamp 
  71.                  *                              ^ 
  72.                  *                           time=1676 
  73.                  *          startTime of Bucket 2: 400, deprecated, should be reset 
  74.                  * 
  75.                  * If the start timestamp of old bucket is behind provided time, that means 
  76.                  * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}. 
  77.                  * Note that the reset and clean-up operations are hard to be atomic, 
  78.                  * so we need a update lock to guarantee the correctness of bucket update
  79.                  * 
  80.                  * The update lock is conditional (tiny scope) and will take effect only when 
  81.                  * bucket is deprecated, so in most cases it won't lead to performance loss. 
  82.                  */ 
  83.       if (updateLock.tryLock()) { 
  84.         try { 
  85.           // Successfully get the update lock, now we reset the bucket. 
  86.           // 清空所有的窗格數據 
  87.           return resetWindowTo(old, windowStart); 
  88.         } finally { 
  89.           updateLock.unlock(); 
  90.         } 
  91.       } else { 
  92.         // Contention failed, the thread will yield its time slice to wait for bucket available. 
  93.         Thread.yield(); 
  94.       } 
  95.       // 如果時鐘回撥,重新創建時間格 
  96.     } else if (windowStart < old.windowStart()) { 
  97.       // Should not go through here, as the provided time is already behind. 
  98.       return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); 
  99.     } 
  100.   } 

漏桶算法

漏桶算法(Leaky Bucket)是網絡世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)時經常使用的一種算法,它的主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。漏桶算法提供了一種機制,通過它,突發流量可以被整形以便為網絡提供一個穩定的流量, 執行過程如下圖所示。

實現代碼案例:

  1. public class LeakyBucket { 
  2.   public long timeStamp = System.currentTimeMillis();  // 當前時間 
  3.   public long capacity; // 桶的容量 
  4.   public long rate; // 水漏出的速度 
  5.   public long water; // 當前水量(當前累積請求數) 
  6.  
  7.   public boolean grant() { 
  8.     long now = System.currentTimeMillis(); 
  9.     // 先執行漏水,計算剩余水量 
  10.     water = Math.max(0, water - (now - timeStamp) * rate);  
  11.  
  12.     timeStamp = now; 
  13.     if ((water + 1) < capacity) { 
  14.       // 嘗試加水,并且水還未滿 
  15.       water += 1; 
  16.       return true
  17.     } else { 
  18.       // 水滿,拒絕加水 
  19.       return false
  20.     } 
  21.   } 

說明:

(1)未滿加水:通過代碼 water +=1進行不停加水的動作。

(2)漏水:通過時間差來計算漏水量。

(3)剩余水量:總水量-漏水量。

在 Sentine 中RateLimiterController 實現了了漏桶算法 , 核心代碼如下

  1. @Override 
  2. public boolean canPass(Node node, int acquireCount, boolean prioritized) { 
  3.   // Pass when acquire count is less or equal than 0. 
  4.   if (acquireCount <= 0) { 
  5.     return true
  6.   } 
  7.   // Reject when count is less or equal than 0. 
  8.   // Otherwise,the costTime will be max of long and waitTime will overflow in some cases. 
  9.   if (count <= 0) { 
  10.     return false
  11.   } 
  12.  
  13.   long currentTime = TimeUtil.currentTimeMillis(); 
  14.   // Calculate the interval between every two requests. 
  15.   // 計算時間間隔 
  16.   long costTime = Math.round(1.0 * (acquireCount) / count * 1000); 
  17.  
  18.   // Expected pass time of this request. 
  19.   // 期望的執行時間 
  20.   long expectedTime = costTime + latestPassedTime.get(); 
  21.  
  22.   // 當前時間 > 期望時間 
  23.   if (expectedTime <= currentTime) { 
  24.     // Contention may exist here, but it's okay. 
  25.     // 可以通過,并且設置最后通過時間 
  26.     latestPassedTime.set(currentTime); 
  27.     return true
  28.   } else { 
  29.     // Calculate the time to wait. 
  30.     // 等待時間 = 期望時間 - 最后時間 - 當前時間 
  31.     long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis(); 
  32.     // 等待時間 > 最大排隊時間 
  33.     if (waitTime > maxQueueingTimeMs) { 
  34.       return false
  35.     } else { 
  36.       // 上次時間 + 間隔時間 
  37.       long oldTime = latestPassedTime.addAndGet(costTime); 
  38.       try { 
  39.         // 等待時間 
  40.         waitTime = oldTime - TimeUtil.currentTimeMillis(); 
  41.         // 等待時間 > 最大排隊時間 
  42.         if (waitTime > maxQueueingTimeMs) { 
  43.           latestPassedTime.addAndGet(-costTime); 
  44.           return false
  45.         } 
  46.         // in race condition waitTime may <= 0 
  47.         // 休眠等待 
  48.         if (waitTime > 0) { 
  49.           Thread.sleep(waitTime); 
  50.         } 
  51.         // 等待完了,就放行 
  52.         return true
  53.       } catch (InterruptedException e) { 
  54.       } 
  55.     } 
  56.   } 
  57.   return false

令牌桶算法

令牌桶算法是網絡流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。典型情況下,令牌桶算法用來控制發送到網絡上的數據的數目,并允許突發數據的發送。如下圖所示:

簡單的說就是,一邊請求時會消耗桶內的令牌,另一邊會以固定速率往桶內放令牌。當消耗的請求大于放入的速率時,進行相應的措施,比如等待,或者拒絕等。

實現代碼案例:

  1. public class TokenBucket { 
  2.   public long timeStamp = System.currentTimeMillis();  // 當前時間 
  3.   public long capacity; // 桶的容量 
  4.   public long rate; // 令牌放入速度 
  5.   public long tokens; // 當前令牌數量 
  6.  
  7.   public boolean grant() { 
  8.     long now = System.currentTimeMillis(); 
  9.     // 先添加令牌 
  10.     tokens = Math.min(capacity, tokens + (now - timeStamp) * rate); 
  11.     timeStamp = now; 
  12.     if (tokens < 1) { 
  13.       // 若不到1個令牌,則拒絕 
  14.       return false
  15.     } else { 
  16.       // 還有令牌,領取令牌 
  17.       tokens -= 1; 
  18.       return true
  19.     } 
  20.   } 

Sentinel 在 WarmUpController 中運用到了令牌桶算法,在這里可以實現對系統的預熱,設定預熱時間和水位線,對于預熱期間多余的請求直接拒絕掉。

  1. public boolean canPass(Node node, int acquireCount, boolean prioritized) { 
  2.   long passQps = (long) node.passQps(); 
  3.  
  4.   long previousQps = (long) node.previousPassQps(); 
  5.   syncToken(previousQps); 
  6.  
  7.   // 開始計算它的斜率 
  8.   // 如果進入了警戒線,開始調整他的qps 
  9.   long restToken = storedTokens.get(); 
  10.   if (restToken >= warningToken) { 
  11.     long aboveToken = restToken - warningToken; 
  12.     // 消耗的速度要比warning快,但是要比慢 
  13.     // current interval = restToken*slope+1/count 
  14.     double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); 
  15.     if (passQps + acquireCount <= warningQps) { 
  16.       return true
  17.     } 
  18.   } else { 
  19.     if (passQps + acquireCount <= count) { 
  20.       return true
  21.     } 
  22.   } 
  23.  
  24.   return false

限流算法總結

計數器 VS 時間窗

時間窗算法的本質也是通過計數器算法實現的。

時間窗算法格子劃分的越多,那么滑動窗口的滾動就越平滑,限流的統計就會越精確,但是也會占用更多的內存存儲。

漏桶 VS 令牌桶

漏桶算法和令牌桶算法本質上是為了做流量整形或速率限制,避免系統因為大流量而被打崩,但是兩者的核心差異在于限流的方向是相反的

漏桶:限制的是流量的流出速率,是相對固定的。

令牌桶 :限制的是流量的平均流入速率,并且允許一定程度的突然性流量,最大速率為桶的容量和生成token的速率。

在某些場景中,漏桶算法并不能有效的使用網絡資源,因為漏桶的漏出速率是相對固定的,所以在網絡情況比較好并且沒有擁塞的狀態下,漏桶依然是會有限制的,并不能放開量,因此并不能有效的利用網絡資源。而令牌桶算法則不同,其在限制平均速率的同時,支持一定程度的突發流量。

參考文檔

https://www.cnblogs.com/linjiqin/p/9707713.html

https://www.cnblogs.com/dijia478/p/13807826.html

 

責任編輯:姜華 來源: 運維開發故事
相關推薦

2021-05-24 08:09:21

SentinelRedis 流控原理

2021-07-12 06:11:14

SkyWalking 儀表板UI篇

2023-03-09 07:47:56

BeanFactorSpring框架

2022-04-27 09:09:57

架構師術語技術語言

2021-10-28 08:51:53

GPIO軟件框架 Linux

2021-04-14 07:55:45

Swift 協議Protocol

2022-02-25 15:50:05

OpenHarmonToggle組件鴻蒙

2023-03-13 09:31:04

2021-07-08 07:30:13

Webpack 前端Tree shakin

2021-05-08 08:36:40

ObjectString前端

2021-04-23 08:59:35

ClickHouse集群搭建數據庫

2022-11-24 06:58:44

Ansible

2021-06-21 14:36:46

Vite 前端工程化工具

2021-01-28 08:55:48

Elasticsear數據庫數據存儲

2023-03-29 07:45:58

VS編輯區編程工具

2021-04-14 14:16:58

HttpHttp協議網絡協議

2021-04-08 11:00:56

CountDownLaJava進階開發

2024-06-13 08:34:48

2022-03-22 09:09:17

HookReact前端

2021-04-01 10:51:55

MySQL鎖機制數據庫
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 一区二区三区四区不卡视频 | 奇米四色影视 | 欧美伊人久久久久久久久影院 | 99re视频这里只有精品 | 一级特黄在线 | 国产精品大片 | 国产视频中文字幕 | 国产色网站 | 精品一区二区三区在线观看 | 国产精品一区二区在线 | 亚洲精品一区二区在线观看 | 成人免费福利视频 | 国产伊人精品 | 亚洲电影第1页 | 日本国产一区二区 | 成在线人视频免费视频 | 国产91在线播放 | 在线观看中文字幕视频 | 天天拍天天色 | 精品日韩一区二区 | 亚洲精品免费在线观看 | 国产综合久久久久久鬼色 | 日韩中文字幕 | 久久久精品久久 | 国产精品免费一区二区三区四区 | 最新中文字幕在线 | 色视频网站| 欧美日韩精品综合 | 欧美在线二区 | 欧美大片一区二区 | 欧美一级欧美三级在线观看 | 99精品国产一区二区三区 | 91精品国产91久久久久久三级 | 久久精品一区 | 色综合天天综合网国产成人网 | 视频一区二区中文字幕 | 久久五月婷 | 国产精品视频999 | 国产精品亚洲精品 | 欧美黄色精品 | 精品国产乱码久久久久久果冻传媒 |