數據結構和算法專題---4、限流算法與應用

本章我們會對限流算法做個簡單介紹,包括常用的限流算法(計數器、漏桶算法、令牌桶案發、滑動窗口)的概述、實現方式、典型場景做個說明。

什么是限流算法

限流是對系統的一種保護措施。即限制流量請求的頻率(每秒處理多少個請求)。一般來說,當請求流量超過系統的瓶頸,則丟棄掉多余的請求流量,保證系統的可用性。即要么不放進來,放進來的就保證提供服務。

計數器

概述

計數器采用簡單的計數操作,到一段時間節點后自動清零

實現

package com.ls.cloud.sys.alg.limit;import com.ls.cloud.common.core.util.DateUtil;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;public class Counter {public static void main(String[] args) {//計數器,這里用信號量實現final Semaphore semaphore = new Semaphore(1);//定時器,到點清零ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {semaphore.release(1);}},3000,3000,TimeUnit.MILLISECONDS);//模擬無限請求從天而降降臨while (true) {try {//判斷計數器semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}//如果準許響應,打印一個okDate date = new Date();SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");System.out.println("執行------------"+ dateFormat.format(date));}}
}

結果分析

執行------------2023-12-05 02:17:33
執行------------2023-12-05 02:17:36
執行------------2023-12-05 02:17:39
執行------------2023-12-05 02:17:42
執行------------2023-12-05 02:17:45

優缺點

  • 優點:實現起來非常簡單。
  • 缺點:控制力度太過于簡略,假如1s內限制3次,那么如果3次在前100ms內已經用完,后面的900ms將只能處于阻塞狀態,白白浪費掉

典型場景

使用計數器限流的場景較少,因為它的處理邏輯不夠靈活。最常見的是登錄驗證碼倒計時,60秒接收一次,如果在限流場景使用計數器,可能導致前面100ms進入全部流程,系統可能依然會出現宕機的情況。

漏桶算法

概述

漏桶算法將請求緩存在桶中,服務流程勻速處理。超出桶容量的部分丟棄。漏桶算法主要用于保護內部的處理業務,保障其穩定有節奏的處理請求,但是無法根據流量的波動彈性調整響應能力。現實中,類似容納人數有限的服務大廳開啟了固定的服務窗口。
在這里插入圖片描述

實現

可以基于隊列進行實現。

package com.ls.cloud.sys.alg.limit;import java.util.concurrent.*;public class Barrel {public static void main(String[] args) {//桶,用阻塞隊列實現,容量為3final LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue(3);//定時器,相當于服務的窗口,2s處理一個ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {// 刪除隊首元素int v = que.poll();System.out.println("處理:"+v);}},2000,2000,TimeUnit.MILLISECONDS);//無數個請求,i 可以理解為請求的編號int i=0;while (true) {i++;try {System.out.println("put:"+i);//如果是put,會一直等待桶中有空閑位置,不會丟棄
//                que.put(i);//等待1s如果進不了桶,就溢出丟棄que.offer(i,1000,TimeUnit.MILLISECONDS);} catch (Exception e) {e.printStackTrace();}}}}

結果

put:1
put:2
put:3
put:4
put:5
處理:1
put:6
put:7
處理:2
put:8
put:9
處理:3
put:10
put:11
處理:5
put:12
put:13
  • put任務號按照順序入桶
  • 執行任務勻速的2s一個被處理
  • 因為桶的容量只有3,所以1-3完美執行,4被溢出丟棄,5正常執行

優缺點

  • 優點:有效的擋住了外部的請求,保護了內部的服務不會過載
  • 內部服務勻速執行,無法應對流量洪峰,無法做到彈性處理突發任務
  • 任務超時溢出時被丟棄。現實中可能需要緩存隊列輔助保持一段時間

典型場景

nginx中的限流是漏桶算法的典型應用,配置案例如下:

http {    #$binary_remote_addr 表示通過remote_addr這個標識來做key,也就是限制同一客戶端ip地址。       #zone=one:10m 表示生成一個大小為10M,名字為one的內存區域,用來存儲訪問的頻次信息。    #rate=1r/s 表示允許相同標識的客戶端每秒1次訪問    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;    server {        location /limited/ {        #zone=one 與上面limit_req_zone 里的name對應。        #burst=5 緩沖區,超過了訪問頻次限制的請求可以先放到這個緩沖區內,類似代碼中的隊列長度。#nodelay 如果設置,超過訪問頻次而且緩沖區也滿了的時候就會直接返回503,如果沒有設置,則所有請求會等待排隊,類似代碼中的put還是offer。                  limit_req zone=one burst=5 nodelay;    }} 

令牌桶

概述

令牌桶算法可以認為是漏桶算法的一種升級,它不但可以將流量做一步限制,還可以解決漏桶中無法彈性伸縮處理請求的問題。體現在現實中,類似服務大廳的門口設置門禁卡發放。發放是勻速的,請求較少時,令牌可以緩存起來,供流量爆發時一次性批量獲取使用。而內部服務窗口不設限。
在這里插入圖片描述

實現

package com.ls.cloud.sys.alg.limit;import java.util.concurrent.*;public class Token {public static void main(String[] args) throws InterruptedException {//令牌桶,信號量實現,容量為3final Semaphore semaphore = new Semaphore(3);//定時器,1s一個,勻速頒發令牌ScheduledExecutorService service = Executors.newScheduledThreadPool(1);service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {if (semaphore.availablePermits() < 3){semaphore.release();}
//                System.out.println("令牌數:"+semaphore.availablePermits());}},1000,1000,TimeUnit.MILLISECONDS);//等待,等候令牌桶儲存Thread.sleep(5);//模擬洪峰5個請求,前3個迅速響應,后兩個排隊for (int i = 0; i < 5; i++) {semaphore.acquire();System.out.println("洪峰:"+i);}//模擬日常請求,2s一個for (int i = 0; i < 3; i++) {Thread.sleep(1000);semaphore.acquire();System.out.println("日常:"+i);Thread.sleep(1000);}//再次洪峰for (int i = 0; i < 5; i++) {semaphore.acquire();System.out.println("洪峰:"+i);}//檢查令牌桶的數量for (int i = 0; i < 5; i++) {Thread.sleep(2000);System.out.println("令牌剩余:"+semaphore.availablePermits());}}
}

結果

洪峰:0
洪峰:1
洪峰:2
洪峰:3
洪峰:4
日常:0
日常:1
日常:2
洪峰:0
洪峰:1
洪峰:2
洪峰:3
洪峰:4
令牌剩余:2
令牌剩余:3
令牌剩余:3
令牌剩余:3
令牌剩余:3
  1. 洪峰0-2迅速被執行,說明桶中暫存了3個令牌,有效應對了洪峰
  2. 洪峰3,4被間隔性執行,得到了有效的限流
  3. 日常請求被勻速執行,間隔均勻
  4. 第二波洪峰來臨,和第一次一樣
  5. 請求過去后,令牌最終被均勻頒發,積累到3個后不再上升

典型場景

springcloud中gateway可以配置令牌桶實現限流控制,案例如下:

cloud: gateway: routes: ‐ id:    limit_route uri:    http://localhost:8080/test filters: ‐   name:    RequestRateLimiter args: #限流的key,ipKeyResolver為spring中托管的Bean,需要擴展KeyResolver接口 key‐resolver:    '#{@ipResolver}' #令牌桶每秒填充平均速率,相當于代碼中的發放頻率 redis‐rate‐limiter.replenishRate:    1 #令牌桶總容量,相當于代碼中,信號量的容量 redis‐rate‐limiter.burstCapacity:    3 

滑動窗口

概述

滑動窗口可以理解為細分之后的計數器,計數器粗暴的限定1分鐘內的訪問次數,而滑動窗口限流將1分鐘拆為多個段,不但要求整個1分鐘內請求數小于上限,而且要求每個片段請求數也要小于上限。相當于將原來的計數周期做了多個片段拆分,更為精細。

實現

在這里插入圖片描述

package com.ls.cloud.sys.alg.limit;import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;public class Window {//整個窗口的流量上限,超出會被限流final int totalMax = 5;//每片的流量上限,超出同樣會被拒絕,可以設置不同的值final int sliceMax = 5;//分多少片final int slice = 3;//窗口,分3段,每段1s,也就是總長度3sfinal LinkedList<Long> linkedList = new LinkedList<>();//計數器,每片一個key,可以使用HashMap,這里為了控制臺保持有序性和可讀性,采用TreeMapMap<Long,AtomicInteger> map = new TreeMap();//心跳,每1s跳動1次,滑動窗口向前滑動一步,實際業務中可能需要手動控制滑動窗口的時機。ScheduledExecutorService service = Executors.newScheduledThreadPool(1);//獲取key值,這里即是時間戳(秒)private Long getKey(){return System.currentTimeMillis()/1000;}public Window(){//初始化窗口,當前時間指向的是最末端,前兩片其實是過去的2sLong key = getKey();for (int i = 0; i < slice; i++) {linkedList.addFirst(key-i);map.put(key-i,new AtomicInteger(0));}//啟動心跳任務,窗口根據時間,自動向前滑動,每秒1步service.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {Long key = getKey();//隊尾添加最新的片linkedList.addLast(key);map.put(key,new AtomicInteger());//將最老的片移除map.remove(linkedList.getFirst());linkedList.removeFirst();System.out.println("step:"+key+":"+map);;}},1000,1000,TimeUnit.MILLISECONDS);}//檢查當前時間所在的片是否達到上限public boolean checkCurrentSlice(){long key = getKey();AtomicInteger integer = map.get(key);if (integer != null){return integer.get() < totalMax;}//默認允許訪問return true;}//檢查整個窗口所有片的計數之和是否達到上限public boolean checkAllCount(){return map.values().stream().mapToInt(value -> value.get()).sum()  < sliceMax;}//請求來臨....public void req(){Long key = getKey();//如果時間窗口未到達當前時間片,稍微等待一下//其實是一個保護措施,放置心跳對滑動窗口的推動滯后于當前請求while (linkedList.getLast()<key){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}//開始檢查,如果未達到上限,返回ok,計數器增加1//如果任意一項達到上限,拒絕請求,達到限流的目的//這里是直接拒絕。現實中可能會設置緩沖池,將請求放入緩沖隊列暫存if (checkCurrentSlice() && checkAllCount()){map.get(key).incrementAndGet();System.out.println(key+"=通過:"+map);}else {System.out.println(key+"=拒絕:"+map);}}public static void main(String[] args) throws InterruptedException {Window window = new Window();//模擬10個離散的請求,相對之間有200ms間隔。會造成總數達到上限而被限流for (int i = 0; i < 10; i++) {Thread.sleep(200);window.req();}//等待一下窗口滑動,讓各個片的計數器都置零Thread.sleep(3000);//模擬突發請求,單個片的計數器達到上限而被限流System.out.println("---------------------------");for (int i = 0; i < 10; i++) {window.req();}}
}

結果

1701766769=通過:{1701766767=0, 1701766768=0, 1701766769=1}
1701766769=通過:{1701766767=0, 1701766768=0, 1701766769=2}
1701766769=通過:{1701766767=0, 1701766768=0, 1701766769=3}
1701766769=通過:{1701766767=0, 1701766768=0, 1701766769=4}
step:1701766770:{1701766768=0, 1701766769=4, 1701766770=0}
1701766770=通過:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒絕:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒絕:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒絕:{1701766768=0, 1701766769=4, 1701766770=1}
1701766770=拒絕:{1701766768=0, 1701766769=4, 1701766770=1}
step:1701766771:{1701766769=4, 1701766770=1, 1701766771=0}
1701766771=拒絕:{1701766769=4, 1701766770=1, 1701766771=0}
step:1701766772:{1701766770=1, 1701766771=0, 1701766772=0}
step:1701766773:{1701766771=0, 1701766772=0, 1701766773=0}
step:1701766774:{1701766772=0, 1701766773=0, 1701766774=0}
---------------------------
1701766774=通過:{1701766772=0, 1701766773=0, 1701766774=1}
1701766774=通過:{1701766772=0, 1701766773=0, 1701766774=2}
1701766774=通過:{1701766772=0, 1701766773=0, 1701766774=3}
1701766774=通過:{1701766772=0, 1701766773=0, 1701766774=4}
1701766774=通過:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒絕:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒絕:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒絕:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒絕:{1701766772=0, 1701766773=0, 1701766774=5}
1701766774=拒絕:{1701766772=0, 1701766773=0, 1701766774=5}
step:1701766775:{1701766773=0, 1701766774=5, 1701766775=0}
step:1701766776:{1701766774=5, 1701766775=0, 1701766776=0}
step:1701766777:{1701766775=0, 1701766776=0, 1701766777=0}
step:1701766778:{1701766776=0, 1701766777=0, 1701766778=0}

典型場景

滑動窗口算法,在tcp協議發包過程中被使用。在web現實場景中,可以將流量控制做更細化處理,解決計數器模型控制力度太粗暴的問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/210373.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/210373.shtml
英文地址,請注明出處:http://en.pswp.cn/news/210373.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

11_企業架構web服務器文件及時同步

企業架構web服務器的文件及時同步 學習目標和內容 1、能夠理解為何要服務器間文件同步 2、能夠簡單描述實現文件同步的幾種方式 3、能夠實現服務器文件實時同步的案例 一、同步文件介紹 1、服務器文件同步的必要性 根據業務發展需求&#xff0c;業務網站架構已經發展到以上模式…

Linux文件結構與文件權限

基于centos了解Linux文件結構 了解一下文件類型 Linux采用的一切皆文件的思想&#xff0c;將硬件設備、軟件等所有數據信息都以文件的形式呈現在用戶面前&#xff0c;這就使得我們對計算機的管理更加方便。所以本篇文章會對Linux操作系統的文件結構和文件權限進行講解。 首先…

單元測試Nunit的幾種斷言

Nunit提供了一些輔助函數用于確定好某個被測試函數是否正常工作。通常把這些函數稱為斷言 斷言是單元測試最基本的組成部分。因此&#xff0c;NUnit程序庫以Assert類的靜態方法的形式提供了不同形式的多種斷言 1. Assert.AreEqual&#xff1a;比較兩個值是否相等。用于比較數…

Qt生成動態鏈接庫并使用動態鏈接庫

項目結構 整個工程由一個主程序構成和一個模塊構成(dll)。整個工程的結構目錄如下 Define.priMyProject.proMyProject.pro.user ---bin ---MainProgrammain.cppMainProgram.proMainProgram.pro.userwidget.cppwidget.hwidget.ui ---MathDllMathDll.proMathDll.pro.userMyMath.…

Axios 攔截器實戰教程:簡單易懂

Axios 提供了一種稱為 “攔截器&#xff08;interceptors&#xff09;” 的功能&#xff0c;使我們能夠在請求或響應被發送或處理之前對它們進行全局處理。攔截器為我們提供了一種簡潔而強大的方式來轉換請求和響應、進行錯誤處理、添加認證信息等操作。在本文中&#xff0c;我…

Matlab 點云收縮L1中值(Weiszfeld算法)

文章目錄 一、簡介二、實現代碼三、實現效果參考資料一、簡介 對于之前的加權均值收縮方式,它存在一個很大的缺點,即容易受到噪聲的影響,因此這里我們采用另一種統計學方案:L1中值。其形式如下所示: 其中 x i x_i

MongoDB的條件操作符

本文主要介紹MongoDB的條件操作符。 目錄 MongoDB條件操作符1.比較操作符2.邏輯操作符3.元素操作符4.數組操作符5.文本搜索操作符 MongoDB條件操作符 MongoDB的條件操作符主要分為比較操作符、邏輯操作符、元素操作符、數組操作符、文本搜索操作符等幾種類型。 以下是這些操作…

拷貝實體類

文章目錄 方式一 &#xff1a; 方式二&#xff1a;&#xff08;不常用&#xff09; 方式一 &#xff1a; 將左邊的實體拷貝到右邊的實體中 import org.springframework.beans.BeanUtils; BeanUtils.copyProperties(memberAddress, resp);將右邊的實體拷貝到左邊的實體中 imp…

對String類的操作 (超細節+演示)

[本節目標] 1.認識String類 2.了解String類的基本用法 3.熟練掌握String類的常見操作 4.認識字符串常量池 5.認識StringBuffer和StringBuilder 1.String類的重要性 在C語言中已經涉及到字符串了&#xff0c;但是在C語言中要表示字符串只能使用字符數組或者字符指針&…

高速風筒安規方案中的安規測試及安規電路特性介紹--【其利天下技術】

作為家用電子產品&#xff0c;高速吹風筒做安規測試&#xff0c;過安規要求是必須保證的&#xff0c;一般電路要過安規測試&#xff0c;那么安規測試的目的是什么呢&#xff1f; 安規測試字面意思是安全規范測試&#xff0c;主要強調對使用人員的安全保護&#xff0c;也就是我…

P7 Linux C三種終止進程的方法

前言 &#x1f3ac; 個人主頁&#xff1a;ChenPi &#x1f43b;推薦專欄1: 《C_ChenPi的博客-CSDN博客》??? &#x1f525; 推薦專欄2: 《Linux C應用編程&#xff08;概念類&#xff09;_ChenPi的博客-CSDN博客》??? &#x1f6f8;推薦專欄3: ??????《 鏈表_Chen…

什么是MyBatis、什么是MyBatis-Plus、簡單詳細上手案例

什么是MyBatis MyBatis是一個開源的Java持久層框架&#xff0c;用于簡化與關系型數據庫的交互。它通過將SQL語句與Java代碼進行分離&#xff0c;提供了一種優雅的方式來處理數據庫操作。 MyBatis的核心思想是將SQL語句與Java方法進行映射&#xff0c;使得開發人員可以通過配置…

【工具類】Word 轉 PDF

商業版權問題 使用破解版-aspose-words-19.5jdk.jar https://blog.csdn.net/aley/article/details/127914145 Document wordDoc new Document(wordFileInputStream); wordDoc.save(pdfFile, new PdfSaveOptions());中文亂碼問題 在linux中使用會造成中文亂碼問題 解決方案…

C語言數據結構-基于單鏈表實現通訊錄

文章目錄 1 基礎要求2 通訊錄功能2.1 引入單鏈表的文件2.2 定義聯系人數據結構2.3 打開通訊錄2.4 保存數據后銷毀通訊錄2.5 添加聯系人2.6 刪除聯系人2.7 修改聯系人2.8 查找聯系人2.9 查看通訊錄 3 通訊錄代碼展示3.1 SeqList_copy.h3.2 SeqList_copy.c3.3 Contact.h3.4 Conta…

模塊化機房在大數據時代的角色:高效、可擴展的數據存儲和處理平臺

隨著大數據時代的到來&#xff0c;數據已經成為企業競爭的核心資源。然而&#xff0c;傳統的數據中心已經無法滿足現代業務的需求&#xff0c;尤其是在數據存儲和處理方面。模塊化機房作為一種新型的數據中心建設模式&#xff0c;具有高效、可擴展等優勢&#xff0c;逐漸成為大…

PyCharm編輯器結合Black插件,輕松實現Python代碼格式化

大家好&#xff0c;使用Black對Python代碼進行格式化&#xff0c;可使代碼看起來更美觀。但是&#xff0c;隨著項目規模不斷變大&#xff0c;對每個文件運行Black變得很繁瑣。本文就來介紹在PyCharm中實現這一目標的方法。 1.安裝Black 首先&#xff0c;在虛擬環境中安裝Blac…

二叉樹的鋸齒形層序遍歷[中等]

優質博文&#xff1a;IT-BLOG-CN 一、題目 給你二叉樹的根節點 root &#xff0c;返回其節點值的 鋸齒形層序遍歷 。&#xff08;即先從左往右&#xff0c;再從右往左進行下一層遍歷&#xff0c;以此類推&#xff0c;層與層之間交替進行&#xff09;。 示例 1&#xff1a; 輸…

認識線程和創建線程

目錄 1.認識多線程 1.1線程的概念 1.2進程和線程 1.2.1進程和線程用圖描述關系 1.2.2進程和線程的區別 1.3Java 的線程和操作系統線程的關系 2.創建線程 2.1繼承 Thread 類 2.2實現 Runnable 接口 2.3匿名內部類創建 Thread 子類對象 2.4匿名內部類創建 Runnable 子類對…

使用貝葉斯網絡檢測因果關系,提升模型效果更科學(附Python代碼)

雖然機器學習技術可以實現良好的性能&#xff0c;但提取與目標變量的因果關系并不直觀。換句話說&#xff0c;就是&#xff1a;哪些變量對目標變量有直接的因果影響&#xff1f; 機器學習的一個分支是貝葉斯概率圖模型(Bayesian probabilistic graphical models)&#xff0c;也…

【Com通信】Com模塊詳細介紹

目錄 前言 1. Com模塊功能介紹 2.關鍵概念理解 3.功能詳細設計 3.1 Introduction 3.2 General Functionality 3.2.1 AUTOSAR COM basis 3.2.2 Signal Values 3.2.3 Endianness Conversion and Sign Extension 3.2.4 Filtering 3.2.5 Signal Gateway 3.3 Normal Ope…