这篇文章主要分享使用Redisson在Spring Cloud Gateway 中实现分布式自定义限流
为啥不使用Sentinel?
想到分布式限流组件,大家很容易想到Sentinel,但是其实有很多局限性,首先Sentinel的流量控制有两个维度的控制:并发线程数 和QPS,它们都是针对某个具体的接口来设置的
Sentinel的限流原理主要是通过统计系统的QPS(即每秒请求数量)和并发量来控制系统的流量,从而达到限流的目的。
     其实之前分享过Sentinel限流的应用,大家感兴趣的话可以先看下了解下。
可以看出目前不支持自定义时间窗口,与分布式限流,以及自定义规则限流,比如 IP地址,用户等维度限流功能
常用限流方案比对
在 Spring Cloud Gateway 中实现分布式限流时,Redisson 方案 与原生 RequestRateLimiter 是两种主流方案,以下是核心对比与选型建议:
| 特性 | Redisson 方案 | RequestRateLimiter (原生) | 
|---|---|---|
| 分布式支持 | ✅ 基于 Redis 集群,天然分布式 | ✅ 基于 Redis,依赖 Lua 脚本实现分布式 | 
| 时间窗口灵活性 | ✅ 秒/分/时/天级自由配置 (RateIntervalUnit) | 
❌ 仅支持秒级窗口(需扩展) | 
| 限流算法 | ✅ 令牌桶算法(允许突发流量) | ✅ 令牌桶算法(固定窗口) | 
| 动态规则更新 | ✅ 通过 trySetRate() 实时调整规则 | 
❌ 需重启服务或手动刷新配置 | 
| 限流维度 | ✅ 支持 IP、用户、API 等多维度组合键 | ✅ 需自定义 KeyResolver 实现 | 
| 依赖复杂度 | 需引入 Redisson 客户端 | 仅需 Spring Data Redis Reactive | 
Redisson 核心优势:
- 
灵活时间窗口:支持分钟/小时级限流(如 1 分钟 100 次请求)
 - 
动态生效:限流规则可运行时热更新;
 - 
精细控制 :提供
RateType.PER_CLIENT(按客户端限流)和RateType.OVERALL(全局限流) 
Redisson 分布式限流总体设计

处理流程
- 客户端请求到达 Spring Cloud Gateway。
 - Gateway 根据配置的 KeyResolver 解析出限流的键(例如:IP、用户ID、API等)。
 - 根据路由地址,从动态配置中获取限流规则(例如:每秒100次)。
 - 使用 Redis 执行 Lua 脚本(保证原子性)进行限流计数和判断。
 - 如果未超过阈值,请求放行;否则返回429(Too Many Requests)。
 - 动态配置管理:通过管理后台更新限流规则,并同步到所有网关节点。
 

redission令牌桶限流算法

系统以固定速率(如每秒 100 个)向桶中添加令牌。例如:
            
            
              java
              
              
            
          
          rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS);
        rate:令牌生成速率(100 个/秒)interval+unit:时间窗口(1 秒)
Redisson 分布式限流的具体代码实现
引入依赖版本
版本兼容性
这边在引入Redisson版本的,一定要注意与gateway版本兼容还有Redis服务版本问题,具体可以参考下,优先根据 Spring Boot 版本选择 Redisson,再匹配 Spring Cloud Gateway
- Spring Boot 2.4.x → Redisson 3.17.x
 - Spring Boot 2.7.x → Redisson 3.23.x
 - Spring Boot 3.2.x → Redisson 3.27.x+
 
| 场景 | Spring Cloud Gateway | Redisson | Spring Boot | Redis Server | 
|---|---|---|---|---|
| 传统项目(兼容旧版) | 2.2.2.RELEASE | 3.17.7 | 2.4.13 | 6.x | 
| 新项目(推荐) | 4.2.0 | 3.27.1 | 3.2.4 | 7.x | 
我这边与gateway版本兼容问题 版本是2.1.1.RELEASE,引入的redisson版本是3.13.6,大家可以做个参考。
            
            
              xml
              
              
            
          
          <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>
        
            
            
              xml
              
              
            
          
          <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency>
        初始化 RedissonClient
大家注意下这里 config.useClusterServers() 要根据自己的redis服务器选择不同的配置
            
            
              java
              
              
            
          
          import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
    @Value("${redis.hosts}")
    private String redisHost;
    @Value("${redis.ports}")
    private int redisPort;
    @Value("${redis.password}")
    private String redisPassword;
    @Value("${redis.timeout}")
    private int timeout;
    @Bean(destroyMethod ="shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useClusterServers()
                .addNodeAddress("redis://" +redisHost+ ":" + redisPort)
                .setPassword(redisPassword)
                .setConnectTimeout(timeout);
        return Redisson.create(config);
    }
}
        规则存储
规则默认存储在mysql数据库中,就是一个很简单的结构,可以根据自己需求扩展
            
            
              sql
              
              
            
          
          CREATE TABLE `limit_rules` (
  `id` bigint(20) NOT NULL,
  `request_user` varchar(64) DEFAULT NULL COMMENT '请求用户',
  `request_ip` varchar(64) DEFAULT NULL COMMENT '请求IP',
  `request_url` varchar(255) NOT NULL COMMENT '请求地址',
  `limit_count` int(11) NOT NULL COMMENT '限制数量',
  `window_size` int(11) NOT NULL COMMENT '时间窗口/秒',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uni_request_url` (`request_url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分布式限流规则表';
        网关定时任务,每隔30s拉取最新的规则数据存取在内存中
            
            
              java
              
              
            
          
          @Service
public class RateLimitRuleService {
    @Autowired
    private RateLimitRuleRepository ruleRepository;
    private final Map<String, RateLimitRule> ruleCache = new ConcurrentHashMap<>();
    // 每30秒刷新规则缓存
    @Scheduled(fixedRate = 30000)
    public void refreshRules() {
        ruleRepository.findAll().forEach(rule -> 
            ruleCache.put(rule.getRouteId() + ":" + rule.getKeyExpression(), rule)
        );
    }
}
        自定义限流过滤器
这里有点需要注意,规则删除之后,一点要清除redis中附属的key的计数数据
            
            
              java
              
              
            
          
          @Component
public class RedissonRateLimitFilter implements GlobalFilter {
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RateLimitRuleService ruleService;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String key = resolveKey(exchange); 
        // 1. 查询动态规则
        RateLimitRule rule = ruleService.getRule( key);
        //这里规则逻辑就省略了,大家自己根据自己规则设置
        if (rule == null) {
            return chain.filter(exchange); // 无规则则放行
        }
        if(rule.isEnabled()){
            // 如果规则被标记为删除,则直接返回
            log.error("RedissonRateLimitFilter rule for path {} has been deleted", path);
            this.destroyRateLimiter(key, path);
            return chain.filter(exchange);
        }
        // 2. 初始化Redisson限流器
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("rate_limit:" + rule.getId() + ":" + key);
        rateLimiter.trySetRateAsync(
                RateType.OVERALL,
                rule.getLimitCount(),
                rule.getWindowSize(),
                RateIntervalUnit.SECONDS
        );
        // 4. 尝试获取令牌
        if (rateLimiter.tryAcquire()) {
            return chain.filter(exchange); // 放行
        } else {
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return exchange.getResponse().setComplete(); // 返回429
        }
    }
    
    public void destroyRateLimiter(String key,String requestUrl) {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        // 1. 删除主配置
        rateLimiter.delete();
        // 2. 删除附属 Key
        this.deleteAuxiliaryKeys(key);
        //3. 删除数据库中规则
        ruleService.deleteRule(requestUrl);
    }
    private void deleteAuxiliaryKeys(String key) {
        String valueKey = key + ":value";
        String permitsKey = key + ":permits";
        // 批量异步删除
        RBatch batch = redissonClient.createBatch();
        batch.getBucket(valueKey).deleteAsync();
        batch.getScoredSortedSet(permitsKey).deleteAsync();
        batch.execute();
    }
}
        以上就是相关的代码,可以看到使用redisson还是很简单的就可以实现分布式限流的功能
压测数据
最后我们来看下压测相关数据,看下对性能影响如何?
对接口进行对照压测,线程组配置如下
 1.无任何配置,吞吐 113/s
 2.对接口进行限流配置,但是配置远大于请求数据,吞吐113.3/s
 3.对接口进行限流配置,但是配置远小于请求数据,大部分接口限流,吞吐115.3/s
     因为压测接口没有任何逻辑,直接返回,所以看上去没有实际的差距,这里也懒得再重新压测了,感兴趣的可以自己再压测下相关业务接口,但是实际我们上线了生产环境一个月了,目前来看对于性能影响来看很小