Redis-限流方案

在實際業務中,可能會遇到瞬時流量劇增的情況,大量的請求可能會導致服務器過載和宕機。為了保護系統自身和上下游服務,需要采用限流的方式,拒絕部分請求。
限流就是對請求的頻率進行控制,迅速拒絕超過請求閾值的請求。
比如:Redis的QPS為10w,但是此時20w個請求同一時刻請求到Redis,造成Redis性能降低。
[圖片]

1. 什么是限流

限流就是在高并發場景下,通過限制系統處理請求的速率,迅速拒絕超過設定上限的請求(最大的QPS),從而保證系統正常運行。
在限流技術中,主要是有兩個注意點:當前系統請求的閾值和拒絕策略。
請求的閾值:閾值就是單位時間內允許請求的最大請求數。例如,將QPS(Queries Per Second)設置為1000,表明1s內最多接受1000個請求。通過設置適當的閾值,可以有效控制系統負載,避免系統請求過多而崩潰。(閾值是上線前通過壓測或者評估得出的指標)

拒絕策略: 處理超過閾值的請求的方法,常見的拒絕策略包括直接拒絕和等待。

  • 直接拒絕策略:直接拒絕超過閾值的請求,直接返回給客戶端。(提示:當前搶購人數太多)
  • 排隊等待策略:將請求放入隊列中,按照一定規則處理,避免瞬間拒絕大量請求。

2. 常見的限流算法

常見的限流算法分為:固定窗口(計數器限流)、滑動窗口、漏桶、令牌桶 四個方案

2.1. 固定窗口限流算法

在一段時間間隔內,處理請求的最大數量。

固定窗口其實就是時間窗口,原理是將時間劃分成固定大小的窗口,并且在每個窗口內限制請求的數量和速率,如果某個窗口內的請求超過了閾值,就直接執行拒絕策略。

即:固定窗口限流算法是規定了單位時間處理的請求數量。

算法步驟:

  1. 將時間劃分為固定大小的窗口,例如每秒一個。
  2. 在每一個時間窗口內,記錄請求的數量。
  3. 當新的請求到達時,當前窗口的請求計數器的值加一。
  4. 如果窗口內請求計數器超過閾值,那么執行拒絕策略。
  5. 當窗口結束后,重置計數器。
    在這里插入圖片描述

例如,每個時間窗口為1s,限流的閾值為3,窗口內超過閾值的請求都會觸發拒絕策略。
優點:實現簡單,易于理解。
缺點:

  • 請求不夠均勻:在固定窗口算法中,請求在窗口內的分布可能會不均勻。假如限制某個接口每分鐘只能訪問30次,那么前30秒就會有30個請求到達的話,那么后30秒就無法處理請求,導致請求不均勻。
  • 無法保證限流速率,因而無法因對突增的流量。比如一個接口一分鐘只能訪問1000次,但是接口的QPS為500,前55秒這個接口沒有收到請求,但是后一秒突然接收到1000個請求。然后,在當前場景下,這1000個請求在1s內是無法被處理的,系統可能就直接被大量的請求給擊垮了。
  • 存在明顯的臨界問題(請求突刺問題):前一個窗口和后一個窗口之間的可能會存在大量的請求,無法應對無法流量。
    比如:一個窗口內請求的閾值為3,以下存在臨界問題:
    在這里插入圖片描述
public class FixedWindowLimiter {private static final String FORMAT_TIME = "yyyy-MM-dd HH:mm:ss";private final long windowSize; // 窗口大小,默認是毫秒private final int maxRequests; // 最大請求數private int currentRequests; //當前窗口內的請求數private LocalDateTime lastRestTime; //上次窗口重置時間,即窗口開始時間private final Lock resetMutex; //重置鎖public FixedWindowLimiter(Duration windowSize, int maxRequests) {this.windowSize = windowSize.toMillis();this.maxRequests = maxRequests;this.currentRequests = 0;this.lastRestTime = LocalDateTime.now();this.resetMutex = new ReentrantLock();}public boolean allow() {resetMutex.lock();try {LocalDateTime now = LocalDateTime.now();if (Duration.between(lastRestTime, now).toMillis() >= windowSize) {this.currentRequests = 0;this.lastRestTime = now;}if (currentRequests >= maxRequests) {return false;}currentRequests++;return true;}finally {resetMutex.unlock();}}
}class FixedWindowLimiterApp {private static final String FORMAT_TIME = "yyyy-MM-dd HH:mm:ss";public static void main(String[] args) {System.out.println(System.currentTimeMillis() / 1000);FixedWindowLimiter limiter = new FixedWindowLimiter(Duration.ofSeconds(1), 3);DateTimeFormatter formatter = DateTimeFormatter.ofPattern(FORMAT_TIME);ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);for (int i = 0; i < 20; i++) {Runnable task = () -> {String now = LocalDateTime.now().format(formatter);if (limiter.allow()) {System.out.println(now + "請求通過");} else {System.out.println(now + "請求被限流");}};pool.schedule(task, i * 100, TimeUnit.MILLISECONDS);}pool.shutdown();try {if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {pool.shutdownNow();}} catch (InterruptedException e) {pool.shutdownNow();}}
}

2.2. 滑動窗口限流算法

滑動窗口限流算法是固定窗口限流算法的升級版本,限流的粒度更小。
滑動窗口和固定窗口相比:滑動窗口是將時間按照一定的比例分片。
假如接口限流每分鐘處理60個請求,可以將1分鐘分成60個小窗口,然后每隔一秒移動一次,每個窗口一秒只能處理不大于60(請求數) / 60(窗口數),如果當前窗口的請求計數綜合超過了限制的數量的話,就觸發拒絕策略。
[圖片]

這樣的話,可以處理[0.5秒 ~ 1.5秒]內的臨界問題,紅色部分的請求將會觸發拒絕策略。
優點:

  • 相比于固定窗口算法,滑動窗口計數器算法可以應對突發流量,解決一些臨界問題。
  • 限流粒度更小,可以提供更精確的限流控制。
    缺點:
  • 和固定窗口一樣,存在請求不夠均勻的情況。

2.3. 漏桶限流算法

漏桶限流的核心思想就是將請求存儲在一個漏桶中,無論我們以任意速率存入請求,漏桶始終以固定的速率流出。所以漏桶算法可以將突發流量均勻地處理,確保系統在穩定的負載下運行。
[圖片]

優點:

  1. 實現簡單,易于理解。
  2. 可以控制限流速率,避免網絡擁塞和系統過載。
    缺點:
  3. 在突發情況請求過多時,會丟棄過多的請求。

2.4. 令牌桶限流算法

令牌桶算法也比較簡單,和漏桶算法一樣,主角還是桶。不過現在桶里面裝的是令牌,請求在被處理之前需要拿到一個令牌,請求處理完畢之后直接丟棄令牌即可。我們可以根據限流大小,按照一定的速率往桶里添加令牌,如果桶裝滿了,就不能繼續往桶里添加令牌了。


優點:

  1. 可以應對突發流量:由于令牌可以在桶中堆積,當流量突然增大時,如果桶中有足夠的令牌,系統可以快速響應這種突發流量,避免請求被立即拒絕。這使得令牌桶算法特別適合處理具有突發性流量的應用場景。
  2. 靈活性強:通過適當調整流派的生成速率和桶的容量,可以靈活地控制流量。
    算法設計:
public class TokenBucketLimiter {private final int rate; // 令牌產生的速度,即每秒生成多少個令牌private final int capacity; // 令牌桶容量,最多可存儲令牌數private int currentTokenNum; // 當前令牌數private LocalDateTime lastTime; // 上次請求時間private final Lock lock; // 請求鎖public TokenBucketLimiter(int rate, int capacity) {this.rate = rate;this.capacity = capacity;this.currentTokenNum = 0;this.lastTime = LocalDateTime.now();this.lock = new ReentrantLock();}public int getCurrentTokenNum() {return currentTokenNum;}public int getRate() {return rate;}public int getCapacity() {return capacity;}public boolean allow() {lock.lock();try {// 計算當前時間和上一次更新時間的時間間隔long timeDiff = Duration.between(lastTime, LocalDateTime.now()).toMillis();// 計算時間間隔內生成的令牌數量int tokenCount = (int) (timeDiff / 1000.0 * rate);if (tokenCount > 0) {currentTokenNum += tokenCount;lastTime = LocalDateTime.now();}// 令牌的數量不能超過桶的大小if (currentTokenNum > capacity) {currentTokenNum = capacity;}// 獲取令牌if (currentTokenNum > 0) {currentTokenNum--;return true;}return false;} finally {lock.unlock();}}}class TokenBucketLimiterApp {public static void mockRequest(int n, Duration d, TokenBucketLimiter limiter) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);for (int i = 0; i < n; i++) {int requestId = i + 1;executor.schedule(() -> {if (limiter.allow()) {System.out.printf("第%d個請求通過\n", requestId);} else {System.out.printf("第%d個請求被限流\n", requestId);}}, i * d.toMillis(), TimeUnit.MILLISECONDS);}executor.shutdown();try {if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();}}public static void main(String[] args) {System.out.println("=================令牌桶算法=================");// 創建一個令牌桶,令牌的生成速度為每秒4個,桶容量為5個請求TokenBucketLimiter limiter = new TokenBucketLimiter(4, 5);// 發送10個請求,每50ms發送一個mockRequest(10, Duration.ofMillis(50), limiter);System.out.println("------------------------------------------");}
}

3. 針對什么進行限流?

實際項目中,針對的先流對象是什么?也就是說針對什么來進行限流?常見的限流對象如下:

  1. 針對IP限流:比較常見,比如通過X-forwarded-For或TCP options字段中真實源IP信息。
  2. 業務ID:挑選唯一的業務ID實現限流。比如,根據用戶的ID進行限流。(寫leetcode的時候多次提交,提醒提交太快)。
  3. 個性化:根據用戶的屬性或者行為。進行不同的限流策略。例如,VIP用戶不限流,普通用戶限流。

4. 單機限流

單機限流是指在單臺服務器上,通過限制其在單位時間內處理的請求數量來防止過載。
單機限流可以使用Google Guava自帶的限流工具類 RateLimiter。RateLimiter是基于令牌桶算法實現的,可以應對突發流量。

5. 分布式限流

單機限流只能保證一個節點限流,但是無法保證分布式限流。
分布式限流和分布式鎖的思想是一樣的,就是將原本設計實現在本地服務的限流操作抽離出來,做成一個中心化的限流器,實現全局共享。


分布式限流常見方案:可以借助Sentinel或者使用Redis來自己實現對應的限流邏輯。
基于Redis的實現步驟如下:(Redis + Lua)

  1. 選取一個Redis中心化組件
  2. 設定限流規則,比如每秒允許的最大請求數(QPS),并且將該值存儲在Redis中。
  3. 每當有請求到達時,服務器首先會向Redis請求令牌。
  4. 如果獲得令牌,請求可以繼續處理;如果未獲得令牌,則表示請求被限流,執行拒絕策略。
local key = KEYS[1] -- hashmap的key
local rate = tonumber(ARGV[1]) -- 令牌生成速率
local capacity = tonumber(ARGV[2]) -- 桶的容量
local now = tonumber(ARGV[3]) -- 當前時間(毫秒)
local requested = tonumber(ARGV[4]) -- 請求的令牌數-- 獲取上次更新時間和當前令牌數
local last_time = redis.call('HGET', key, 'last_time')
local current_tokens = redis.call('HGET', key, 'tokens')-- 如果是首次訪問,那么就將last_time初始化為當前時間,當前令牌數為桶容量
if last_time == false thenlast_time = nowcurrent_tokens = capacity
else-- 不是第一次訪問,那么從redis獲取上次訪問時間和當前令牌數last_time = tonumber(last_time)current_tokens = tonumber(current_tokens)
end-- 計算時間差
local time_diff = now - last_time
-- 計算新的令牌數
local new_tokens = math.min(capacity, current_tokens + (time_diff * rate / 1000))local allowed = new_tokens >= requestedif allowed thennew_tokens = new_tokens - requested
endredis.call('HSET', key, 'last_time', now)
redis.call('HSET', key, 'tokens', new_tokens)return allowed

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

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

相關文章

無感方波開環強拖總結

一、強拖階段的核心原理與設計要點 開環換相邏輯 固定頻率斜坡&#xff1a;以預設斜率逐步提升換相頻率&#xff08;如0.5-5Hz/ms&#xff09;&#xff0c;強制電機跟隨磁場旋轉。電壓-頻率協調控制&#xff1a;初始階段施加高電壓&#xff08;80%-100%額定&#xff09;克服靜摩…

Java虛擬機之垃圾收集(一)

目錄 一、如何判定對象“生死”&#xff1f; 1. 引用計數算法&#xff08;理論參考&#xff09; 2. 可達性分析算法&#xff08;JVM 實際使用&#xff09; 3. 對象的“緩刑”機制 二、引用類型與回收策略 三、何時觸發垃圾回收&#xff1f; 1. 分代回收策略 2. 手動觸發…

代碼隨想錄算法訓練營第22天 | 組合 組合總和 電話號碼的字母組合

77. 組合 77. 組合 - 力扣&#xff08;LeetCode&#xff09; class Solution {List<Integer> path new ArrayList<>();List<List<Integer>> result new ArrayList<>();public void backTracking(int n,int k,int startIndex){if(path.size() …

#UVM# 關于field automation機制中的標志位及if的使用

通過前面文章的復習,我們知道了 uvm_field 機制帶來的好處,確實方便了我們很多代碼的coding 時間,但是會不會有一種情況呢? 比如,我們不想將實例中的某一些成員進行打包、復制、比較操作,怎么辦呢? 如果只執行 比較但不進行打包操作呢?是不是很復雜呢 ? 一 標志位…

RK3588 安裝ffmpeg6.1.2

在安裝 ffmpeg 在 RK3588 開發板上時,你需要確保你的開發環境(例如 Ubuntu、Debian 或其他 Linux 發行版)已經設置好了交叉編譯工具鏈,以便能夠針對 RK3588 架構編譯軟件。以下是一些步驟和指導,幫助你安裝 FFmpeg: 1. 安裝依賴項 首先,確保你的系統上安裝了所有必要的…

leetcode day25 28 KMP算法

28找出字符串中第一個匹配項的下標 給你兩個字符串 haystack 和 needle &#xff0c;請你在 haystack 字符串中找出 needle 字符串的第一個匹配項的下標&#xff08;下標從 0 開始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;則返回 -1 。 示例 1&#xff…

編程語言介紹:Rust

什么是Rust Rust是由Mozilla研究院開發的一種系統級編程語言&#xff0c;旨在提供更好的內存安全保證&#xff0c;同時保持高性能&#xff0c;自2010年首次發布以來&#xff0c;Rust以其安全性、并發性和實用性迅速獲得了廣泛的關注。Rust最獨特的特性之一是其所有權模型&#…

Java Spring MVC (2)

常見的Request Controller 和 Response Controller 的區別 用餐廳點餐來理解 想象你去一家餐廳吃飯&#xff1a; Request Controller&#xff08;接單員&#xff09;&#xff1a;負責處理你的點餐請求&#xff0c;記錄你的口味、桌號等信息。Response Controller&#xff08…

Oracle 字符類型對比

本文以 Oracle12c 為例 1.主要區別對比 類型存儲方式最大長度字符集支持適用場景備注?CHAR(M)固定長度空格填充2000 字節&#xff0c;M 代表字節長度默認字符集固定長度編碼實際存儲長度固定為定義長度&#xff08;如 CHAR(10) 始終占 10 字節&#xff09;?VARCHAR2(M)可變長…

Linux系列:如何用heaptrack跟蹤.NET程序的非托管內存泄露

一&#xff1a;背景 1. 講故事 前面跟大家分享過一篇 C# 調用 C代碼引發非托管內存泄露 的文章&#xff0c;這是一個故意引發的正向泄露&#xff0c;這一篇我們從逆向的角度去洞察引發泄露的禍根代碼&#xff0c;這東西如果在 windows 上還是很好處理的&#xff0c;很多人知道開…

vite.config.js 是Vite 項目的配置文件,分析具體用法

vite.config.js 是 Vite 項目的配置文件&#xff0c;用于定義項目的構建、開發服務器、插件等配置選項。以下是示例代碼中各部分的作用分析&#xff1a; 1. 導入模塊 import { fileURLToPath, URL } from node:url import { defineConfig } from vite import vue from vitejs…

行為模式---責任鏈模式

概念 責任鏈模式是一種行為設置模式&#xff0c;它的核心思想就是將請求的發送者和接收者進行解耦&#xff0c;每個接收者都可以處理請求。 在責任鏈模式中將每個接收者連成一個鏈條&#xff0c;當有請求發送上來的時候會經過每一個接收者。直到消息被處理。 適用場景 1、當…

pytest結合allure

Allure 一、文檔二、指令三、裝飾器3.1 allure.step裝飾器3.2 allure.description裝飾器3.3 allure.title裝飾器3.4 allure.link、allure.issue 和 allure.testcase裝飾器3.5 allure.epic、allure.feature 和 allure.story裝飾器3.6 allure.severity裝飾器 一、文檔 allure文檔…

前端知識點---http.createHttp()的理解(arkts)

通俗易懂的例子&#xff1a;點外賣 &#x1f354;&#x1f964; 想象一下&#xff0c;你在家里點外賣&#xff0c;HTTP 請求就像是你和餐廳之間的溝通方式。 1?? 沒有 http.createHttp()&#xff1a;每次點餐都重新撥電話 &#x1f4de; 如果你每次點餐都重新撥打餐廳的電話…

大模型開發(五):P-Tuning項目——新零售決策評價系統(下)

P-Tuning項目——新零售決策評價系統&#xff08;下&#xff09; 0 前言1 P-Tuning原理2 數據處理 0 前言 上篇文章我們介紹了使用PET方式微調BERT模型&#xff0c;PET屬于提示詞微調的一種&#xff0c;另一種比較常見的提示詞微調是P-Tuning&#xff0c;我們今天在相同的項目…

分布式中間件:Redis介紹

目錄 Redis 概述 Redis 的特點 高性能 豐富的數據結構 持久化 分布式特性 簡單易用 Redis 的數據結構 字符串&#xff08;String&#xff09; 哈希&#xff08;Hash&#xff09; 列表&#xff08;List&#xff09; 集合&#xff08;Set&#xff09; 有序集合&…

在昇騰GPU上部署DeepSeek大模型與OpenWebUI:從零到生產的完整指南

引言 隨著國產AI芯片的快速發展&#xff0c;昇騰&#xff08;Ascend&#xff09;系列GPU憑借其高性能和兼容性&#xff0c;逐漸成為大模型部署的重要選擇。本文將以昇騰300i為例&#xff0c;手把手教你如何部署DeepSeek大模型&#xff0c;并搭配OpenWebUI構建交互式界面。無論…

系統思考—組織診斷

“未經過診斷的行動是盲目的。” — 托馬斯愛迪生 最近和一家教育培訓機構溝通時&#xff0c;發現他們面臨一個有意思的問題&#xff1a;每年招生都挺不錯&#xff0c;但教師的整體績效一直提升緩慢&#xff0c;導致師生之間存在長期的不匹配。管理層試了很多辦法&#xff0c;…

AI大模型學習(五): LangChain(四)

Langchian讀取數據庫 案例&#xff1a;在數據庫中表格數據上的問題系統的基本方法,將涵蓋使用鏈和代理的視線,通過查詢數據庫中的數據并得到自然語言的答案,兩者之間的主要區別在于,我們代理可以根據多次循環查詢數據庫以回答問題 實現思路: 1.將問題轉換成DSL查詢,模型將用…

人工智能與深度學習的應用案例:從技術原理到實踐創新

第一章 引言 人工智能(AI)作為21世紀最具變革性的技術之一,正通過深度學習(Deep Learning)等核心技術推動各行業的智能化進程。從計算機視覺到自然語言處理,從醫療診斷到工業制造,深度學習通過模擬人腦神經網絡的層次化學習機制,實現了對復雜數據的高效分析與決策。本…