利用 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;
}
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);
}
}
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)。
redis-cell 或自行實現多子窗口加權)。ratelimit: 避免污染業務數據;可結合 API 路徑 ratelimit:/api/order:192.168.1.1 實現精細化限流。短短 10 行核心代碼,卻能撐起高併發系統的第一道防線。建議將限流邏輯封裝為通用組件(Filter/Interceptor/Aspect),接入配置中心動態調整閾值,實現「零代碼變更、秒級生效」的運維體驗。