游乐游手机版
首页/数据库/文章详情

Redis分布式限流在生产环境中的落地实战方案

时间:2026-06-14 07:05
在生产环境中落地分布式限流,从来都不是搭个计数器那么简单。方案不仅要扛得住突发流量,还要兼顾高可用和动态调整的能力。不少人问起过,有没有一套拿来就能用的标准模版?今天就给各位一个完整的实战方案。 这套方案适配Spring Boot 2 x 3 x + Spring Cloud微服务生态,覆盖Lua脚

在生产环境中落地分布式限流,从来都不是搭个计数器那么简单。方案不仅要扛得住突发流量,还要兼顾高可用和动态调整的能力。不少人问起过,有没有一套拿来就能用的标准模版?今天就给各位一个完整的实战方案。

这套方案适配Spring Boot 2.x/3.x + Spring Cloud微服务生态,覆盖Lua脚本原子限流(固定窗口/滑动窗口)、Redisson令牌桶/信号量限流Spring Cloud Gateway网关统一限流Redis宕机兜底降级四大核心能力。兼顾原子性、动态配置、高可用、可监控,直接复制到生产环境,按需微调即可。

Redis分布式限流生产环境落地方案

前置依赖

生产环境统一引入以下核心依赖(Ma ven),版本适配自身Spring Cloud/Spring Boot版本:



    org.springframework.boot
    spring-boot-starter-data-redis



    org.redisson
    redisson-spring-boot-starter
    3.23.3



    org.springframework.cloud
    spring-cloud-starter-gateway



    com.alibaba.cloud
    spring-cloud-starter-alibaba-nacos-config



    com.alibaba.cloud
    spring-cloud-starter-alibaba-sentinel



    cn.hutool
    hutool-all
    5.8.23



    com.google.gua va
    gua va
    32.1.3-jre

一、基础环境配置

1. Redis连接配置(application.yml)

包含连接池、序列化优化,适配单机/主从/集群:

spring:
  redis:
    host: 192.168.1.100 # 生产替换为Redis集群地址
    port: 6379
    password: prod_redis_123
    database: 2 # 限流专用库,与业务隔离
    lettuce:
      pool:
        max-active: 50 # 最大连接数,按QPS调整
        max-idle: 20
        min-idle: 5
        max-wait: 3000ms # 连接超时,生产建议3s内
    timeout: 2000ms # Redis操作超时
  # Nacos配置(动态限流规则)
  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 # 限流规则统一配置文件
            group: DEFAULT_GROUP
            refresh: true # 支持热更新

2. Redisson配置(生产高可用版)

支持单机/主从/哨兵/集群各模式。这里以Redis Cluster为例(生产推荐),创建RedissonConfig.ja va

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();
        // 集群模式配置(生产核心)
        config.useClusterServers()
                .addNodeAddress(clusterNodes.split(","))
                .setPassword(password)
                .setDatabase(database)
                .setScanInterval(2000) // 节点扫描间隔
                .setMasterConnectionPoolSize(50) // 主节点连接池
                .setSla veConnectionPoolSize(20) // 从节点连接池
                .setReadMode(ReadMode.SLA VE) // 读从节点,分担压力
                .setSubscriptionMode(SubscriptionMode.SLA VE); // 订阅从节点

        // 超时配置,避免Redis阻塞
        config.setConnectTimeout(2000);
        config.setTimeout(2000);
        return Redisson.create(config);
    }
}

Nacos补充配置:在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脚本原子限流模板(固定窗口 + 滑动窗口)

依托Redis单线程原子执行Lua脚本,解决计数限流的并发问题。分为固定窗口(简单高效,生产80%场景适用)和滑动窗口(解决固定窗口临界超量问题,高精度场景适用)。封装通用调用工具类,支持接口/IP/用户多维度限流。

1. 核心Lua脚本(放在resources/lua目录下,生产建议统一管理)

(1)固定窗口计数限流limit_fixed_window.lua

核心逻辑:计数+过期时间原子设置,达到阈值返回0(拒绝),否则返回1(允许)。支持自定义窗口时长和阈值。

-- 固定窗口限流Lua脚本(原子性)
-- KEYS[1]:限流key(如limit:api:order:192.168.1.1)
-- ARGV[1]:限流阈值(如100)
-- ARGV[2]:窗口过期时间(秒,如60)

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])

-- 原子计数
local current = redis.call('incr', key)

-- 首次计数,设置过期时间(避免内存泄漏)
if current == 1 then
    redis.call('expire', key, expire)
end

-- 超过阈值返回0,否则返回1
if current > limit then
    return 0
else
    return 1
end

(2)滑动窗口计数限流limit_slide_window.lua

核心逻辑:基于ZSet存储请求时间戳,自动清理过期请求,统计窗口内总请求数。解决了固定窗口临界超量问题——比如1分钟窗口,59秒和1秒各请求100次,固定窗口会允许200次,滑动窗口只会允许100次。

-- 滑动窗口限流Lua脚本(原子性)
-- KEYS[1]:限流key(如limit:api:seckill:192.168.1.1)
-- ARGV[1]:限流阈值(如100)
-- ARGV[2]:窗口时长(毫秒,如60000)
-- ARGV[3]:当前请求时间戳(毫秒,如System.currentTimeMillis())

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local windowMs = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- 窗口开始时间:当前时间 - 窗口时长
local windowStart = now - windowMs

-- 1. 移除ZSet中过期的请求记录(score < 窗口开始时间)
redis.call('zremrangebyscore', key, 0, windowStart)

-- 2. 统计当前窗口内的请求数
local current = redis.call('zcard', key)

-- 3. 超过阈值,返回0拒绝
if current >= limit then
    return 0
end

-- 4. 未超阈值,添加当前请求到ZSet(score=时间戳,value=唯一标识避免重复)
redis.call('zadd', key, now, now .. math.random(10000))

-- 5. 设置ZSet过期时间,避免内存泄漏(窗口时长+1秒)
redis.call('expire', key, math.ceil(windowMs / 1000) + 1)

return 1

2. Lua脚本通用调用工具类RedisLimitLuaUtil.ja va

封装脚本加载、参数组装、Redis调用逻辑。全局唯一,避免重复加载脚本,支持多维度限流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 ja vax.annotation.PostConstruct;
import ja vax.annotation.Resource;
import ja va.util.Collections;
import ja va.util.Objects;

/**
 * Redis Lua脚本限流通用工具类(固定窗口+滑动窗口)
 */
@Component
public class RedisLimitLuaUtil {

    @Resource
    private RedisTemplate redisTemplate;

    // 固定窗口脚本
    private DefaultRedisScript fixedWindowScript;
    // 滑动窗口脚本
    private DefaultRedisScript slideWindowScript;

    // 限流key前缀,统一规范
    private static final String LIMIT_KEY_PREFIX = "limit:";

    /**
     * 初始化Lua脚本(项目启动时加载,避免重复加载)
     */
    @PostConstruct
    public void initScript() {
        // 固定窗口脚本初始化
        fixedWindowScript = new DefaultRedisScript<>();
        fixedWindowScript.setResultType(Long.class);
        fixedWindowScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit_fixed_window.lua")));

        // 滑动窗口脚本初始化
        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 限流目标(如接口名order/create、IP192.168.1.1、用户ID1001)
     * @param limit 限流阈值
     * @param expire 窗口过期时间(秒)
     * @return true=允许,false=拒绝
     */
    public boolean fixedWindowLimit(String limitDimension, String target, int limit, int expire) {
        try {
            String key = generateLimitKey(limitDimension, target);
            // 执行Lua脚本,原子判断
            Long result = redisTemplate.execute(
                    fixedWindowScript,
                    Collections.singletonList(key),
                    limit,
                    expire
            );
            // 结果为1则允许,0则拒绝
            return Objects.nonNull(result) && result == 1;
        } catch (Exception e) {
            // Redis异常时,返回false触发降级策略
            log.error("固定窗口限流Redis执行异常,维度:{},目标:{}", limitDimension, target, e);
            return false;
        }
    }

    /**
     * 滑动窗口限流判断
     * @param limitDimension 限流维度(api/ip/user)
     * @param target 限流目标
     * @param limit 限流阈值
     * @param windowMs 窗口时长(毫秒)
     * @return true=允许,false=拒绝
     */
    public boolean slideWindowLimit(String limitDimension, String target, int limit, long windowMs) {
        try {
            String key = generateLimitKey(limitDimension, target);
            long now = System.currentTimeMillis();

            // 执行Lua脚本,原子判断
            Long result = redisTemplate.execute(
                    slideWindowScript,
                    Collections.singletonList(key),
                    limit,
                    windowMs,
                    now
            );
            return Objects.nonNull(result) && result == 1;
        } catch (Exception e) {
            log.error("滑动窗口限流Redis执行异常,维度:{},目标:{}", limitDimension, target, e);
            return false;
        }
    }

    /**
     * 生成限流key:limit:维度:目标
     * 例: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. 业务层调用示例(Controller/Service)

直接注入工具类,按需选择固定/滑动窗口,一行代码实现限流

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import ja vax.annotation.Resource;
import ja vax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/api/order")
public class OrderController {

    @Resource
    private RedisLimitLuaUtil redisLimitLuaUtil;
    @Resource
    private LimitDegradeUtil limitDegradeUtil; // 降级工具类(后续实现)

    @PostMapping("/create")
    public Result createOrder(HttpServletRequest request) {
        // 1. 提取限流目标(接口+IP双维度,生产常用)
        String apiTarget = "order/create";
        String ipTarget = request.getRemoteAddr();

        // 2. 限流规则:接口每分钟1000次,IP每分钟100次
        boolean apiLimit = redisLimitLuaUtil.fixedWindowLimit("api", apiTarget, 1000, 60);
        boolean ipLimit = redisLimitLuaUtil.fixedWindowLimit("ip", ipTarget, 100, 60);

        // 3. 限流判断+降级(Redis异常时触发本地降级)
        if (!apiLimit || !ipLimit) {
            // 触发降级:先尝试本地兜底,失败则返回限流提示
            if (!limitDegradeUtil.localLimitFallback(apiTarget)) {
                return Result.fail(429, "请求过于频繁,请稍后再试");
            }
        }

        // 4. 执行业务逻辑
        return Result.success("订单创建成功");
    }
}

三、Redisson限流核心模板(令牌桶 + 信号量)

Redisson底层基于Lua脚本实现原子性,开箱即用支持令牌桶限流(应对突发流量,如秒杀/网关)和信号量限流(控制并发数,如数据库/中间件资源访问)。封装通用工具类,支持全局唯一令牌池动态规则调整

1. Redisson限流通用工具类RedissonLimitUtil.ja va

封装令牌桶(RateLimiter)和信号量(Semaphore)核心方法。生产建议单例,避免重复创建Redisson对象:

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 ja vax.annotation.Resource;
import ja va.util.concurrent.TimeUnit;

/**
 * Redisson限流工具类(令牌桶+信号量)
 * 令牌桶:控制QPS,支持突发流量
 * 信号量:控制并发数,保护下游资源
 */
@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个令牌,无超时)
     * @param target 限流目标(如seckill/sku1001、gateway/api)
     * @param rate 令牌生成速率(如100)
     * @param rateInterval 令牌生成时间间隔(如1)
     * @param unit 时间单位(如SECONDS)
     * @param capacity 令牌桶最大容量(突发流量上限,如200)
     * @return true=获取令牌成功,false=失败
     */
    public boolean rateLimit(String target, int rate, int rateInterval, RateIntervalUnit unit, int capacity) {
        try {
            String key = RATE_LIMIT_PREFIX + target;
            // 获取令牌桶实例(全局唯一,Redisson自动缓存)
            RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
            // 初始化令牌桶规则(仅首次执行有效,后续修改需调用setRate)
            rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, unit, capacity);
            // 获取1个令牌,无超时等待
            return rateLimiter.tryAcquire(1);
        } catch (Exception e) {
            log.error("Redisson令牌桶限流异常,目标:{}", target, e);
            return false;
        }
    }

    /**
     * 令牌桶限流(带超时等待,适合高并发突发场景)
     * @param target 限流目标
     * @param rate 生成速率
     * @param rateInterval 时间间隔
     * @param unit 时间单位
     * @param capacity 桶容量
     * @param waitTime 超时等待时间
     * @param waitUnit 等待时间单位
     * @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令牌桶限流(带等待)异常,目标:{}", target, e);
            return false;
        }
    }

    /**
     * 信号量限流(获取1个许可,控制并发数)
     * @param target 限流目标(如resource/mysql、resource/kafka)
     * @param permits 最大许可数(最大并发数)
     * @return true=成功,false=失败
     */
    public boolean semaphoreLimit(String target, int permits) {
        try {
            String key = SEMAPHORE_PREFIX + target;
            RSemaphore semaphore = redissonClient.getSemaphore(key);
            // 初始化许可数(仅首次有效)
            if (!semaphore.isExists()) {
                semaphore.trySetPermits(permits);
            }
            // 获取1个许可,无超时
            return semaphore.tryAcquire(1);
        } catch (Exception e) {
            log.error("Redisson信号量限流异常,目标:{}", target, e);
            return false;
        }
    }

    /**
     * 信号量限流(带超时,释放许可必须在finally中执行)
     * @param target 限流目标
     * @param permits 最大并发数
     * @param waitTime 超时时间
     * @param unit 时间单位
     * @return RSemaphore实例(用于释放许可),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信号量限流(带等待)异常,目标:{}", target, e);
        }
        return null;
    }

    /**
     * 释放信号量许可(必须调用,避免资源泄漏)
     */
    public void releaseSemaphore(RSemaphore semaphore) {
        if (semaphore != null && semaphore.isAcquired()) {
            semaphore.release(1);
        }
    }
}

2. 核心场景调用示例

(1)令牌桶限流(秒杀场景,支持突发流量)

@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;

        // 限流规则:每秒生成100个令牌,桶容量200(支持200的突发流量)
        boolean acquire = redissonLimitUtil.rateLimitWithWait(
                target, 100, 1, RateIntervalUnit.SECONDS, 200,
                500, TimeUnit.MILLISECONDS // 超时等待500ms
        );

        if (!acquire) {
            if (!limitDegradeUtil.localLimitFallback(target)) {
                return Result.fail(429, "秒杀太火爆了,请稍后再试");
            }
        }

        // 执行秒杀业务
        return Result.success("秒杀成功");
    }
}

(2)信号量限流(数据库资源保护,控制并发数)

@Service
public class OrderQueryService {

    @Resource
    private RedissonLimitUtil redissonLimitUtil;
    @Resource
    private LimitDegradeUtil limitDegradeUtil;

    public List queryOrderByUserId(Long userId) {
        String target = "resource/mysql/orderQuery";

        // 限流规则:最大并发数20(避免数据库连接池打满)
        RSemaphore semaphore = redissonLimitUtil.semaphoreLimitWithWait(
                target, 20, 1, TimeUnit.SECONDS
        );

        if (semaphore == null) {
            if (!limitDegradeUtil.localLimitFallback(target)) {
                throw new BusinessException(429, "当前查询人数过多,请稍后再试");
            }
        }

        // 必须在finally中释放许可,避免资源泄漏
        try {
            // 执行数据库查询
            return orderMapper.selectByUserId(userId);
        } finally {
            redissonLimitUtil.releaseSemaphore(semaphore);
        }
    }
}

四、Spring Cloud Gateway网关整合限流模板

网关是分布式限流的最佳入口,实现全链路统一限流,避免限流逻辑散落在各个微服务。这里整合Redisson令牌桶限流(网关主流方案),支持路由级/全局级限流、Nacos动态规则多维度提取(接口/IP/用户),返回标准HTTP 429响应。

1. 网关限流规则配置(Nacosredis-limit-rules.yml,支持热更新)

# 网关限流规则(key=路由ID,与gateway路由配置一致)
gateway:
  limit:
    rules:
      order-service: # 订单服务路由ID
        enable: true # 是否开启限流
        target: gateway/order-service # 限流目标
        rate: 200 # 令牌生成速率(QPS)
        rateInterval: 1 # 时间间隔(秒)
        capacity: 400 # 令牌桶容量
        waitTime: 500 # 超时等待时间(毫秒)
      seckill-service: # 秒杀服务路由ID
        enable: true
        target: gateway/seckill-service
        rate: 500
        rateInterval: 1
        capacity: 1000
        waitTime: 300
      default: # 全局默认规则
        enable: true
        target: gateway/default
        rate: 1000
        rateInterval: 1
        capacity: 2000
        waitTime: 200

2. 限流规则实体类GatewayLimitRule.ja va

映射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 ja va.util.Map;

/**
 * 网关限流规则配置(Nacos热更新)
 */
@Data
@Component
@RefreshScope // 开启配置热更新
@ConfigurationProperties(prefix = "gateway.limit")
public class GatewayLimitRule {

    // key=路由ID,value=具体规则
    private Map rules;

    @Data
    public static class GatewayLimitItem {
        private boolean enable; // 是否开启限流
        private String target; // 限流目标
        private int rate; // 令牌速率
        private int rateInterval; // 时间间隔
        private int capacity; // 桶容量
        private long waitTime; // 超时等待时间(毫秒)
    }
}

3. 网关全局限流过滤器GatewayLimitGlobalFilter.ja va

实现GlobalFilterOrdered拦截所有网关请求,按路由ID匹配限流规则,原子执行限流判断,限流失败直接返回429响应:

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 ja vax.annotation.Resource;
import ja va.util.Objects;

/**
 * Spring Cloud Gateway全局限流过滤器(Redisson令牌桶)
 * 优先级最高,最先执行
 */
@Component
public class GatewayLimitGlobalFilter implements GlobalFilter, Ordered {

    @Resource
    private RedissonLimitUtil redissonLimitUtil;
    @Resource
    private GatewayLimitRule gatewayLimitRule;
    @Resource
    private LimitDegradeUtil limitDegradeUtil;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 1. 获取当前路由ID(gateway路由配置的ID)
        String routeId = exchange.getAttribute("org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ID");
        if (StrUtil.isBlank(routeId)) {
            routeId = "default"; // 无路由ID,使用默认规则
        }

        // 2. 匹配限流规则
        GatewayLimitRule.GatewayLimitItem limitItem = gatewayLimitRule.getRules().get(routeId);
        if (Objects.isNull(limitItem) || !limitItem.isEnable()) {
            return chain.filter(exchange); // 未开启限流,直接放行
        }

        // 3. 执行令牌桶限流
        boolean acquire = redissonLimitUtil.rateLimitWithWait(
                limitItem.getTarget(),
                limitItem.getRate(),
                limitItem.getRateInterval(),
                RateIntervalUnit.SECONDS,
                limitItem.getCapacity(),
                limitItem.getWaitTime(),
                ja va.util.concurrent.TimeUnit.MILLISECONDS
        );

        // 4. 限流判断+降级
        if (!acquire) {
            // 触发本地降级,失败则返回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\":\"网关限流:请求过于频繁\"}";
                return response.writeWith(Mono.just(response.bufferFactory().wrap(errorMsg.getBytes())));
            }
        }

        // 5. 放行,执行后续过滤器
        return chain.filter(exchange);
    }

    /**
     * 过滤器优先级:最高(Ordered.HIGHEST_PRECEDENCE)
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

4. 网关路由配置(application.yml)

关联限流规则的路由ID,生产建议配合Nacos动态路由:

spring:
  cloud:
    gateway:
      routes:
        # 订单服务路由(ID=order-service,与限流规则一致)
        - id: order-service
          uri: lb://order-service # 负载均衡到订单服务
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1 # 去除/api前缀
        # 秒杀服务路由(ID=seckill-service,与限流规则一致)
        - id: seckill-service
          uri: lb://seckill-service
          predicates:
            - Path=/api/seckill/**
          filters:
            - StripPrefix=1
        # 全局默认路由
        - id: default
          uri: lb://common-service
          predicates:
            - Path=/**

五、生产环境降级策略模板(Redis宕机兜底 + 限流降级)

限流的核心降级目标:Redis宕机/超时/压力过大时,不影响核心业务可用。通过本地内存限流(Gua va RateLimiter)做兜底,同时结合Sentinel熔断限制降级后的请求量,避免本地限流被击穿。

1. 降级核心工具类LimitDegradeUtil.ja va

封装本地令牌桶兜底限流指标统计,支持按目标单独配置本地规则,避免全局本地限流被击穿:

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;

import ja vax.annotation.PostConstruct;
import ja va.util.Map;
import ja va.util.concurrent.ConcurrentHashMap;

/**
 * 限流降级工具类(Redis异常时本地兜底)
 * 基于Gua va RateLimiter实现本地令牌桶限流
 */
@Component
public class LimitDegradeUtil {

    // 本地限流实例缓存:key=限流目标,value=Gua va RateLimiter
    private final Map localRateLimiterMap = new ConcurrentHashMap<>();

    // 本地默认QPS(可从Nacos动态配置)
    private static final double DEFAULT_LOCAL_QPS = 50.0;

    /**
     * 初始化核心目标的本地限流规则(生产建议从Nacos加载)
     */
    @PostConstruct
    public void initLocalLimit() {
        // 秒杀服务本地兜底QPS=10
        localRateLimiterMap.put("seckill/sku1001", RateLimiter.create(10.0));
        // 订单服务本地兜底QPS=20
        localRateLimiterMap.put("order/create", RateLimiter.create(20.0));
        // 网关全局本地兜底QPS=100
        localRateLimiterMap.put("gateway/default", RateLimiter.create(100.0));
    }

    /**
     * 本地限流兜底方法
     * @param target 限流目标
     * @return true=获取本地令牌成功,false=失败
     */
    public boolean localLimitFallback(String target) {
        try {
            // 从缓存获取本地限流实例,无则创建默认实例
            RateLimiter rateLimiter = localRateLimiterMap.getOrDefault(target, RateLimiter.create(DEFAULT_LOCAL_QPS));
            // 尝试获取1个本地令牌(无超时)
            return rateLimiter.tryAcquire();
        } catch (Exception e) {
            log.error("本地限流兜底异常,目标:{}", target, e);
            return false;
        }
    }

    /**
     * 动态更新本地限流规则(支持Nacos热更新调用)
     * @param target 限流目标
     * @param qps 目标QPS
     */
    public void updateLocalLimit(String target, double qps) {
        localRateLimiterMap.put(target, RateLimiter.create(qps));
        log.info("本地限流规则更新成功,目标:{},QPS:{}", target, qps);
    }
}

2. Redis异常熔断降级(Sentinel整合)

通过Sentinel对Redis操作做熔断。当Redis异常率达到阈值时,直接触发本地降级,避免Redis异常拖垮整个服务。添加SentinelConfig.ja va

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 ja vax.annotation.PostConstruct;
import ja va.util.ArrayList;
import ja va.util.List;

/**
 * Sentinel熔断配置(Redis操作熔断)
 */
@Configuration
public class SentinelConfig {

    /**
     * 开启Sentinel注解支持
     */
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }

    /**
     * 初始化Redis操作熔断规则
     * 策略:异常比例熔断,5秒内异常率>50%,熔断10秒
     */
    @PostConstruct
    public void initDegradeRules() {
        List rules = new ArrayList<>();

        // Redis Lua脚本限流熔断规则
        DegradeRule luaRule = new DegradeRule();
        luaRule.setResource("redisLimitLua"); // 资源名,与@SentinelResource一致
        luaRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); // 异常比例
        luaRule.setCount(0.5); // 异常比例阈值50%
        luaRule.setTimeWindow(10); // 熔断时间10秒
        luaRule.setMinRequestAmount(10); // 最小请求数:5秒内至少10次请求才触发

        // Redisson限流熔断规则
        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,指定熔断降级方法

// 在RedisLimitLuaUtil的fixedWindowLimit方法添加
@SentinelResource(value = "redisLimitLua", fallback = "fixedWindowFallback")
public boolean fixedWindowLimit(String limitDimension, String target, int limit, int expire) {
    // 原有逻辑
}

// 熔断降级方法(参数与原方法一致)
public boolean fixedWindowFallback(String limitDimension, String target, int limit, int expire) {
    log.warn("Redis Lua限流触发Sentinel熔断,触发本地降级,目标:{}", target);
    return false; // 返回false触发本地兜底
}

// 在RedissonLimitUtil的rateLimit方法添加
@SentinelResource(value = "redissonLimit", fallback = "rateLimitFallback")
public boolean rateLimit(String target, int rate, int rateInterval, RateIntervalUnit unit, int capacity) {
    // 原有逻辑
}

// 熔断降级方法
public boolean rateLimitFallback(String target, int rate, int rateInterval, RateIntervalUnit unit, int capacity) {
    log.warn("Redisson令牌桶限流触发Sentinel熔断,触发本地降级,目标:{}", target);
    return false;
}

六、生产环境通用配置 & 监控埋点

1. Redis序列化优化(避免默认JDK序列化问题)

创建RedisConfig.ja va,替换为Jackson2JsonRedisSerializer,节省内存且避免序列化兼容问题:

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 redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // JSON序列化器
        Jackson2JsonRedisSerializer 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. 限流监控埋点(Micrometer+Prometheus)

添加监控指标,统计限流次数、Redis异常次数、本地降级次数,方便Grafana可视化和告警:

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

import ja vax.annotation.PostConstruct;
import ja vax.annotation.Resource;

/**
 * 限流监控指标工具类
 */
@Component
public class LimitMonitorUtil {

    @Resource
    private MeterRegistry meterRegistry;

    // 限流拒绝次数
    private Counter limitRejectCounter;
    // Redis异常次数
    private Counter redisErrorCounter;
    // 本地降级次数
    private Counter localDegradeCounter;

    @PostConstruct
    public void initCounter() {
        limitRejectCounter = Counter.builder("redis.limit.reject.count")
                .description("Redis分布式限流拒绝请求次数")
                .register(meterRegistry);
        redisErrorCounter = Counter.builder("redis.limit.error.count")
                .description("Redis分布式限流Redis操作异常次数")
                .register(meterRegistry);
        localDegradeCounter = Counter.builder("redis.limit.local.degrade.count")
                .description("Redis分布式限流本地降级触发次数")
                .register(meterRegistry);
    }

    // 记录限流拒绝
    public void recordLimitReject() {
        limitRejectCounter.increment();
    }

    // 记录Redis异常
    public void recordRedisError() {
        redisErrorCounter.increment();
    }

    // 记录本地降级
    public void recordLocalDegrade() {
        localDegradeCounter.increment();
    }
}

使用示例:在限流判断失败处调用监控方法:

if (!apiLimit || !ipLimit) {
    limitMonitorUtil.recordLimitReject(); // 记录限流拒绝
    if (!limitDegradeUtil.localLimitFallback(apiTarget)) {
        limitMonitorUtil.recordLocalDegrade(); // 记录本地降级
        return Result.fail(429, "请求过于频繁");
    }
}

七、生产环境落地关键注意事项

  1. Redis高可用:必须使用Redis主从+哨兵Redis Cluster,开启RDB+AOF混合持久化,避免Redis单点故障导致限流失效;
  2. 限流key设计:遵循前缀:维度:目标规范,避免键冲突,同时设置过期时间,防止Redis内存泄漏;
  3. 避免热点key:对高并发限流key(如秒杀接口)采用分段限流(如按IP哈希到不同key:limit:seckill:sku1001:{hash(ip)%10}),分担Redis单节点压力;
  4. 动态配置:所有限流规则(阈值、窗口、QPS)均从Nacos/Apollo加载,支持热更新,无需重启服务;
  5. 异常隔离:Redis操作添加超时时间(生产建议200-500ms),避免Redis阻塞导致服务线程池耗尽;
  6. 降级兜底:本地限流的QPS必须远低于分布式限流,避免本地兜底被击穿,同时结合Sentinel做熔断;
  7. 监控告警:对redis.limit.reject.countredis.limit.error.count设置告警阈值,及时发现限流规则不合理或Redis故障;
  8. 压测验证:上线前通过JMeter/Gatling做压测,验证限流规则的有效性和Redis的性能瓶颈。

总结

这套模板是Redis分布式限流的生产级开箱即用方案,核心亮点:

  1. 双核心限流:Lua脚本(轻量原子)+ Redisson(开箱即用),覆盖99%生产场景;
  2. 网关统一限流:Spring Cloud Gateway整合,实现全链路入口限流,避免逻辑散落;
  3. 高可用降级:Redis宕机/异常时自动触发Gua va本地兜底,结合Sentinel熔断,保证服务可用;
  4. 可配置可监控:Nacos动态配置规则,Micrometer埋点监控,支持告警和可视化;
  5. 规范可扩展:统一的key设计、工具类封装,支持多维度限流(接口/IP/用户/租户)和自定义扩展。

使用时仅需根据自身业务调整限流规则降级QPSRedis部署方式,即可快速落地到生产环境。

来源:https://www.jb51.net/database/3583528ql.htm
上一篇Redis布隆过滤器实现缓存去重实例详解 下一篇Redis Cluster多key事务操作的实现示例
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

Spring Boot 3动态拼接SQL为何引发严重安全漏洞
数据库 · 2026-07-02

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须