如何使用 Redis 實現 API 網關或單個服務的請求限流?

使用 Redis 高效實現 API 網關與服務的請求限流

在微服務架構中,對 API 網關或單個服務的請求進行速率限制至關重要,以防止惡意攻擊、資源濫用并確保系統的穩定性和可用性。 Redis 憑借其高性能、原子操作和豐富的數據結構,成為實現請求限流的理想選擇。

本文將詳細探討如何利用 Redis 實現 API 網關或單個服務的請求限流,深入分析各種主流算法,并提供在 Python 和 Java 環境下的具體代碼示例,包括使用 Lua 腳本確保操作的原子性。

為什么選擇 Redis 進行限流?

Redis 之所以成為實現限流的熱門選擇,主要得益于其以下幾個關鍵特性:

  • 卓越的性能: Redis 是一款內存數據庫,能夠提供極快的讀寫速度,這對于需要實時處理大量請求的限流場景至關重要。
  • 原子操作: Redis 的 INCREXPIRE 等命令是原子性的,可以避免在并發請求下出現競態條件,確保計數和過期的準確性。
  • 豐富的數據結構: Redis 提供了字符串、哈希、列表、有序集合等多種數據結構,可以靈活地實現各種復雜的限流算法。
  • 可擴展性: Redis 支持集群和橫向擴展,能夠應對高并發的請求環境。
  • 自帶過期機制: 通過 EXPIRE 命令可以為鍵設置生存時間(TTL),輕松實現時間窗口的自動重置。

核心概念:識別請求來源

在實施限流之前,首先需要確定如何識別和區分不同的請求來源。常見的識別方式包括:

  • IP 地址: 基于客戶端的 IP 地址進行限制,是簡單有效的常用方法。
  • 用戶 ID 或 API 密鑰: 針對已認證的用戶或第三方應用進行精細化限流。
  • 設備 ID: 對移動端等特定設備進行限制。

主流限流算法及其 Redis 實現

以下是幾種主流的限流算法及其使用 Redis 的實現方式:

1. 固定窗口計數器 (Fixed Window Counter)

這是最簡單的限流算法。它在固定的時間窗口內(例如,每分鐘)統計請求次數,如果超過預設的閾值,則拒絕后續的請求,直到下一個時間窗口開始。

優點: 實現簡單,容易理解。

缺點: 在時間窗口的邊界處可能會出現“突刺”流量問題。例如,在窗口結束前的瞬間和新窗口開始的瞬間,可能會有兩倍于限制的請求通過。

Redis 實現:

主要利用 INCREXPIRE 命令。

  • Python 示例:

    import redis
    import timer = redis.Redis()def is_rate_limited_fixed_window(user_id: str, limit: int, window: int) -> bool:key = f"rate_limit:{user_id}"current_requests = r.get(key)if current_requests is None:# 使用 pipeline 保證原子性pipe = r.pipeline()pipe.incr(key)pipe.expire(key, window)pipe.execute()return Falseif int(current_requests) >= limit:return Truer.incr(key)return False# 示例: 每位用戶每60秒最多10個請求
    for i in range(15):if is_rate_limited_fixed_window("user123", 10, 60):print("請求被限制")else:print("請求成功")time.sleep(1)
    
  • 使用 Lua 腳本保證原子性:

    為了避免 GETINCR 之間的競態條件,強烈建議使用 Lua 腳本將多個命令作為一個原子操作執行。

    -- rate_limiter.lua
    local key = KEYS[1]
    local limit = tonumber(ARGV[1])
    local window = tonumber(ARGV[2])local current = tonumber(redis.call("GET", key) or "0")if current >= limit thenreturn 1 -- 1 表示被限制
    endif current == 0 thenredis.call("INCR", key)redis.call("EXPIRE", key, window)
    elseredis.call("INCR", key)
    endreturn 0 -- 0 表示未被限制
    
    • Java (Jedis) 調用 Lua 腳本示例:
      import redis.clients.jedis.Jedis;public class RateLimiter {private final Jedis jedis;private final String script;public RateLimiter(Jedis jedis) {this.jedis = jedis;// 實際項目中應從文件加載this.script = "local key = KEYS[1]..."}public boolean isRateLimited(String key, int limit, int window) {Object result = jedis.eval(this.script, 1, key, String.valueOf(limit), String.valueOf(window));return "1".equals(result.toString());}
      }
      
2. 滑動窗口日志 (Sliding Window Log)

該算法記錄每個請求的時間戳。當一個新請求到達時,會移除時間窗口之外的舊時間戳,然后統計窗口內的時間戳數量。如果數量超過限制,則拒絕請求。

優點: 限流精度高,有效解決了固定窗口的邊界問題。

缺點: 需要存儲所有請求的時間戳,當請求量很大時會占用較多內存。

Redis 實現:

使用有序集合 (Sorted Set),將成員 (member) 設置為唯一值(如請求 ID 或時間戳),將分數 (score) 設置為請求的時間戳。

  • Python 示例:

    import redis
    import timer = redis.Redis()def is_rate_limited_sliding_log(user_id: str, limit: int, window: int) -> bool:key = f"rate_limit_log:{user_id}"now = int(time.time() * 1000)window_start = now - window * 1000# 使用 pipeline 保證原子性pipe = r.pipeline()# 移除窗口外的數據pipe.zremrangebyscore(key, 0, window_start)# 添加當前請求pipe.zadd(key, {f"{now}:{int(time.time()*1000000)}": now}) # 保證 member 唯一# 獲取窗口內的請求數pipe.zcard(key)# 設置過期時間,防止冷數據占用內存pipe.expire(key, window)results = pipe.execute()current_requests = results[2]return current_requests > limit
    
  • 使用 Lua 腳本實現滑動窗口:

    -- sliding_window.lua
    local key = KEYS[1]
    local limit = tonumber(ARGV[1])
    local window = tonumber(ARGV[2])
    local now = redis.call("TIME")
    local now_ms = (now[1] * 1000) + math.floor(now[2] / 1000)
    local window_start = now_ms - window * 1000-- 移除過期的請求記錄
    redis.call("ZREMRANGEBYSCORE", key, 0, window_start)-- 獲取當前窗口內的請求數
    local count = redis.call("ZCARD", key)if count >= limit thenreturn 1 -- 被限制
    end-- 添加當前請求
    redis.call("ZADD", key, now_ms, now_ms .. "-" .. count) -- member 保證唯一性
    redis.call("EXPIRE", key, window)return 0 -- 未被限制
    
3. 令牌桶算法 (Token Bucket)

該算法的核心是一個固定容量的“令牌桶”,系統會以恒定的速率向桶里放入令牌。每個請求需要從桶里獲取一個令牌才能被處理。如果桶里沒有令牌,請求將被拒絕或排隊等待。

優點: 能夠應對突發流量,只要桶內有足夠的令牌,就可以一次性處理多個請求。

缺點: 實現相對復雜,需要一個獨立的進程或定時任務來持續生成令牌。

Redis 實現:

可以使用 Redis 的列表 (List) 作為令牌桶。一個后臺任務(或在每次請求時計算)負責向列表中添加令牌。

  • Java (Spring Boot with Bucket4j) 示例:

    Bucket4j 是一個流行的 Java 限流庫,可以與 Redis 結合使用實現分布式令牌桶。

    // 依賴: bucket4j-core, jcache-api, redisson
    // 配置 RedissonClient...// 創建一個基于 Redis 的代理管理器
    ProxyManager<String> proxyManager = new JCacheProxyManager<>(jcache);// 定義令牌桶配置:每分鐘補充10個令牌,桶容量為10
    BucketConfiguration configuration = BucketConfiguration.builder().addLimit(Bandwidth.simple(10, Duration.ofMinutes(1))).build();// 獲取或創建令牌桶
    Bucket bucket = proxyManager.getProxy("rate-limit-bucket:user123", configuration);// 嘗試消費一個令牌
    if (bucket.tryConsume(1)) {// 請求成功
    } else {// 請求被限制
    }
    

    Spring Cloud Gateway 也內置了基于 Redis 的令牌桶算法限流器。

4. 漏桶算法 (Leaky Bucket)

漏桶算法將請求看作是流入“漏桶”的水,而漏桶以固定的速率漏出水(處理請求)。如果流入的速率過快,導致桶溢出,則多余的請求將被丟棄。

優點: 能夠平滑請求流量,保證服務以恒定的速率處理請求。

缺點: 無法有效利用系統空閑資源來處理突發流量。

Redis 實現:

Redis-Cell 模塊提供了基于 GCRA (Generic Cell Rate Algorithm) 的漏桶算法實現。

API 網關限流 vs. 單個服務限流

限流邏輯可以部署在不同的層面,主要分為 API 網關層和單個微服務層。

API 網關限流

在 API 網關層面進行統一限流是一種常見的做法。

  • 優點:

    • 集中管理: 可以在一個地方統一配置和管理所有服務的限流規則。
    • 保護后端服務: 將惡意或超額流量擋在微服務集群之外,保護整個系統的穩定性。
  • 實現方式:

    • 利用網關自帶功能: 像 Spring Cloud Gateway、Kong、KrakenD 等 API 網關都提供了基于 Redis 的限流插件或模塊。
    • 自定義中間件: 在網關中編寫自定義的中間件或過濾器,嵌入上述的 Redis 限流邏輯。
  • Spring Cloud Gateway 示例 (application.yml):

    spring:cloud:gateway:routes:- id: my_routeuri: lb://my-servicepredicates:- Path=/my-api/**filters:- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 10      # 令牌桶每秒填充速率redis-rate-limiter.burstCapacity: 20     # 令牌桶容量redis-rate-limiter.requestedTokens: 1  # 每次請求消耗的令牌數key-resolver: "#{@ipKeyResolver}"     # 使用 IP 地址作為限流的 key
    
單個服務限流

將限流邏輯直接實現在單個微服務內部。

  • 優點:

    • 靈活性: 每個服務可以根據自身的負載能力和業務需求,定制更精細化的限流策略。
    • 獨立性: 不依賴于特定的 API 網關。
  • 實現方式:

    • 通過 AOP (面向切面編程) 創建注解,對需要限流的 Controller 方法進行攔截。
    • 在服務的入口處,如攔截器或過濾器中,調用 Redis 進行限流判斷。
  • Python (FastAPI 中間件) 示例:

    from fastapi import FastAPI, Request
    from fastapi.responses import JSONResponse
    import redisapp = FastAPI()
    r = redis.Redis()@app.middleware("http")
    async def rate_limit_middleware(request: Request, call_next):# 簡單示例:使用固定窗口計數器ip = request.client.hostkey = f"rate_limit:{ip}"limit = 5window = 60# 此處應使用 Lua 腳本保證原子性current = r.incr(key)if current == 1:r.expire(key, window)if current > limit:return JSONResponse(status_code=429, content={"message": "Too Many Requests"})response = await call_next(request)return response
    

總結

使用 Redis 實現請求限流是一種高效且可擴展的方案。開發者應根據具體的業務場景和需求選擇合適的限流算法。

  • 對于簡單的限流需求,固定窗口計數器是一個不錯的起點。
  • 為了更精確地控制流量并避免邊界問題,滑動窗口日志滑動窗口計數器是更好的選擇。
  • 如果需要應對突發流量,令牌桶算法則非常適用。

在實施時,務必使用 Lua 腳本來保證操作的原子性,避免在并發環境下出現數據不一致的問題。將限流邏輯部署在 API 網關層可以對整個系統起到保護作用,而部署在單個服務層則提供了更高的靈活性。在實際應用中,兩者也常常結合使用,構建多層級的防護體系。

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

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

相關文章

圖片查重從設計到實現(7) :使用 Milvus 實現高效圖片查重功能

使用 Milvus 實現高效圖片查重功能本文將介紹如何利用 Milvus 向量數據庫構建一個高效的圖片查重系統&#xff0c;通過傳入圖片就能快速從已有數據中找出匹配度高的相似圖片。一.什么是圖片查重&#xff1f; 圖片查重指的是通過算法識別出內容相同或高度相似的圖片&#xff0c;…

誘導多能干細胞(iPSC)的自述

自十七年前誘導多能干細胞&#xff08;也稱iPS細胞或iPSC&#xff09;技術出現以來&#xff0c;干細胞生物學和再生醫學取得了巨大進展。人類iPSC已廣泛用于疾病建模、藥物發現和細胞療法開發。新的病理機制已被闡明&#xff0c;源自iPSC篩選的新藥正在研發中&#xff0c;并且首…

基于深度學習的醫學圖像分析:使用DeepLabv3+實現醫學圖像分割

前言 醫學圖像分析是計算機視覺領域中的一個重要應用&#xff0c;特別是在醫學圖像分割任務中&#xff0c;深度學習技術已經取得了顯著的進展。醫學圖像分割是指從醫學圖像中識別和分割出特定的組織或器官&#xff0c;這對于疾病的診斷和治療具有重要意義。近年來&#xff0c;D…

Lombok 字段魔法:用 @FieldDefaults 解鎖“隱身+鎖死”雙重特效

前言 項目里總有這樣一種神秘現象:明明只是幾個字段,卻堆滿 private final,每次都得機械敲上一遍。有的同事一邊敲一邊默念“代碼規范不能丟”,表情嚴肅得像在寫遺囑。可惜,規范雖好,手指遭殃。 于是,Lombok 悄然登場,肩扛簡潔大旗,手握注解神器,@FieldDefaults 正…

小白如何自學網絡安全,零基礎入門到精通,看這一篇就夠了!

小白如何自學網絡安全&#xff0c;零基礎入門到精通&#xff0c;看這一篇就夠了&#xff01; 小白人群想學網安但是不知道從哪入手&#xff1f;一篇文章告訴你如何在4個月內吃透網安課程&#xff0c;掌握網安技術 一、基礎階段 1.了解網安相關基礎知識 了解中華人民共和國網…

前端 vue 第三方工具包詳解-小白版

恭喜你邁入Vue世界&#xff01;&#x1f604; 對于前端小白&#xff0c;掌握這些常用第三方包能極大提升開發效率和項目質量。以下是Vue生態中必備的第三方包及小白友好式用法解析&#xff1a;&#x1f9f1; 一、基礎工具包&#xff08;每個項目必裝&#xff09; 1. Vue Router…

解決mac下git pull、push需要輸入密碼

解決方法&#xff1a; 1.強制配置 SSH 自動加載鑰匙串 編輯 SSH 配置文件 vi ~/.ssh/configHost *AddKeysToAgent yes # 自動將密鑰添加到 ssh-agentUseKeychain yes # 明確使用鑰匙串存儲密碼IdentityFile ~/.ssh/id_rsa # 替換為你的私鑰路徑2.修復 Sh…

內存網格、KV存儲和Redis的概念、使用場景及異同

基本概念 內存網格 (In-Memory Data Grid - IMDG) 內存網格是一種分布式內存數據存儲技術&#xff0c;具有以下特點&#xff1a;分布式架構 數據跨多個服務器節點分布存儲提供線性擴展能力內存優先 主要數據存儲在內存中&#xff0c;提供微秒級訪問延遲支持持久化作為備份企業級…

【C++算法】87.BFS解決最短路徑問題_為高爾夫比賽砍樹

文章目錄題目鏈接&#xff1a;題目描述&#xff1a;解法C 算法代碼&#xff1a;題目鏈接&#xff1a; 675. 為高爾夫比賽砍樹 題目描述&#xff1a; 解法 注意&#xff1a;砍樹要從低到高砍。 砍掉1&#xff0c;從1到5到2 砍掉2&#xff0c;從2到5到3 砍掉3&#xff0c;從3到5…

JavaScript內存管理完全指南:從入門到精通

文章目錄JavaScript內存管理完全指南&#xff1a;從入門到精通1. 哪些數據類型屬于引用類型&#xff08;復雜數據類型&#xff09;&#xff1f;2. 為什么引用類型要存儲在堆中&#xff1f;3. 引用類型的內存存儲示例示例 1&#xff1a;對象&#xff08;Object&#xff09;示例 …

Linux網絡-------3.應?層協議HTTP

1.HTTP協議 雖然我們說,應?層協議是我們程序猿??定的.但實際上,已經有?佬們定義了?些現成的,??常好?的應?層協議,供我們直接參考使?.HTTP(超?本傳輸協議)就是其中之?。 在互聯?世界中&#xff0c;HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超?本…

05 GWAS表型數據處理原理

表型數據處理 ? 質量性狀 – 二分類&#xff1a;可用0 / 1, 1 / 2 數值表示 – 多分類&#xff1a;啞變量賦值&#xff0c;0/1 ? 數量性狀 – 盡量符合正太分布 – 剔除異常表型值樣本 – 多年多點重復觀測 – 對于閾值性狀&#xff0c;分級數量化或啞變量賦值 R中 shapiro.t…

【Cpolar實現內網穿透】

Cpolar實現內網穿透業務需求第一步&#xff1a;準備工作1、關閉安全軟件2、下載所需軟件第二步&#xff1a;Nginx的配置第三步&#xff1a;使用cpolar實現內網穿透1、進入 https://dashboard.cpolar.com/get-started 注冊&#xff0c;登錄&#xff0c;完成身份證的實名認證2、下…

基于 JavaWeb+MySQL 的學院黨費繳費系統

基于 JavaWeb 的學院黨費繳費系統第 1 章緒論1.1 項目背景當今互聯網發展及其迅速&#xff0c;互聯網的便利性已經遍及到各行各業&#xff0c;惠及到每一個人&#xff0c;傳統的繳費方式都需要每個人前往繳費點陸續排隊繳費&#xff0c;不僅浪費大量了個人時間&#xff0c;而且…

LCGL基本使用

LVGC簡介 light video Graphics Library (1)純c與語言編程,將面向對象的思想植入c語言。 (2)輕量化圖形庫資源,人機交互效果好,在(ios Android QT)移植性較好,但是這些平臺對硬件要求較高 lcgc工程搭建 工程源碼的獲取 獲取工程結構 https://github.com/lvgl/lv_po…

嵌入式第十六課!!!結構體與共用體

一、結構體結構體是一種數據類型&#xff0c;它的形式是這樣的&#xff1a;struct 結構體名{ 結構體成員語句1&#xff1b;結構體成員語句2&#xff1b;結構體成員語句3&#xff1b;}&#xff1b;舉個例子&#xff1a;struct Student {int id;char name[20];float score…

java web 實現簡單下載功能

java web 實現簡單下載功能 項目結構├── src\ │ ├── a.txt │ └── com\ │ └── demo\ │ └── web\ │ ├── Cookie\ │ ├── download\ │ ├── homework\ │ ├── serv…

虛幻基礎:模型穿模

能幫到你的話&#xff0c;就給個贊吧 &#x1f618; 文章目錄模型穿模模型之間的阻擋是否正確設置模型是角色的組件&#xff1a;角色的組件不會與場景中其他的物體發生阻擋但可以發生重疊模型穿模 模型之間的阻擋是否正確設置 模型是角色的組件&#xff1a;角色的組件不會與場…

【Linux】linux基礎開發工具(二) 編譯器gcc/g++、動靜態庫感性認識、自動化構建-make/Makefile

文章目錄一、gcc/g介紹二、gcc編譯選項預處理編譯匯編鏈接三個細節三、動靜態庫感性認識動靜態庫的優缺點四、自動化構建-make/Makefile背景知識初步上手Makefilemakefile的推導過程makefile語法一、gcc/g介紹 我們之前介紹了編輯器vim&#xff0c;可以讓我們在linux上linux系統…

CentOS 7 上使用 Docker 安裝 Jenkins 完整教程

目錄 前言 準備工作 系統要求 檢查系統信息 更新系統 安裝Docker 第一步:卸載舊版本Docker(如果存在) 第二步:安裝必要的軟件包 第三步:添加Docker官方倉庫 第四步:安裝Docker CE 第五步:啟動Docker服務 第六步:驗證Docker安裝 第七步:配置Docker用戶權限…