基于 Redisson 實現分布式系統下的接口限流

在高并發場景下,接口限流是保障系統穩定性的重要手段。常見的限流算法有漏桶算法、令牌桶算法等,而單機模式的限流方案在分布式集群環境下往往失效。本文將介紹如何利用 Redisson 結合 Redis 實現分布式環境下的接口限流,確保集群中所有節點的流量控制保持一致。

分布式限流的核心挑戰

在單機系統中,我們可以通過本地緩存(如 Guava 的 RateLimiter)實現限流,但在分布式集群環境下,這種方案會遇到兩個核心問題:

  • 集群節點間的限流狀態不共享,導致整體流量超過預期閾值
  • 無法保證同一用戶 / IP 的請求在不同節點上被統一限制

因此,分布式限流需要一個「中心化的狀態存儲」來記錄流量數據,而 Redis 憑借其高并發特性和分布式特性,成為了理想的選擇。

基于 Redisson 的分布式限流設計思路

核心原理是通過 Redis 記錄每個用戶對接口的訪問頻率,利用分布式鎖實現并發控制,具體設計如下:

  1. 唯一標識用戶與接口
    為了避免限制 A 用戶時影響 B 用戶,需要為每個用戶 + 接口組合生成唯一的「限流鍵」。

    一般為用戶:使用 token + 接口路徑 +用戶的id

  2. 基于 Redis 的訪問頻率記錄
    每次請求到來時,通過 Redisson 操作 Redis 記錄訪問時間,并檢查單位時間內的訪問次數是否超過閾值。

  3. AOP 無侵入式攔截
    通過自定義注解 + Spring AOP 攔截需要限流的接口,在請求到達時執行限流邏輯,不侵入業務代碼。

  4. 自動過期的限流狀態
    為 Redis 中的限流鍵設置過期時間,避免長期存儲無效數據,同時確保超過限制時間后自動允許用戶再次訪問。

實現步驟

引入依賴

pom.xml 中添加 Redisson 和 AOP 依賴

<!-- Redisson 分布式工具 -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.3</version>
</dependency><!-- Spring AOP -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定義限流注解

創建 @NoRepeatSubmit 注解,用于標記需要限流的接口,并支持自定義限流參數:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {/*** 設置請求鎖定時間(秒)*/int lockTime() default 5;
}

實現限流切面

通過 AOP 攔截 @NoRepeatSubmit 注解的方法,使用 Redisson 操作 Redis 實現限流邏輯:

Aspect
@Component
public class RepeatSubmitAspect {private static final Logger log = LoggerFactory.getLogger(RepeatSubmitAspect.class);@Resourceprivate RedissonClient redissonClient;@Pointcut("@annotation(com.example.demo.config.NoRepeatSubmit)")public void pointCut() {}@Around("pointCut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();NoRepeatSubmit annotation = method.getAnnotation(NoRepeatSubmit.class);ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attributes == null) {throw new IllegalArgumentException("無法獲取請求信息");}HttpServletRequest request = attributes.getRequest();String token = request.getHeader("token");String path = request.getServletPath();if (token == null || token.isEmpty()) {throw new IllegalArgumentException("缺少token請求頭");}// 使用token+path作為鎖的keyString key = "repeat_submit:" + token + ":" + path;RLock lock = redissonClient.getLock(key);// 嘗試獲取鎖,等待0秒,自動釋放時間由注解指定boolean isSuccess = false;isSuccess = lock.tryLock(0, annotation.lockTime(), TimeUnit.SECONDS);if (isSuccess) {log.info("獲取鎖成功: {}", key);// 執行目標方法return pjp.proceed();} else {log.info("重復請求,獲取鎖失敗: {}", key);return Result.fail("請勿重復提交請求");}}
}

測試

@RestController
@RequestMapping("/api/order")
public class OrderController {@PostMapping("/create")@NoRepeatSubmit(lockTime = 10) // 設置5秒內不允許重復提交public Result createOrder() {// 模擬訂單創建過程try {Thread.sleep(2000); // 模擬業務處理耗時2秒} catch (InterruptedException e) {e.printStackTrace();}return Result.success("訂單創建成功");}
}

限制之后
在這里插入圖片描述

這里還可以增加更多的邏輯,比如限制次數等等。

核心邏輯說明

  1. 用戶唯一標識生成
    通過 getUniqueUserKey 方法獲取用戶標識:已登錄用戶用 token,未登錄用戶用 IP,確保不同用戶的限流互不干擾。
  2. 限流鍵設計
    限流鍵格式為 rate_limit:用戶標識:接口路徑,例如 rate_limit:test_token:/api/order/submit,精確控制「用戶 + 接口」的訪問頻率。
  3. 分布式鎖的作用
    由于 Redis 的 INCR 操作雖然原子,但在高并發下可能出現「讀取 - 判斷 - 更新」的競態條件,因此通過 Redisson 分布式鎖確保計數邏輯的原子性。
  4. 自動過期機制
    每個限流鍵都設置了與時間窗口相同的過期時間,避免 Redis 中存儲大量無效數據,同時確保時間窗口結束后自動重置計數。
    由于 Redis 的 INCR 操作雖然原子,但在高并發下可能出現「讀取 - 判斷 - 更新」的競態條件,因此通過 Redisson 分布式鎖確保計數邏輯的原子性。
  5. 自動過期機制
    每個限流鍵都設置了與時間窗口相同的過期時間,避免 Redis 中存儲大量無效數據,同時確保時間窗口結束后自動重置計數。

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

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

相關文章

ubuntu播放rosbag包(可鼠標交互)

1 前言 眾所周知&#xff0c;ubuntu中播放bag包最主要的工具是rviz&#xff0c;然而rviz有一個無法忍受的缺陷就是不支持鼠標回滾&#xff0c;并且顯示的時間的ros時間&#xff0c;不是世界時間&#xff0c;因此在遇到相關bug時不能與對應的世界時間對應。基于以上&#xff0c…

一文理解緩存的本質:分層架構、原理對比與實戰精粹

&#x1f4d6; 推薦閱讀&#xff1a;《Yocto項目實戰教程:高效定制嵌入式Linux系統》 &#x1f3a5; 更多學習視頻請關注 B 站&#xff1a;嵌入式Jerry 一文理解緩存的本質&#xff1a;分層架構、原理對比與實戰精粹 “緩存讓系統飛起來”——但每一層緩存有何不同&#xff1f;…

【離線數倉項目】——電商域DIM層開發實戰

摘要本文主要介紹了電商域離線數倉項目中DIM層的開發實戰。首先闡述了DIM層的簡介、作用、設計特征、典型維度分類以及交易支付場景下的表示例和客戶維度表設計。接著介紹了DIM層設計規范&#xff0c;包括表結構設計規范、數據處理規范以及常見要求規范。然后詳細講解了DIM層的…

Unreal Engine 自動設置圖像

void UYtGameSettingSubsystem::RunHardwareBenchmark(int32 WorkScale, float CPUMultiplier, float GPUMultiplier) {UGameUserSettings* UserSettings UGameUserSettings::GetGameUserSettings();if (UserSettings){// 運行基準測試&#xff08;異步操作&#xff0c;可能需…

使用Spring Boot和PageHelper實現數據分頁

在Spring Boot項目中&#xff0c;利用PageHelper插件可以輕松實現數據分頁功能。以下是具體的實現步驟和代碼示例。添加依賴在項目的pom.xml文件中添加PageHelper和MyBatis的依賴。<dependency><groupId>com.github.pagehelper</groupId><artifactId>p…

【IT-Infra】從ITIL到CMDB,配置管理,資產管理,物理機與設備管理(含Infra系列說明)

【IT-Infra】從ITIL到CMDB&#xff0c;配置管理&#xff0c;資產管理&#xff0c;物理機與設備管理&#xff08;含Infra系列說明&#xff09; 文章目錄序&#xff1a;Infra系列說明1、ITIL 信息技術基礎架構庫&#xff08;起源&#xff09;2、CMDB 配置管理數據庫&#xff08;I…

vue使用printJS實現批量打印及單個打印 避免空白頁

本文介紹了使用print-js庫實現批量打印功能的實現方法。通過安裝print-js依賴后,創建一個batchPrintAction方法,該方法接收選中行數據,生成包含多個標簽頁的HTML字符串。每個標簽頁以表格形式展示6個數據字段,并設置了80mm50mm的標簽尺寸。方法使用PrintJS進行打印,配置了…

C++ 選擇排序、冒泡排序、插入排序

選擇排序&#xff1a;是一種簡單直觀的排序算法&#xff0c;每次均是選擇最小&#xff08;大&#xff09;的元素進行排序。選擇排序算法思想&#xff1a;1 在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置2 再從剩余未排序元素中繼…

Linux入門篇學習——Linux 編寫第一個自己的命令,make 工具和 makefile 文件

目錄 一、Linux 編寫第一個自己的命令 1.命令的概念 2.定義一個自己的命令 二、make 工具和 makefile 文件 1.使用 make 工具 2.makefile文件 一、Linux 編寫第一個自己的命令 1.命令的概念 命令就是可執行程序。 比如說我們輸入 ls -al &#xff0c;ls 就是可執行程序的…

實驗一 接蘋果

主要步驟蘋果樹制作&#xff08;蘋果與籃子的制作同理&#xff09;為蘋果添加標簽相機位置設置與游戲面板長寬比設置&#xff08;16:9&#xff09;蘋果下落設置&#xff08;將蘋果從平拋運動改為垂直下落&#xff09;通過設置物理圖層并更改碰撞矩陣表實現通過PlayerPrefs實現游…

Nginx服務器集群:橫向擴展與集群解決方案

橫向擴展&#xff1a;基礎概念 在深入了解Nginx的橫向擴展細節之前&#xff0c;首先理解橫向擴展的含義及其重要性。橫向擴展是指通過增加服務器數量來分散負載并提升整體性能。這與縱向擴展形成對比&#xff0c;縱向擴展是指在單個服務器上增加更多資源&#xff08;如CPU、內…

缺陷的生命周期(Bug Life Cycle)是什么?

一、缺陷生命周期的定義缺陷生命周期是指一個Bug從被發現到最終關閉的完整流程&#xff0c;反映了缺陷在不同角色&#xff08;測試、開發、產品等&#xff09;間的流轉狀態。它是軟件測試流程的核心管理模型&#xff0c;直接影響團隊協作效率。二、標準缺陷生命周期階段以下是通…

AtCoder Beginner Contest 333(A,B,C,D,E,F)

AtCoder Beginner Contest 333 A 題意 輸出n個n(n<9) 代碼 #include<bits/stdc.h> using namespace std; void solve(){int n;cin>>n;for(int i1;i<n;i)cout<<n; } signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int T__1;//cin…

留學真相:凌晨兩點被海關攔下時,我才明白人生沒有退路

> 獨立不是選擇&#xff0c;而是生存的必修課下飛機那一刻&#xff0c;幻想中的“鍍金生活”瞬間崩塌。倫敦海關凌晨兩點的燈光下&#xff0c;你顫抖著翻找學校文件&#xff0c;手機信號格空空如也&#xff1b;大巴誤點后&#xff0c;你拖著兩個32公斤的行李箱站在陰雨中&am…

探索AIGC領域DALL·E 2的圖像生成與人類創意的融合

探索AIGC領域DALLE 2的圖像生成與人類創意的融合關鍵詞&#xff1a;AIGC、DALLE 2、圖像生成、人類創意、創意融合摘要&#xff1a;本文聚焦于AIGC領域中DALLE 2的圖像生成技術與人類創意的融合。首先介紹了相關背景&#xff0c;包括DALLE 2的發展歷程和人類創意在藝術創作中的…

【ECharts】多個ECharts版本共存解決方案

多個ECharts版本共存解決方案 在單個HTML頁面中使用多個ECharts版本的關鍵在于避免全局命名空間沖突。下面我將展示一個完整的解決方案&#xff0c;包含兩種不同的實現方法。 解決方案思路命名空間隔離法&#xff1a; 使用不同的全局變量名保存不同版本的ECharts在加載新版本前…

力扣熱門算法題 204.計數質數,207.課程表,213.打家劫舍II

力扣熱門算法題 204.計數質數&#xff0c;207.課程表&#xff0c;213.打家劫舍II&#xff0c;每題做詳細思路梳理&#xff0c;配套Python&Java雙語代碼&#xff0c; 2025.07.07 可通過leetcode所有測試用例。 目錄 204.計數質數 解題思路 完整代碼 207.課程表 解題思…

深入理解 macOS 的 quarantine、xattr 與 Gatekeeper

在 macOS 上安裝第三方應用時&#xff0c;你是否遇到過如下提示&#xff1f; “xxx.app 已損壞&#xff0c;無法打開。”“無法打開‘xxx.app’&#xff0c;因為它來自身份不明的開發者。”“你確定要打開這個應用嗎&#xff1f;它是從互聯網下載的。” 這些提示背后&#xff0…

FastAPI學習筆記記錄

FastAPI 學習筆記 最近在公司中需要寫接口&#xff0c;選取了fastapi這個框架&#xff0c;一個原因是FastAPI 是主流框架&#xff0c;同時FastAPI 有著高性能&#xff0c;支持異步和高并發。 FastAPI 安裝 直接用下面兩行命令進行安裝 pip3 install fastapi pip install uvicor…

HTML(上)

1.web標準主要包括結構(Structure)、表現(Presentation)和行為(Behavior)三個方面。1.1 結構結構用于對網頁元素進行整理和分類&#xff0c;核心技術&#xff1a;HTML。 HTML (HyperText Markup Language)&#xff1a;超文本標記語言&#xff0c;用于定義網頁的內容和結構&…