首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

基於 Redis 的分佈式 IP 限流:10 行代碼實現百萬級 QPS 控制

     編輯:Java 綜合教學

核心思路:Redis 原子操作 + Key 過期機制

利用 Redis 的 INCRBY 原子自增與 EXPIRE 過期時間,以 IP 為 Key、限流窗口為 TTL,即可實現高性能、分佈式的頻率控制。單節點 Redis 輕鬆支撐 6 萬+ ops/s,集群模式下可水平擴展至百萬級 QPS。

最小化實現(偽代碼)

// 限制:同一 IP 2 秒內最多 1000 次請求
String key = "ratelimit:" + ip;
long count = redis.incrby(key, 1);

if (count > 1000) {
    // 超過限流閾值,拒絕服務
    return REJECT;
} else {
    if (count == 1) {
        // 首次請求,設置 2 秒過期
        redis.expire(key, 2);
    }
    // 放行
    return ALLOW;
}

完整可運行 Java 示例(基於 r3c 客戶端)

import r3c.CRedisClient;
import r3c.RedisException;

public class IpRateLimiter {
    private final CRedisClient redis;
    private final int limit;      // 最大請求數
    private final int windowSec;  // 時間窗口(秒)

    public IpRateLimiter(String redisNodes, int limit, int windowSec) {
        this.redis = new CRedisClient(redisNodes);
        this.limit = limit;
        this.windowSec = windowSec;
    }

    public boolean tryAcquire(String ip) {
        String key = "rl:" + ip;
        try {
            long cnt = redis.incrby(key, 1);
            if (cnt > limit) {
                return false; // 限流
            }
            if (cnt == 1) {
                redis.expire(key, windowSec); // 設置窗口
            }
            return true; // 放行
        } catch (RedisException e) {
            // Redis 故障時的降級策略:默認放行或拒絕,視業務而定
            return true; // fail-open
        }
    }

    public static void main(String[] args) {
        IpRateLimiter limiter = new IpRateLimiter("10.223.25.102:6379", 1000, 2);
        String testIp = "127.0.0.1";

        for (int i = 0; i < 100000; i++) {
            boolean ok = limiter.tryAcquire(testIp);
            if (!ok) {
                System.out.println("[OVER] 超過頻率,限制訪問");
            } else if (i == 0) {
                System.out.println("[FIRST] 第一次,訪問放行");
            }
        }
        // 清理測試數據
        limiter.redis.del("rl:" + testIp);
    }
}

關鍵細節與優化建議

  1. 原子性保證:INCRBY + EXPIRE 非單一原子操作,極端併發下可能出現「計數器已過期但未刪除,重新設置 EXPIRE 導致窗口延長」的微小竄改。生產環境建議使用 Lua 腳本保證原子性:
-- rate_limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local cnt = redis.call('INCR', key)
if cnt == 1 then
    redis.call('EXPIRE', key, window)
end
if cnt > limit then
    return 0
end
return 1

Java 調用:redis.eval(script, 1, key, limit, windowSec)。

  1. 滑動窗口 vs 固定窗口:上述為固定窗口,邊界處可能允許 2 倍流量突發。若需嚴格平滑,可用 滑動日誌(Sorted Set,score=timestamp)或 滑動窗口計數器(Redis 4.0+ 模塊 redis-cell 或自行實現多子窗口加權)。
  2. Key 設計:加前綴 ratelimit: 避免污染業務數據;可結合 API 路徑 ratelimit:/api/order:192.168.1.1 實現精細化限流。
  3. 異常處理:Redis 不可用時採用 Fail-Open(放行)或 Fail-Close(拒絕),視業務風險偏好決定,並上報監控告警。
  4. 性能調優:使用連接池、Pipeline 批量發送、集群模式(Redis Cluster / Codis / Twemproxy)分片,單機 6 萬+ QPS,集群線性擴展。

適用場景

  • API 網關防刷、防爬蟲
  • 登錄/註冊/短信發送接口頻率控制
  • 惡意 IP 自動封禁(配合計數器超閾值寫入黑名單 Set,TTL 24h)
  • 分佈式系統熔斷前的流量整形

短短 10 行核心代碼,卻能撐起高併發系統的第一道防線。建議將限流邏輯封裝為通用組件(Filter/Interceptor/Aspect),接入配置中心動態調整閾值,實現「零代碼變更、秒級生效」的運維體驗。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved