Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis 分布式限流

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

作者:三水不滴

本文主要介绍了Redis分布式限流生产环境落地方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

前置依赖

生产环境统一引入以下核心依赖(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网关 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos动态配置(限流规则热更新) -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 熔断降级(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>
<!-- 本地降级限流(Guava) -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

一、基础环境配置

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

封装脚本加载、参数组装、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 javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;

/**
 * Redis Lua脚本限流通用工具类(固定窗口+滑动窗口)
 */
@Component
public class RedisLimitLuaUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // 固定窗口脚本
    private DefaultRedisScript<Long> fixedWindowScript;
    // 滑动窗口脚本
    private DefaultRedisScript<Long> 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 javax.annotation.Resource;
import javax.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.java

封装令牌桶(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 javax.annotation.Resource;
import java.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<Order> 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.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;

/**
 * 网关限流规则配置(Nacos热更新)
 */
@Data
@Component
@RefreshScope // 开启配置热更新
@ConfigurationProperties(prefix = "gateway.limit")
public class GatewayLimitRule {
    // key=路由ID,value=具体规则
    private Map<String, GatewayLimitItem> 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.java

实现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 javax.annotation.Resource;
import java.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<Void> 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(),
                java.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 宕机 / 超时 / 压力过大时,不影响核心业务可用,通过本地内存限流(Guava RateLimiter) 做兜底,同时结合Sentinel 熔断限制降级后的请求量,避免本地限流被击穿。

1. 降级核心工具类LimitDegradeUtil.java

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

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;

/**
 * 限流降级工具类(Redis异常时本地兜底)
 * 基于Guava RateLimiter实现本地令牌桶限流
 */
@Component
public class LimitDegradeUtil {
    // 本地限流实例缓存:key=限流目标,value=Guava RateLimiter
    private final Map<String, RateLimiter> 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.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操作熔断规则
     * 策略:异常比例熔断,5秒内异常率>50%,熔断10秒
     */
    @PostConstruct
    public void initDegradeRules() {
        List<DegradeRule> 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.java,替换为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<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. 限流监控埋点(Micrometer+Prometheus)

添加监控指标,统计限流次数、Redis 异常次数、本地降级次数,方便 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;

/**
 * 限流监控指标工具类
 */
@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 宕机 / 异常时自动触发 Guava 本地兜底,结合 Sentinel 熔断,保证服务可用;
  4. 可配置可监控:Nacos 动态配置规则,Micrometer 埋点监控,支持告警和可视化;
  5. 规范可扩展:统一的 key 设计、工具类封装,支持多维度限流(接口 / IP / 用户 / 租户)和自定义扩展。

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

到此这篇关于Redis分布式限流生产环境落地方案的文章就介绍到这了,更多相关Redis 分布式限流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文