用Redisson的RRateLimiter做限流【2】——大QPS限流策略

一、背景

线上有一个接口需要限流10W的QPS,这个时候如果再使用Redisson的RRateLimiter做限流,就会造成热key问题,会压垮redis。

二、解决方案

方案一:通过请求参数取模打散创建多个的RRateLimiter

暂时不使用这种方案。

方案二:每次从Redis取多个令牌到本地机器

每次从Redis取多个令牌到本地机器,本地机器的令牌消耗完了再用RRateLimiter向Redis取令牌。

方案三:方案一+方案二的一个组合

2.1 引入maven依赖

xml 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.4</version>
</dependency>

2.2 实现代码

2.2.1 本地限流器
java 复制代码
public class CustomRateLimiter {
    private final RRateLimiter rateLimiter;
    // 用原子变量来缓存令牌数量
    private final AtomicLong tokens;
    // 每次从 Redis 获取的令牌数量
    private final long batchSize;
    
    public CustomRateLimiter(RRateLimiter rRateLimiter, long batchSize) {
        this.rateLimiter = rRateLimiter;
        // 比如:10
        this.batchSize = batchSize;
        // 初始化令牌数量
        this.tokens = new AtomicLong(0);
    }
    
    public boolean tryAcquire() {
        // 尝试获取本地令牌
        long currentTokens = tokens.get();
        if (currentTokens > 0) {
            // 如果本地缓存的令牌足够,直接消耗一个,成功获取令牌
            return tokens.decrementAndGet() >= 0;
        } else {
            // 从 Redis 获取 batchSize 个令牌
            if (rateLimiter.tryAcquire(batchSize)){
                // 先消耗一个,再更新
                tokens.addAndGet(batchSize - 1);
                return true;
            }else {
                return false;
            }
        }
    }
}
2.2.2 用bean管理本地限流器
java 复制代码
@Service
public class RateLimiterConfiguration {
    @Autowired
    @Qualifier("RRateLimiter")
    private RRateLimiter myRRateLimiter;
    
    @Bean(name = "myCustomRateLimiter")
    public CustomRateLimiter getMyRateLimiter() {
        return new CustomRateLimiter(myRRateLimiter, 10);
    }
}
2.2.3 RRateLimiter配置项
java 复制代码
@Configuration
public class RedissonConfiguration {
    @Bean(name = "rateLimiterRedissonClient")
    public RedissonClient getRateLimiterRedissonClient() {
        Config config = new Config();
        String clusterNodes = "redis集群节点信息(127.0.0.1:6379)";
        int connectionMinIdleSize = 10; // 连接池最小空闲线程数
        int connectionMaxSize = 100; // 连接池最大连接数
        
        if (StringUtils.isBlank(clusterNodes)) {
            throw new RuntimeException("Redisson 初始化失败, 没有配置集群地址");
        }
        
        String[] nodes = clusterNodes.split(",");
        List<String> newNodes = new ArrayList<>(nodes.length);
        Arrays.stream(nodes)
                .forEach((index) -> newNodes.add(index.startsWith("redis://") ? index : "redis://" + index));

        config.useClusterServers()
                .addNodeAddress(newNodes.toArray(new String[0]))
                .setMasterConnectionMinimumIdleSize(connectionMinIdleSize)
                .setSlaveConnectionMinimumIdleSize(connectionMinIdleSize)
                .setMasterConnectionPoolSize(connectionMaxSize)
                .setSlaveConnectionPoolSize(connectionMaxSize);
        
        config.setTransportMode(TransportMode.NIO);
        return Redisson.create(config);
    }
}
java 复制代码
@Configuration
public class RRateLimiterConfiguration {
    
    @Autowired
    @Qualifier("rateLimiterRedissonClient")
    private RedissonClient rateLimiterRedissonClient;
    
    @Bean(name = "RRateLimiter")
    public RRateLimiter getUserTimeGetCoinRRateLimiter() {
        String RRateLimiterName = "限流器名字";
        RRateLimiter RRateLimiter = rateLimiterRedissonClient.getRateLimiter(RRateLimiterName);
        long RRateLimiterFre = 3; // 限流器令牌桶最大大小 也是N秒内发放的令牌桶数量
        long RRateLimiterTime = 5; // N 秒内限制通过令牌的数量的N(时间参数)
        RRateLimiter.trySetRate(RateType.OVERALL,
                RRateLimiterFre, RRateLimiterTime, RateIntervalUnit.SECONDS);
        return RRateLimiter;
    }
}
2.2.4 使用
java 复制代码
@Autowired
@Qualifier("myCustomRateLimiter")
private CustomRateLimiter myCustomRateLimiter;

if (myCustomRateLimiter.tryAcquire()) {
    // 执行被限流的业务逻辑
} else {
    // 被限流后的操作
}
相关推荐
风象南12 分钟前
SpringBoot中4种接口幂等性实现策略
java·spring boot·后端
洛小豆24 分钟前
在Java中Exception 和 Error 有什么区别?
java·后端·面试
寻梦人121382 小时前
关于在Spring Boot + SpringSecurity工程中Sercurity上下文对象无法传递至新线程的问题解决
java·spring boot·后端
来自星星的坤9 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc9 小时前
RaabitMQ 快速入门
java·后端·rabbitmq
烛阴9 小时前
零基础必看!Express 项目 .env 配置,开发、测试、生产环境轻松搞定!
javascript·后端·express
燃星cro9 小时前
参照Spring Boot后端框架实现序列化工具类
java·spring boot·后端
追逐时光者12 小时前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.12 小时前
GO语言入门
开发语言·后端·golang
转转技术团队13 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端