一、背景
线上有一个接口需要限流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 {
// 被限流后的操作
}