用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 {
    // 被限流后的操作
}
相关推荐
二闹2 分钟前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户490558160812514 分钟前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白16 分钟前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈17 分钟前
VS Code 终端完全指南
后端
该用户已不存在42 分钟前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃1 小时前
内存监控对应解决方案
后端
码事漫谈1 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端
Moonbit1 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞 (上):编译前端实现
后端·算法·编程语言
Moonbit1 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞(下):llvm IR 代码生成
后端·程序员·代码规范
Moonbit2 小时前
MoonBit Pearls Vol.05: 函数式里的依赖注入:Reader Monad
后端·rust·编程语言