簡單的java實現滑動時間窗口限流算法

2020-10-14 Java正道的光

在網上搜滑動時間窗口限流算法,大多都太複雜了,本人實現了個簡單的,先上代碼:

import java.time.LocalTime;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.Random;import java.util.concurrent.ConcurrentHashMap;/** * 滑動時間窗口限流工具 * 本限流工具只適用於單機版,如果想要做全局限流,可以按本程序的思想,用redis的List結構去實現 * * @author dijia478 * @date 2020-10-13 10:53 */public class SlideWindow { /** 隊列id和隊列的映射關係,隊列裡面存儲的是每一次通過時候的時間戳,這樣可以使得程序裡有多個限流隊列 */ private volatile static Map<String, List<Long>> MAP = new ConcurrentHashMap<>(); private SlideWindow() {} public static void main(String[] args) throws InterruptedException { while (true) { // 任意10秒內,只允許2次通過 System.out.println(LocalTime.now().toString() + SlideWindow.isGo("ListId", 2, 10000L)); // 睡眠0-10秒 Thread.sleep(1000 * new Random().nextInt(10)); } } /** * 滑動時間窗口限流算法 * 在指定時間窗口,指定限制次數內,是否允許通過 * * @param listId 隊列id * @param count 限制次數 * @param timeWindow 時間窗口大小 * @return 是否允許通過 */ public static synchronized boolean isGo(String listId, int count, long timeWindow) { // 獲取當前時間 long nowTime = System.currentTimeMillis(); // 根據隊列id,取出對應的限流隊列,若沒有則創建 List<Long> list = MAP.computeIfAbsent(listId, k -> new ArrayList<>()); // 如果隊列還沒滿,則允許通過,並添加當前時間戳到隊列開始位置 if (list.size() < count) { list.add(0, nowTime); return true; } // 隊列已滿(達到限制次數),則獲取隊列中最早添加的時間戳 Long farTime = list.get(count - 1); // 用當前時間戳 減去 最早添加的時間戳 if (nowTime - farTime <= timeWindow) { // 若結果小於等於timeWindow,則說明在timeWindow內,通過的次數大於count // 不允許通過 return false; } else { // 若結果大於timeWindow,則說明在timeWindow內,通過的次數小於等於count // 允許通過,並刪除最早添加的時間戳,將當前時間添加到隊列開始位置 list.remove(count - 1); list.add(0, nowTime); return true; } }}

運行可以看到,任意10秒內,通過的次數不超過2次。或者按照實現原理來說,任意通過2次內的時間差,都不超過10秒:

這裡畫圖做說明,為什麼這樣可以做到滑動窗口限流,假設10秒內允許通過5次

1.這條線就是隊列list,當第一個事件進來,隊列大小是0,時間是第1秒:

2.因為size=0,小於5,都沒有到限制的次數,完全不用考慮時間窗口,直接把這次事件的時間戳放到0的位置:

3.第2.8秒的時候,第二個事件來了。因為此時size=1,還是小於5,把這次事件的時間戳放到0的位置,原來第1秒來的事件時間戳會往後移動一格:

4.陸續的又來了3個事件,隊列大小變成了5,先來的時間戳依次向後移動。此時,第6個事件來了,時間是第8秒:

5.因為size=5,不小於5,此時已經達到限制次數,以後都需要考慮時間窗口了。所以取出位置4的時間(離現在最遠的時間),和第6個事件的時間戳做比較:

6.得到的差是7秒,小於時間窗口10秒,說明在10秒內,來的事件個數大於5了,所以本次不允許通過:

7.接下來即便來上100個事件,只要時間差小於等於10秒,都同上,拒絕通過:

8.第11.1秒,第101次事件過來了。因為size=5,不小於5,所以取出位置4的時間(離現在最遠的時間),和第101個事件的時間戳做比較:

9.得到的差是10.1秒,大於時間窗口10秒,說明在10秒內,來的事件個數小於等於5了,所以本次允許通過:

10.刪除位置4的時間(離現在最遠的時間),把這次事件的時間戳放到0的位置,後面的時間戳依次向後移動:

往後再來其他事件,就是重複4-10的步驟,即可實現,在任意滑動時間窗口內,限制通過的次數

其本質思想是轉換概念,將原本問題的確定時間大小,進行次數限制。轉換成確定次數大小,進行時間限制。

相關焦點

  • 限流算法之令牌桶法(Java實現)
    限流算法之令牌桶法(Java實現)前面2篇說過限速算法,可以參考以下:限流算法之計數器算法(Java實現)限流算法之滑動窗口法(Java實現)令牌桶算計介紹今天簡單解釋另一種令牌桶法大概描述如下:所有的請求在處理之前都需要拿到一個可用的令牌才會被處理;獲取不到令牌,則請求返回失敗根據限流大小,設置按照一定的速率往桶裡添加令牌;桶設置最大的放置令牌限制,當桶滿時、新添加的令牌就被丟棄或者拒絕;下面就是一個簡單的示意圖:
  • 微服務中的限流邏輯與算法
    常見的限流算法常見的限流算法主要有:計數器、固定窗口,滑動窗口、漏桶、令牌桶。接下來我們分別介紹下這幾種限流算法。在具體實現時,如果該計數器是存在單機內存中,那麼就實現了單機限流;而如果存在例如Redis中,集群中的所有節點依次為限流依據,那麼就算實現了集群限流算法。
  • 限流,永遠都不是一件簡單的事!
    滑動窗口計數器為了解決上面的臨界的問題,我們這裡可以使用滑動窗口來解決這個問題:其實換個角度想,我們普通的計數器其實就是窗口數量為1的滑動窗口計數器,只要我們分的窗口越多,我們使用計數器方案的時候統計就會越精確,但是相對來說維護的窗口的成本就會增加,等會我們介紹sentinel的時候會詳細介紹他是怎麼實現滑動窗口計數的。
  • 三分鐘快速了解微服務中的限流邏輯與算法
    在具體實現時,如果該計數器是存在單機內存中,那麼就實現了單機限流;而如果存在例如Redis中,集群中的所有節點依次為限流依據,那麼就算實現了集群限流算法。<滑動窗口限流>滑動窗口限流解決了固定窗口臨界值的問題,可以保證任意時間窗口內都不會超過限流閥值
  • 6 種限流實現方案
    服務端限流服務端限流需要配合限流的算法來執行,而算法相當於執行限流的「大腦」,用於指導限制方案的實現。1.時間窗口算法所謂的滑動時間算法指的是以當前時間為截止時間,往前取一定的時間,比如往前取 60s 的時間,在這 60s 之內運行最大的訪問數為 100,此時算法的執行邏輯為,先清除 60s 之前的所有請求記錄,再計算當前集合內請求數量是否大於設定的最大請求數 100,如果大於則執行限流拒絕策略,否則插入本次請求記錄並返回可以正常執行的標識給客戶端
  • 圖解+代碼|常見限流算法以及限流在單機分布式場景下的思考
    而且一般的限流都是為了限制在指定時間間隔內的訪問量,因此還有個算法叫固定窗口。滑動窗口限流滑動窗口限流解決固定窗口臨界值的問題,可以保證在任意時間窗口內都不會超過閾值。相對於固定窗口,滑動窗口除了需要引入計數器之外還需要記錄時間窗口內每個請求到達的時間點,因此對內存的佔用會比較多。
  • 架構師成長之路之限流漫談
    這種實現方式簡單粗暴,可以解決絕大部分分布式限流的問題。但是其存在的問題是:該計數方式並不是準確計數,由於時間窗口一旦過期,則之前積累的數據就失效,這樣可能導致比如本來希望限制「一分鐘內訪問不能超過 100 次」,但實際上做不到精準的限制,會存在誤判放過本應拒絕的流量。
  • 阿里架構師深度剖析:微服務接口鑑權限流背後的數據結構和算法
    最簡單的限流算法叫固定時間窗口限流算法。這種算法是如何工作的呢?首先我們需要選定一個時間起點,之後每當有接口請求到來,我們就將計數器加一。如果在當前時間窗口內,根據限流規則(比如每秒鐘最大允許 100 次訪問請求),出現累加訪問次數超過限流值的情況時,我們就拒絕後續的訪問請求。當進入下一個時間窗口之後,計數器就清零重新計數。
  • 「面經」慌了,面試居然被問到怎麼做高並發系統的限流?
    限流的算法常見的限流算法有:計數器、漏桶和令牌桶算法。計數器計數器是最簡單粗暴的算法。比如某個服務最多只能每秒鐘處理100個請求。我們可以設置一個1秒鐘的滑動窗口,窗口中有10個格子,每個格子100毫秒,每100毫秒移動一次,每次移動都需要記錄當前服務請求的次數。內存中需要保存10次的次數。可以用數據結構LinkedList來實現。格子每次移動的時候判斷一次,當前訪問次數和LinkedList中最後一個相差是否超過100,如果超過就需要限流了。
  • 2020-10-29:使用redis實現分布式限流組件...
    2020-10-29:使用redis實現分布式限流組件,要求高並發場景同一IP一分鐘內只能訪問100次,超過限制返回異常,寫出實現思路或偽代碼均可。福哥答案2020-10-29:簡單回答:固定窗口:string。key存ip,value存次數。滑動窗口:list。key存ip,value=list,存每次訪問的時間。
  • 開發高並發系統時常見的限流方式及算法
    限流的目的是通過對並發訪問/請求進行限速或者一個時間窗口內的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務(定向到錯誤頁或告知資源沒有了).排隊或等待(比如秒殺、評論、下單)、降級(返回兜底數據或默認數據,如商品詳情頁庫存默認有貨)。
  • 輕鬆兩步,在 SpringBoot 服務上實現接口限流
    在日常開發中,限流功能時常被使用,用於對某些接口進行限流熔斷,譬如限制單位時間內接口訪問次數;或者按照某種規則進行限流,如限制ip的單位時間訪問次數等。之前我們已經講過接口限流的工具類ratelimter可以實現令牌桶的限流,很明顯sentinel的功能更為全面和完善。
  • Golang 標準庫 限流器 time/rate 設計與實現
    限流器是後臺服務中十分重要的組件,在實際的業務場景中使用居多,其設計在微服務、網關、和一些後臺服務中會經常遇到。限流器的作用是用來限制其請求的速率,保護後臺響應服務,以免服務過載導致服務不可用現象出現。限流器的實現方法有很多種,例如 Token Bucket、滑動窗口法、Leaky Bucket等。
  • 技術分享 | 一文了解高並發限流算法
    限流:通過對並發訪問/請求進行限速,或者對一個時間窗口內的請求進行限速來保護系統穩定可用,一旦達到限制速率則可以拒絕服務、排隊或等待、降級等處理。本文聊聊限流的常用算法,並且通過案例測試驗證令牌桶算法。
  • 通過API網關實現微服務管控-限流,熔斷和降級
    其二是在微服務架構開發裡面,單個微服務應該儘量簡單,就是實現業務規則邏輯和暴露API接口服務能力。但是當前的微服務開發框架,在實現類似限流熔斷等的時候,基本都需要對已經開發完成的微服務進行相關的配置修改或增加註解等。其三對於ServiceMesh服務網格當前還沒有大足夠成熟和大面積應用的階段,但是API網關本身已經足夠成熟並得到大量項目實踐驗證。
  • 在springboot中使用Guava基於令牌桶實現限流
    限流說詳細了,名堂也多。這種算法那種算法,這種策略那種策略的。沒有絕對的銀彈。都要結合實際的場景來實現。最簡單的,使用google的Guava,幾行代碼。就可以優雅的對一個接口完成限流。Guava谷歌的一個工具庫,包含了大量的Java工具類,像hash算法,字符串處理,集合等等。。。https://github.com/google/guava<!
  • 資料庫之一文了解高並發限流算法-愛可生
    限流:通過對並發訪問/請求進行限速,或者對一個時間窗口內的請求進行限速來保護系統穩定可用,一旦達到限制速率則可以拒絕服務、排隊或等待、降級等處理。本文聊聊限流的常用算法,並且通過案例測試驗證令牌桶算法。
  • 慌了,面試居然被問到怎麼做高並發系統的限流?
    限流的算法常見的限流算法有:計數器、漏桶和令牌桶算法。計數器計數器是最簡單粗暴的算法。比如某個服務最多只能每秒鐘處理100個請求。我們可以設置一個1秒鐘的滑動窗口,窗口中有10個格子,每個格子100毫秒,每100毫秒移動一次,每次移動都需要記錄當前服務請求的次數。
  • 高並發系統如何做到限流,看這篇就對了
    限流的算法常見的限流算法有:計數器、漏桶和令牌桶算法。計數器計數器是最簡單粗暴的算法。比如某個服務最多只能每秒鐘處理100個請求。我們可以設置一個1秒鐘的滑動窗口,窗口中有10個格子,每個格子100毫秒,每100毫秒移動一次,每次移動都需要記錄當前服務請求的次數。內存中需要保存10次的次數。可以用數據結構LinkedList來實現。格子每次移動的時候判斷一次,當前訪問次數和LinkedList中最後一個相差是否超過100,如果超過就需要限流了。
  • 漏桶、令牌桶限流算法的Go語言實現
    以下文章來源於李文周 ,作者李文周限流限流又稱為流量控制(流控),通常是指限制到達系統的並發請求數。我們生活中也會經常遇到限流的場景,比如:某景區限制每日進入景區的遊客數量為 8 萬人;沙河地鐵站早高峰通過站外排隊逐一放行的方式限制同一時間進入車站的旅客數量等。