Redis分布式限流生產(chǎn)環(huán)境落地方案
該方案適配Spring Boot 2.x/3.x + Spring Cloud微服務(wù)生態(tài),覆蓋Lua 腳本原子限流(固定窗口 / 滑動(dòng)窗口)、Redisson 令牌桶 / 信號(hào)量限流、Spring Cloud Gateway 網(wǎng)關(guān)統(tǒng)一限流、Redis 宕機(jī)兜底降級(jí)四大核心能力,兼顧原子性、動(dòng)態(tài)配置、高可用、可監(jiān)控,可直接復(fù)制到生產(chǎn)環(huán)境并按需微調(diào)。
前置依賴
生產(chǎn)環(huán)境統(tǒng)一引入以下核心依賴(Maven),版本適配自身 Spring Cloud/Spring Boot 版本:
<!-- Redis核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson分布式限流 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>
<!-- Spring Cloud Gateway網(wǎng)關(guān) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos動(dòng)態(tài)配置(限流規(guī)則熱更新) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 熔斷降級(jí)(Sentinel,可選替換為Resilience4j) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 工具類 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.23</version>
</dependency>
<!-- 本地降級(jí)限流(Guava) -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
一、基礎(chǔ)環(huán)境配置
1. Redis 連接配置(application.yml)
包含連接池、序列化優(yōu)化,適配單機(jī) / 主從 / 集群:
spring:
redis:
host: 192.168.1.100 # 生產(chǎn)替換為Redis集群地址
port: 6379
password: prod_redis_123
database: 2 # 限流專用庫,與業(yè)務(wù)隔離
lettuce:
pool:
max-active: 50 # 最大連接數(shù),按QPS調(diào)整
max-idle: 20
min-idle: 5
max-wait: 3000ms # 連接超時(shí),生產(chǎn)建議3s內(nèi)
timeout: 2000ms # Redis操作超時(shí)
# Nacos配置(動(dòng)態(tài)限流規(guī)則)
cloud:
nacos:
config:
server-addr: 192.168.1.101:8848
namespace: prod
group: DEFAULT_GROUP
file-extension: yml
shared-configs:
- data-id: redis-limit-rules.yml # 限流規(guī)則統(tǒng)一配置文件
group: DEFAULT_GROUP
refresh: true # 支持熱更新
2. Redisson 配置(生產(chǎn)高可用版)
支持單機(jī) / 主從 / 哨兵 / 集群,這里以Redis Cluster為例(生產(chǎn)推薦),創(chuàng)建RedissonConfig.java:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
// 從Nacos/配置文件讀取Redis集群地址
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database:0}")
private int database;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 集群模式配置(生產(chǎn)核心)
config.useClusterServers()
.addNodeAddress(clusterNodes.split(","))
.setPassword(password)
.setDatabase(database)
.setScanInterval(2000) // 節(jié)點(diǎn)掃描間隔
.setMasterConnectionPoolSize(50) // 主節(jié)點(diǎn)連接池
.setSlaveConnectionPoolSize(20) // 從節(jié)點(diǎn)連接池
.setReadMode(ReadMode.SLAVE) // 讀從節(jié)點(diǎn),分擔(dān)壓力
.setSubscriptionMode(SubscriptionMode.SLAVE); // 訂閱從節(jié)點(diǎn)
// 超時(shí)配置,避免Redis阻塞
config.setConnectTimeout(2000);
config.setTimeout(2000);
return Redisson.create(config);
}
}
Nacos 補(bǔ)充配置:在application.yml添加 Redis 集群地址,支持熱更新:
spring:
redis:
cluster:
nodes: redis://192.168.1.100:7001,redis://192.168.1.100:7002,redis://192.168.1.100:7003
二、Lua 腳本原子限流模板(固定窗口 + 滑動(dòng)窗口)
依托 Redis單線程原子執(zhí)行 Lua 腳本,解決計(jì)數(shù)限流的并發(fā)問題,分為固定窗口(簡(jiǎn)單高效,生產(chǎn) 80% 場(chǎng)景適用)和滑動(dòng)窗口(解決固定窗口臨界超量問題,高精度場(chǎng)景適用),封裝通用調(diào)用工具類,支持接口 / IP / 用戶多維度限流。
1. 核心 Lua 腳本(放在resources/lua目錄下,生產(chǎn)建議統(tǒng)一管理)
(1)固定窗口計(jì)數(shù)限流limit_fixed_window.lua
核心邏輯:計(jì)數(shù) + 過期時(shí)間原子設(shè)置,達(dá)到閾值返回 0(拒絕),否則返回 1(允許),支持自定義窗口時(shí)長(zhǎng)和閾值。
-- 固定窗口限流Lua腳本(原子性)
-- KEYS[1]:限流key(如limit:api:order:192.168.1.1)
-- ARGV[1]:限流閾值(如100)
-- ARGV[2]:窗口過期時(shí)間(秒,如60)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])
-- 原子計(jì)數(shù)
local current = redis.call('incr', key)
-- 首次計(jì)數(shù),設(shè)置過期時(shí)間(避免內(nèi)存泄漏)
if current == 1 then
redis.call('expire', key, expire)
end
-- 超過閾值返回0,否則返回1
if current > limit then
return 0
else
return 1
end
(2)滑動(dòng)窗口計(jì)數(shù)限流limit_slide_window.lua
核心邏輯:基于 ZSet 存儲(chǔ)請(qǐng)求時(shí)間戳,自動(dòng)清理過期請(qǐng)求,統(tǒng)計(jì)窗口內(nèi)總請(qǐng)求數(shù),解決固定窗口臨界超量問題(如 1 分鐘窗口,59 秒和 1 秒各請(qǐng)求 100 次,固定窗口會(huì)允許 200 次,滑動(dòng)窗口僅允許 100 次)。
-- 滑動(dòng)窗口限流Lua腳本(原子性)
-- KEYS[1]:限流key(如limit:api:seckill:192.168.1.1)
-- ARGV[1]:限流閾值(如100)
-- ARGV[2]:窗口時(shí)長(zhǎng)(毫秒,如60000)
-- ARGV[3]:當(dāng)前請(qǐng)求時(shí)間戳(毫秒,如System.currentTimeMillis())
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local windowMs = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 窗口開始時(shí)間:當(dāng)前時(shí)間 - 窗口時(shí)長(zhǎng)
local windowStart = now - windowMs
-- 1. 移除ZSet中過期的請(qǐng)求記錄(score < 窗口開始時(shí)間)
redis.call('zremrangebyscore', key, 0, windowStart)
-- 2. 統(tǒng)計(jì)當(dāng)前窗口內(nèi)的請(qǐng)求數(shù)
local current = redis.call('zcard', key)
-- 3. 超過閾值,返回0拒絕
if current >= limit then
return 0
end
-- 4. 未超閾值,添加當(dāng)前請(qǐng)求到ZSet(score=時(shí)間戳,value=唯一標(biāo)識(shí)避免重復(fù))
redis.call('zadd', key, now, now .. math.random(10000))
-- 5. 設(shè)置ZSet過期時(shí)間,避免內(nèi)存泄漏(窗口時(shí)長(zhǎng)+1秒)
redis.call('expire', key, math.ceil(windowMs / 1000) + 1)
return 1
2. Lua 腳本通用調(diào)用工具類RedisLimitLuaUtil.java
封裝腳本加載、參數(shù)組裝、Redis 調(diào)用邏輯,全局唯一,避免重復(fù)加載腳本,支持多維度限流 key 生成:
import cn.hutool.core.util.StrUtil;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;
/**
* Redis Lua腳本限流通用工具類(固定窗口+滑動(dòng)窗口)
*/
@Component
public class RedisLimitLuaUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// 固定窗口腳本
private DefaultRedisScript<Long> fixedWindowScript;
// 滑動(dòng)窗口腳本
private DefaultRedisScript<Long> slideWindowScript;
// 限流key前綴,統(tǒng)一規(guī)范
private static final String LIMIT_KEY_PREFIX = "limit:";
/**
* 初始化Lua腳本(項(xiàng)目啟動(dòng)時(shí)加載,避免重復(fù)加載)
*/
@PostConstruct
public void initScript() {
// 固定窗口腳本初始化
fixedWindowScript = new DefaultRedisScript<>();
fixedWindowScript.setResultType(Long.class);
fixedWindowScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit_fixed_window.lua")));
// 滑動(dòng)窗口腳本初始化
slideWindowScript = new DefaultRedisScript<>();
slideWindowScript.setResultType(Long.class);
slideWindowScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit_slide_window.lua")));
}
/**
* 固定窗口限流判斷
* @param limitDimension 限流維度(api/ip/user)
* @param target 限流目標(biāo)(如接口名order/create、IP192.168.1.1、用戶ID1001)
* @param limit 限流閾值
* @param expire 窗口過期時(shí)間(秒)
* @return true=允許,false=拒絕
*/
public boolean fixedWindowLimit(String limitDimension, String target, int limit, int expire) {
try {
String key = generateLimitKey(limitDimension, target);
// 執(zhí)行Lua腳本,原子判斷
Long result = redisTemplate.execute(
fixedWindowScript,
Collections.singletonList(key),
limit,
expire
);
// 結(jié)果為1則允許,0則拒絕
return Objects.nonNull(result) && result == 1;
} catch (Exception e) {
// Redis異常時(shí),返回false觸發(fā)降級(jí)策略
log.error("固定窗口限流Redis執(zhí)行異常,維度:{},目標(biāo):{}", limitDimension, target, e);
return false;
}
}
/**
* 滑動(dòng)窗口限流判斷
* @param limitDimension 限流維度(api/ip/user)
* @param target 限流目標(biāo)
* @param limit 限流閾值
* @param windowMs 窗口時(shí)長(zhǎng)(毫秒)
* @return true=允許,false=拒絕
*/
public boolean slideWindowLimit(String limitDimension, String target, int limit, long windowMs) {
try {
String key = generateLimitKey(limitDimension, target);
long now = System.currentTimeMillis();
// 執(zhí)行Lua腳本,原子判斷
Long result = redisTemplate.execute(
slideWindowScript,
Collections.singletonList(key),
limit,
windowMs,
now
);
return Objects.nonNull(result) && result == 1;
} catch (Exception e) {
log.error("滑動(dòng)窗口限流Redis執(zhí)行異常,維度:{},目標(biāo):{}", limitDimension, target, e);
return false;
}
}
/**
* 生成限流key:limit:維度:目標(biāo)
* 例:limit:ip:192.168.1.1、limit:api:order/create
*/
private String generateLimitKey(String limitDimension, String target) {
return StrUtil.format("{}{}:{}", LIMIT_KEY_PREFIX, limitDimension, target);
}
}
3. 業(yè)務(wù)層調(diào)用示例(Controller/Service)
直接注入工具類,按需選擇固定 / 滑動(dòng)窗口,一行代碼實(shí)現(xiàn)限流:
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Resource
private RedisLimitLuaUtil redisLimitLuaUtil;
@Resource
private LimitDegradeUtil limitDegradeUtil; // 降級(jí)工具類(后續(xù)實(shí)現(xiàn))
@PostMapping("/create")
public Result createOrder(HttpServletRequest request) {
// 1. 提取限流目標(biāo)(接口+IP雙維度,生產(chǎn)常用)
String apiTarget = "order/create";
String ipTarget = request.getRemoteAddr();
// 2. 限流規(guī)則:接口每分鐘1000次,IP每分鐘100次
boolean apiLimit = redisLimitLuaUtil.fixedWindowLimit("api", apiTarget, 1000, 60);
boolean ipLimit = redisLimitLuaUtil.fixedWindowLimit("ip", ipTarget, 100, 60);
// 3. 限流判斷+降級(jí)(Redis異常時(shí)觸發(fā)本地降級(jí))
if (!apiLimit || !ipLimit) {
// 觸發(fā)降級(jí):先嘗試本地兜底,失敗則返回限流提示
if (!limitDegradeUtil.localLimitFallback(apiTarget)) {
return Result.fail(429, "請(qǐng)求過于頻繁,請(qǐng)稍后再試");
}
}
// 4. 執(zhí)行業(yè)務(wù)邏輯
return Result.success("訂單創(chuàng)建成功");
}
}
三、Redisson 限流核心模板(令牌桶 + 信號(hào)量)
Redisson 底層基于 Lua 腳本實(shí)現(xiàn)原子性,開箱即用支持令牌桶限流(應(yīng)對(duì)突發(fā)流量,如秒殺 / 網(wǎng)關(guān))和信號(hào)量限流(控制并發(fā)數(shù),如數(shù)據(jù)庫 / 中間件資源訪問),封裝通用工具類,支持全局唯一令牌池和動(dòng)態(tài)規(guī)則調(diào)整。
1. Redisson 限流通用工具類RedissonLimitUtil.java
封裝令牌桶(RateLimiter)和信號(hào)量(Semaphore)核心方法,生產(chǎn)建議單例,避免重復(fù)創(chuàng)建 Redisson 對(duì)象:
import org.redisson.api.RRateLimiter;
import org.redisson.api.RSemaphore;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* Redisson限流工具類(令牌桶+信號(hào)量)
* 令牌桶:控制QPS,支持突發(fā)流量
* 信號(hào)量:控制并發(fā)數(shù),保護(hù)下游資源
*/
@Component
public class RedissonLimitUtil {
@Resource
private RedissonClient redissonClient;
// 限流key前綴
private static final String RATE_LIMIT_PREFIX = "rate:limit:";
private static final String SEMAPHORE_PREFIX = "semaphore:limit:";
/**
* 令牌桶限流(獲取1個(gè)令牌,無超時(shí))
* @param target 限流目標(biāo)(如seckill/sku1001、gateway/api)
* @param rate 令牌生成速率(如100)
* @param rateInterval 令牌生成時(shí)間間隔(如1)
* @param unit 時(shí)間單位(如SECONDS)
* @param capacity 令牌桶最大容量(突發(fā)流量上限,如200)
* @return true=獲取令牌成功,false=失敗
*/
public boolean rateLimit(String target, int rate, int rateInterval, RateIntervalUnit unit, int capacity) {
try {
String key = RATE_LIMIT_PREFIX + target;
// 獲取令牌桶實(shí)例(全局唯一,Redisson自動(dòng)緩存)
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 初始化令牌桶規(guī)則(僅首次執(zhí)行有效,后續(xù)修改需調(diào)用setRate)
rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, unit, capacity);
// 獲取1個(gè)令牌,無超時(shí)等待
return rateLimiter.tryAcquire(1);
} catch (Exception e) {
log.error("Redisson令牌桶限流異常,目標(biāo):{}", target, e);
return false;
}
}
/**
* 令牌桶限流(帶超時(shí)等待,適合高并發(fā)突發(fā)場(chǎng)景)
* @param target 限流目標(biāo)
* @param rate 生成速率
* @param rateInterval 時(shí)間間隔
* @param unit 時(shí)間單位
* @param capacity 桶容量
* @param waitTime 超時(shí)等待時(shí)間
* @param waitUnit 等待時(shí)間單位
* @return true=成功,false=失敗
*/
public boolean rateLimitWithWait(String target, int rate, int rateInterval, RateIntervalUnit unit,
int capacity, long waitTime, TimeUnit waitUnit) {
try {
String key = RATE_LIMIT_PREFIX + target;
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, unit, capacity);
return rateLimiter.tryAcquire(1, waitTime, waitUnit);
} catch (Exception e) {
log.error("Redisson令牌桶限流(帶等待)異常,目標(biāo):{}", target, e);
return false;
}
}
/**
* 信號(hào)量限流(獲取1個(gè)許可,控制并發(fā)數(shù))
* @param target 限流目標(biāo)(如resource/mysql、resource/kafka)
* @param permits 最大許可數(shù)(最大并發(fā)數(shù))
* @return true=成功,false=失敗
*/
public boolean semaphoreLimit(String target, int permits) {
try {
String key = SEMAPHORE_PREFIX + target;
RSemaphore semaphore = redissonClient.getSemaphore(key);
// 初始化許可數(shù)(僅首次有效)
if (!semaphore.isExists()) {
semaphore.trySetPermits(permits);
}
// 獲取1個(gè)許可,無超時(shí)
return semaphore.tryAcquire(1);
} catch (Exception e) {
log.error("Redisson信號(hào)量限流異常,目標(biāo):{}", target, e);
return false;
}
}
/**
* 信號(hào)量限流(帶超時(shí),釋放許可必須在finally中執(zhí)行)
* @param target 限流目標(biāo)
* @param permits 最大并發(fā)數(shù)
* @param waitTime 超時(shí)時(shí)間
* @param unit 時(shí)間單位
* @return RSemaphore實(shí)例(用于釋放許可),null=獲取失敗
*/
public RSemaphore semaphoreLimitWithWait(String target, int permits, long waitTime, TimeUnit unit) {
try {
String key = SEMAPHORE_PREFIX + target;
RSemaphore semaphore = redissonClient.getSemaphore(key);
if (!semaphore.isExists()) {
semaphore.trySetPermits(permits);
}
if (semaphore.tryAcquire(1, waitTime, unit)) {
return semaphore;
}
} catch (Exception e) {
log.error("Redisson信號(hào)量限流(帶等待)異常,目標(biāo):{}", target, e);
}
return null;
}
/**
* 釋放信號(hào)量許可(必須調(diào)用,避免資源泄漏)
*/
public void releaseSemaphore(RSemaphore semaphore) {
if (semaphore != null && semaphore.isAcquired()) {
semaphore.release(1);
}
}
}
2. 核心場(chǎng)景調(diào)用示例
(1)令牌桶限流(秒殺場(chǎng)景,支持突發(fā)流量)
@RestController
@RequestMapping("/api/seckill")
public class SeckillController {
@Resource
private RedissonLimitUtil redissonLimitUtil;
@Resource
private LimitDegradeUtil limitDegradeUtil;
@PostMapping("/buy/{skuId}")
public Result seckill(@PathVariable String skuId) {
String target = "seckill/sku" + skuId;
// 限流規(guī)則:每秒生成100個(gè)令牌,桶容量200(支持200的突發(fā)流量)
boolean acquire = redissonLimitUtil.rateLimitWithWait(
target, 100, 1, RateIntervalUnit.SECONDS, 200,
500, TimeUnit.MILLISECONDS // 超時(shí)等待500ms
);
if (!acquire) {
if (!limitDegradeUtil.localLimitFallback(target)) {
return Result.fail(429, "秒殺太火爆了,請(qǐng)稍后再試");
}
}
// 執(zhí)行秒殺業(yè)務(wù)
return Result.success("秒殺成功");
}
}
(2)信號(hào)量限流(數(shù)據(jù)庫資源保護(hù),控制并發(fā)數(shù))
@Service
public class OrderQueryService {
@Resource
private RedissonLimitUtil redissonLimitUtil;
@Resource
private LimitDegradeUtil limitDegradeUtil;
public List<Order> queryOrderByUserId(Long userId) {
String target = "resource/mysql/orderQuery";
// 限流規(guī)則:最大并發(fā)數(shù)20(避免數(shù)據(jù)庫連接池打滿)
RSemaphore semaphore = redissonLimitUtil.semaphoreLimitWithWait(
target, 20, 1, TimeUnit.SECONDS
);
if (semaphore == null) {
if (!limitDegradeUtil.localLimitFallback(target)) {
throw new BusinessException(429, "當(dāng)前查詢?nèi)藬?shù)過多,請(qǐng)稍后再試");
}
}
// 必須在finally中釋放許可,避免資源泄漏
try {
// 執(zhí)行數(shù)據(jù)庫查詢
return orderMapper.selectByUserId(userId);
} finally {
redissonLimitUtil.releaseSemaphore(semaphore);
}
}
}
四、Spring Cloud Gateway 網(wǎng)關(guān)整合限流模板
網(wǎng)關(guān)是分布式限流的最佳入口,實(shí)現(xiàn)全鏈路統(tǒng)一限流,避免限流邏輯散落在各個(gè)微服務(wù),這里整合Redisson 令牌桶限流(網(wǎng)關(guān)主流方案),支持路由級(jí) / 全局級(jí)限流、Nacos 動(dòng)態(tài)規(guī)則、多維度提取(接口 / IP / 用戶),返回標(biāo)準(zhǔn) HTTP 429 響應(yīng)。
1. 網(wǎng)關(guān)限流規(guī)則配置(Nacosredis-limit-rules.yml,支持熱更新)
# 網(wǎng)關(guān)限流規(guī)則(key=路由ID,與gateway路由配置一致)
gateway:
limit:
rules:
order-service: # 訂單服務(wù)路由ID
enable: true # 是否開啟限流
target: gateway/order-service # 限流目標(biāo)
rate: 200 # 令牌生成速率(QPS)
rateInterval: 1 # 時(shí)間間隔(秒)
capacity: 400 # 令牌桶容量
waitTime: 500 # 超時(shí)等待時(shí)間(毫秒)
seckill-service: # 秒殺服務(wù)路由ID
enable: true
target: gateway/seckill-service
rate: 500
rateInterval: 1
capacity: 1000
waitTime: 300
default: # 全局默認(rèn)規(guī)則
enable: true
target: gateway/default
rate: 1000
rateInterval: 1
capacity: 2000
waitTime: 200
2. 限流規(guī)則實(shí)體類GatewayLimitRule.java
映射 Nacos 配置,支持配置熱更新:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 網(wǎng)關(guān)限流規(guī)則配置(Nacos熱更新)
*/
@Data
@Component
@RefreshScope // 開啟配置熱更新
@ConfigurationProperties(prefix = "gateway.limit")
public class GatewayLimitRule {
// key=路由ID,value=具體規(guī)則
private Map<String, GatewayLimitItem> rules;
@Data
public static class GatewayLimitItem {
private boolean enable; // 是否開啟限流
private String target; // 限流目標(biāo)
private int rate; // 令牌速率
private int rateInterval; // 時(shí)間間隔
private int capacity; // 桶容量
private long waitTime; // 超時(shí)等待時(shí)間(毫秒)
}
}
3. 網(wǎng)關(guān)全局限流過濾器GatewayLimitGlobalFilter.java
實(shí)現(xiàn)GlobalFilter和Ordered,攔截所有網(wǎng)關(guān)請(qǐng)求,按路由 ID 匹配限流規(guī)則,原子執(zhí)行限流判斷,限流失敗直接返回 429 響應(yīng):
import cn.hutool.core.util.StrUtil;
import org.redisson.api.RateIntervalUnit;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.Objects;
/**
* Spring Cloud Gateway全局限流過濾器(Redisson令牌桶)
* 優(yōu)先級(jí)最高,最先執(zhí)行
*/
@Component
public class GatewayLimitGlobalFilter implements GlobalFilter, Ordered {
@Resource
private RedissonLimitUtil redissonLimitUtil;
@Resource
private GatewayLimitRule gatewayLimitRule;
@Resource
private LimitDegradeUtil limitDegradeUtil;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 1. 獲取當(dāng)前路由ID(gateway路由配置的ID)
String routeId = exchange.getAttribute("org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ID");
if (StrUtil.isBlank(routeId)) {
routeId = "default"; // 無路由ID,使用默認(rèn)規(guī)則
}
// 2. 匹配限流規(guī)則
GatewayLimitRule.GatewayLimitItem limitItem = gatewayLimitRule.getRules().get(routeId);
if (Objects.isNull(limitItem) || !limitItem.isEnable()) {
return chain.filter(exchange); // 未開啟限流,直接放行
}
// 3. 執(zhí)行令牌桶限流
boolean acquire = redissonLimitUtil.rateLimitWithWait(
limitItem.getTarget(),
limitItem.getRate(),
limitItem.getRateInterval(),
RateIntervalUnit.SECONDS,
limitItem.getCapacity(),
limitItem.getWaitTime(),
java.util.concurrent.TimeUnit.MILLISECONDS
);
// 4. 限流判斷+降級(jí)
if (!acquire) {
// 觸發(fā)本地降級(jí),失敗則返回429
if (!limitDegradeUtil.localLimitFallback(limitItem.getTarget())) {
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String errorMsg = "{"code":429,"msg":"網(wǎng)關(guān)限流:請(qǐng)求過于頻繁"}";
return response.writeWith(Mono.just(response.bufferFactory().wrap(errorMsg.getBytes())));
}
}
// 5. 放行,執(zhí)行后續(xù)過濾器
return chain.filter(exchange);
}
/**
* 過濾器優(yōu)先級(jí):最高(Ordered.HIGHEST_PRECEDENCE)
*/
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
4. 網(wǎng)關(guān)路由配置(application.yml)
關(guān)聯(lián)限流規(guī)則的路由 ID,生產(chǎn)建議配合 Nacos 動(dòng)態(tài)路由:
spring:
cloud:
gateway:
routes:
# 訂單服務(wù)路由(ID=order-service,與限流規(guī)則一致)
- id: order-service
uri: lb://order-service # 負(fù)載均衡到訂單服務(wù)
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1 # 去除/api前綴
# 秒殺服務(wù)路由(ID=seckill-service,與限流規(guī)則一致)
- id: seckill-service
uri: lb://seckill-service
predicates:
- Path=/api/seckill/**
filters:
- StripPrefix=1
# 全局默認(rèn)路由
- id: default
uri: lb://common-service
predicates:
- Path=/**
五、生產(chǎn)環(huán)境降級(jí)策略模板(Redis 宕機(jī)兜底 + 限流降級(jí))
限流的核心降級(jí)目標(biāo):Redis 宕機(jī) / 超時(shí) / 壓力過大時(shí),不影響核心業(yè)務(wù)可用,通過本地內(nèi)存限流(Guava RateLimiter) 做兜底,同時(shí)結(jié)合Sentinel 熔斷限制降級(jí)后的請(qǐng)求量,避免本地限流被擊穿。
1. 降級(jí)核心工具類LimitDegradeUtil.java
封裝本地令牌桶兜底、限流指標(biāo)統(tǒng)計(jì),支持按目標(biāo)單獨(dú)配置本地規(guī)則,避免全局本地限流被擊穿:
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 限流降級(jí)工具類(Redis異常時(shí)本地兜底)
* 基于Guava RateLimiter實(shí)現(xiàn)本地令牌桶限流
*/
@Component
public class LimitDegradeUtil {
// 本地限流實(shí)例緩存:key=限流目標(biāo),value=Guava RateLimiter
private final Map<String, RateLimiter> localRateLimiterMap = new ConcurrentHashMap<>();
// 本地默認(rèn)QPS(可從Nacos動(dòng)態(tài)配置)
private static final double DEFAULT_LOCAL_QPS = 50.0;
/**
* 初始化核心目標(biāo)的本地限流規(guī)則(生產(chǎn)建議從Nacos加載)
*/
@PostConstruct
public void initLocalLimit() {
// 秒殺服務(wù)本地兜底QPS=10
localRateLimiterMap.put("seckill/sku1001", RateLimiter.create(10.0));
// 訂單服務(wù)本地兜底QPS=20
localRateLimiterMap.put("order/create", RateLimiter.create(20.0));
// 網(wǎng)關(guān)全局本地兜底QPS=100
localRateLimiterMap.put("gateway/default", RateLimiter.create(100.0));
}
/**
* 本地限流兜底方法
* @param target 限流目標(biāo)
* @return true=獲取本地令牌成功,false=失敗
*/
public boolean localLimitFallback(String target) {
try {
// 從緩存獲取本地限流實(shí)例,無則創(chuàng)建默認(rèn)實(shí)例
RateLimiter rateLimiter = localRateLimiterMap.getOrDefault(target, RateLimiter.create(DEFAULT_LOCAL_QPS));
// 嘗試獲取1個(gè)本地令牌(無超時(shí))
return rateLimiter.tryAcquire();
} catch (Exception e) {
log.error("本地限流兜底異常,目標(biāo):{}", target, e);
return false;
}
}
/**
* 動(dòng)態(tài)更新本地限流規(guī)則(支持Nacos熱更新調(diào)用)
* @param target 限流目標(biāo)
* @param qps 目標(biāo)QPS
*/
public void updateLocalLimit(String target, double qps) {
localRateLimiterMap.put(target, RateLimiter.create(qps));
log.info("本地限流規(guī)則更新成功,目標(biāo):{},QPS:{}", target, qps);
}
}
2. Redis 異常熔斷降級(jí)(Sentinel 整合)
通過Sentinel對(duì) Redis 操作做熔斷,當(dāng) Redis 異常率達(dá)到閾值時(shí),直接觸發(fā)本地降級(jí),避免 Redis 異常拖垮整個(gè)服務(wù),添加SentinelConfig.java:
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* Sentinel熔斷配置(Redis操作熔斷)
*/
@Configuration
public class SentinelConfig {
/**
* 開啟Sentinel注解支持
*/
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
/**
* 初始化Redis操作熔斷規(guī)則
* 策略:異常比例熔斷,5秒內(nèi)異常率>50%,熔斷10秒
*/
@PostConstruct
public void initDegradeRules() {
List<DegradeRule> rules = new ArrayList<>();
// Redis Lua腳本限流熔斷規(guī)則
DegradeRule luaRule = new DegradeRule();
luaRule.setResource("redisLimitLua"); // 資源名,與@SentinelResource一致
luaRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); // 異常比例
luaRule.setCount(0.5); // 異常比例閾值50%
luaRule.setTimeWindow(10); // 熔斷時(shí)間10秒
luaRule.setMinRequestAmount(10); // 最小請(qǐng)求數(shù):5秒內(nèi)至少10次請(qǐng)求才觸發(fā)
// Redisson限流熔斷規(guī)則
DegradeRule redissonRule = new DegradeRule();
redissonRule.setResource("redissonLimit");
redissonRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
redissonRule.setCount(0.5);
redissonRule.setTimeWindow(10);
redissonRule.setMinRequestAmount(10);
rules.add(luaRule);
rules.add(redissonRule);
DegradeRuleManager.loadRules(rules);
}
}
3. 熔斷注解使用(修改限流工具類)
在 Lua/Redisson 限流工具類的核心方法添加@SentinelResource,指定熔斷降級(jí)方法:
// 在RedisLimitLuaUtil的fixedWindowLimit方法添加
@SentinelResource(value = "redisLimitLua", fallback = "fixedWindowFallback")
public boolean fixedWindowLimit(String limitDimension, String target, int limit, int expire) {
// 原有邏輯
}
// 熔斷降級(jí)方法(參數(shù)與原方法一致)
public boolean fixedWindowFallback(String limitDimension, String target, int limit, int expire) {
log.warn("Redis Lua限流觸發(fā)Sentinel熔斷,觸發(fā)本地降級(jí),目標(biāo):{}", target);
return false; // 返回false觸發(fā)本地兜底
}
// 在RedissonLimitUtil的rateLimit方法添加
@SentinelResource(value = "redissonLimit", fallback = "rateLimitFallback")
public boolean rateLimit(String target, int rate, int rateInterval, RateIntervalUnit unit, int capacity) {
// 原有邏輯
}
// 熔斷降級(jí)方法
public boolean rateLimitFallback(String target, int rate, int rateInterval, RateIntervalUnit unit, int capacity) {
log.warn("Redisson令牌桶限流觸發(fā)Sentinel熔斷,觸發(fā)本地降級(jí),目標(biāo):{}", target);
return false;
}
六、生產(chǎn)環(huán)境通用配置 & 監(jiān)控埋點(diǎn)
1. Redis 序列化優(yōu)化(避免默認(rèn) JDK 序列化問題)
創(chuàng)建RedisConfig.java,替換為Jackson2JsonRedisSerializer,節(jié)省內(nèi)存且避免序列化兼容問題:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// JSON序列化器
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(om);
// String序列化器
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// key采用String序列化
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value采用JSON序列化
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
2. 限流監(jiān)控埋點(diǎn)(Micrometer+Prometheus)
添加監(jiān)控指標(biāo),統(tǒng)計(jì)限流次數(shù)、Redis 異常次數(shù)、本地降級(jí)次數(shù),方便 Grafana 可視化和告警:
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* 限流監(jiān)控指標(biāo)工具類
*/
@Component
public class LimitMonitorUtil {
@Resource
private MeterRegistry meterRegistry;
// 限流拒絕次數(shù)
private Counter limitRejectCounter;
// Redis異常次數(shù)
private Counter redisErrorCounter;
// 本地降級(jí)次數(shù)
private Counter localDegradeCounter;
@PostConstruct
public void initCounter() {
limitRejectCounter = Counter.builder("redis.limit.reject.count")
.description("Redis分布式限流拒絕請(qǐng)求次數(shù)")
.register(meterRegistry);
redisErrorCounter = Counter.builder("redis.limit.error.count")
.description("Redis分布式限流Redis操作異常次數(shù)")
.register(meterRegistry);
localDegradeCounter = Counter.builder("redis.limit.local.degrade.count")
.description("Redis分布式限流本地降級(jí)觸發(fā)次數(shù)")
.register(meterRegistry);
}
// 記錄限流拒絕
public void recordLimitReject() {
limitRejectCounter.increment();
}
// 記錄Redis異常
public void recordRedisError() {
redisErrorCounter.increment();
}
// 記錄本地降級(jí)
public void recordLocalDegrade() {
localDegradeCounter.increment();
}
}
使用示例:在限流判斷失敗處調(diào)用監(jiān)控方法:
if (!apiLimit || !ipLimit) {
limitMonitorUtil.recordLimitReject(); // 記錄限流拒絕
if (!limitDegradeUtil.localLimitFallback(apiTarget)) {
limitMonitorUtil.recordLocalDegrade(); // 記錄本地降級(jí)
return Result.fail(429, "請(qǐng)求過于頻繁");
}
}
七、生產(chǎn)環(huán)境落地關(guān)鍵注意事項(xiàng)
- Redis 高可用:必須使用Redis 主從 + 哨兵或Redis Cluster,開啟RDB+AOF 混合持久化,避免 Redis 單點(diǎn)故障導(dǎo)致限流失效;
- 限流 key 設(shè)計(jì):遵循
前綴:維度:目標(biāo)規(guī)范,避免鍵沖突,同時(shí)設(shè)置過期時(shí)間,防止 Redis 內(nèi)存泄漏; - 避免熱點(diǎn) key:對(duì)高并發(fā)限流 key(如秒殺接口)采用分段限流(如按 IP 哈希到不同 key:
limit:seckill:sku1001:{hash(ip)%10}),分擔(dān) Redis 單節(jié)點(diǎn)壓力; - 動(dòng)態(tài)配置:所有限流規(guī)則(閾值、窗口、QPS)均從Nacos/Apollo加載,支持熱更新,無需重啟服務(wù);
- 異常隔離:Redis 操作添加超時(shí)時(shí)間(生產(chǎn)建議 200-500ms),避免 Redis 阻塞導(dǎo)致服務(wù)線程池耗盡;
- 降級(jí)兜底:本地限流的 QPS 必須遠(yuǎn)低于分布式限流,避免本地兜底被擊穿,同時(shí)結(jié)合 Sentinel 做熔斷;
- 監(jiān)控告警:對(duì)
redis.limit.reject.count、redis.limit.error.count設(shè)置告警閾值,及時(shí)發(fā)現(xiàn)限流規(guī)則不合理或 Redis 故障; - 壓測(cè)驗(yàn)證:上線前通過 JMeter/Gatling 做壓測(cè),驗(yàn)證限流規(guī)則的有效性和 Redis 的性能瓶頸。
總結(jié)
該模板是 Redis 分布式限流的生產(chǎn)級(jí)開箱即用方案,核心亮點(diǎn):
- 雙核心限流:Lua 腳本(輕量原子)+ Redisson(開箱即用),覆蓋 99% 生產(chǎn)場(chǎng)景;
- 網(wǎng)關(guān)統(tǒng)一限流:Spring Cloud Gateway 整合,實(shí)現(xiàn)全鏈路入口限流,避免邏輯散落;
- 高可用降級(jí):Redis 宕機(jī) / 異常時(shí)自動(dòng)觸發(fā) Guava 本地兜底,結(jié)合 Sentinel 熔斷,保證服務(wù)可用;
- 可配置可監(jiān)控:Nacos 動(dòng)態(tài)配置規(guī)則,Micrometer 埋點(diǎn)監(jiān)控,支持告警和可視化;
- 規(guī)范可擴(kuò)展:統(tǒng)一的 key 設(shè)計(jì)、工具類封裝,支持多維度限流(接口 / IP / 用戶 / 租戶)和自定義擴(kuò)展。
使用時(shí)僅需根據(jù)自身業(yè)務(wù)調(diào)整限流規(guī)則、降級(jí) QPS和Redis 部署方式,即可快速落地到生產(chǎn)環(huán)境。
到此這篇關(guān)于Redis分布式限流生產(chǎn)環(huán)境落地方案的文章就介紹到這了,更多相關(guān)Redis 分布式限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Redis實(shí)現(xiàn)分布式限流的幾種方法
- redis+lua實(shí)現(xiàn)分布式限流的示例
- 詳解Redisson分布式限流的使用及原理
- Redisson分布式限流的實(shí)現(xiàn)原理分析
- Redisson分布式限流器RRateLimiter的使用及原理小結(jié)
- Redis分布式限流的幾種實(shí)現(xiàn)
- Redisson分布式限流的實(shí)現(xiàn)原理解析
- redisson分布式限流RRateLimiter源碼解析
- Redis分布式限流組件設(shè)計(jì)與使用實(shí)例
- 基于Redis+Lua腳本實(shí)現(xiàn)分布式限流組件封裝的方法
- Redis和Lua實(shí)現(xiàn)分布式限流器的方法詳解
相關(guān)文章
Redis的數(shù)據(jù)淘汰策略使用及注意事項(xiàng)
Redis的數(shù)據(jù)淘汰策略是指在內(nèi)存不足時(shí),如何決定哪些數(shù)據(jù)需要被淘汰以釋放內(nèi)存空間,Redis提供了多種數(shù)據(jù)淘汰策略,如noeviction、allkeys-lru、allkeys-lfu、volatile-lru、volatile-lfu、volatile-random、allkeys-random和volatile-ttl2025-12-12
redis lettuce連接池經(jīng)常出現(xiàn)連接拒絕(Connection refused)問題解決
本文主要介紹了在Windows 10/11系統(tǒng)中使用Spring Boot和Lettuce連接池訪問Redis時(shí),遇到的連接拒絕問題,下面就來介紹一下解決方法,感興趣的可以了解一下2025-03-03
Redis延遲隊(duì)列和分布式延遲隊(duì)列的簡(jiǎn)答實(shí)現(xiàn)
在我們的工作中,很多地方使用延遲隊(duì)列,比如訂單到期沒有付款取消訂單,制訂一個(gè)提醒的任務(wù)等都需要延遲隊(duì)列,那么我們需要實(shí)現(xiàn)延遲隊(duì)列,本文就來介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下2021-05-05
使用RedisAtomicInteger計(jì)數(shù)出現(xiàn)少計(jì)問題及解決
這篇文章主要介紹了使用RedisAtomicInteger計(jì)數(shù)出現(xiàn)少計(jì)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
redis實(shí)現(xiàn)刪除list中特定索引的值
這篇文章主要介紹了redis實(shí)現(xiàn)刪除list中特定索引的值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
使用 Redis 流實(shí)現(xiàn)消息隊(duì)列的代碼
這篇文章主要介紹了使用 Redis 流實(shí)現(xiàn)消息隊(duì)列,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11

