分布式限流(令牌桶限流)——面试答问+代码案例
·
什么是 Redisson 的 RateLimiter?它在项目中具体如何实现分布式限流?你的限流策略是 什么?
RateLimiter 是 Redisson 基于 Redis 实现的分布式限流组件,底层使用令牌桶算法,能够控制某 个操作或服务在一定时间内的请求频率,保护系统不被过多的请求压垮。在项目中,考虑到 AI 生成图表是一个耗时且耗费资源的操作,我决定给 AI 生成图表接口增加限 流,具体的策略是:单个用户每秒内最多执行 2 次生成图表操作。具体的实现方式如下:1)集中管理限流器:创建一个 RedisLimiterManager 类,集中管理整个项目中所有的限流器,并提供创建限流器的接口。2)创建限流器:通过向 redissonClient 传入指定的 key 来创建限流器,每个用户对应的 key 不同,对应的限流器也不同,从而实现不同用户的独立限流3)设置限流规则:通过 rateLimiter 的 trySetRate 方法制定限流规则,每秒内最多获取 2 个令牌4)请求令牌:当用户要执行操作时,会执行对应 rateLimiter 的 tryAcquire 方法尝试获取令牌,如果能够获取到,可以执行后续操作,否则抛出 TOO_MANY_REQUEST 异常。
说说令牌桶算法
面对突发流量的时候,我们可以使用令牌桶算法限流。
令牌桶算法原理:
- 有一个令牌管理员,根据限流大小,定速往令牌桶里放令牌。
- 如果令牌数量满了,超过令牌桶容量的限制,那就丢弃。
- 系统在接受到一个用户请求时,都会先去令牌桶要一个令牌。如果拿到令牌,那么就处理这个请求的业务逻辑;
- 如果拿不到令牌,就直接拒绝这个请求。
图片参考出处
作者:捡田螺的小男孩
链接:https://juejin.cn/post/6967742960540581918
实现案例
引入 Redisson 代码包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.3</version>
</dependency>
创建 RedissonConfig 配置类,用于初始化 RedissonClient 对象单例:
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
private Integer database;
private String host;
private Integer port;
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setPassword(password) // 👈 添加这一行用于设置 Redis 密码
.setDatabase(database);
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
编写 RedisLimiterManager:
// 表明这是一个 Spring 框架中的服务类,Spring 会自动将其实例化并管理
@Service
public class RedisLimiterManager {
// 通过 Spring 的依赖注入机制,将 RedissonClient 实例注入到当前类中
// RedissonClient 是 Redisson 框架的核心客户端,用于与 Redis 服务器进行交互
@Resource
private RedissonClient redissonClient;
/**
* 限流操作
*
* @param key 用于区分不同的限流器,例如不同的用户 id 应分别进行统计
* 每个不同的 key 会对应一个独立的限流器,可实现针对不同业务场景或用户的限流
* @throws BusinessException 当请求超过限流阈值时,抛出业务异常,异常码为 ErrorCode.TOO_MANY_REQUEST
*/
public void doRateLimit(String key) {
// 从 RedissonClient 中获取一个名为 key 的限流器实例
// RRateLimiter 是 Redisson 提供的用于实现限流功能的接口
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 尝试为限流器设置速率规则
// RateType.OVERALL 表示对整个系统进行限流,即不区分不同的客户端或节点
// 2 表示每秒允许的最大操作次数
// 1 表示时间间隔的数量
// RateIntervalUnit.SECONDS 表示时间间隔的单位为秒
// 此设置意味着每秒最多允许 2 次操作
rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
// 每当有一个操作到来时,尝试从限流器中获取一个令牌
// 若成功获取到令牌,说明该操作在限流范围内,可以继续执行
// 若未获取到令牌,说明该操作超过了限流阈值
boolean canOp = rateLimiter.tryAcquire(1);
// 若未获取到令牌
if (!canOp) {
// 抛出业务异常,提示请求次数过多
// ErrorCode.TOO_MANY_REQUEST 是自定义的错误码,用于表示请求过于频繁
throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
}
}
}
单元测试:
// 表明这是一个 Spring Boot 的测试类,Spring Boot 会自动加载测试所需的上下文环境
@SpringBootTest
class RedisLimiterManagerTest {
// 通过 Spring 的依赖注入机制,将 RedisLimiterManager 实例注入到当前测试类中
// 这样就可以在测试方法中使用 RedisLimiterManager 的功能
@Resource
private RedisLimiterManager redisLimiterManager;
/**
* 测试 RedisLimiterManager 的 doRateLimit 方法
*
* @throws InterruptedException 当线程在休眠期间被中断时抛出此异常
*/
@Test
void doRateLimit() throws InterruptedException {
// 定义一个用户 ID,用于作为限流器的 key,模拟不同用户的限流情况
String userId = "1";
// 模拟在 1 秒内进行 2 次操作
for (int i = 0; i < 2; i++) {
// 调用 RedisLimiterManager 的 doRateLimit 方法进行限流操作
redisLimiterManager.doRateLimit(userId);
// 若操作未被限流,打印“成功”信息
System.out.println("成功");
}
// 让当前线程休眠 1 秒,模拟时间间隔
// 因为之前设置的限流器规则是每秒最多 2 次操作,休眠 1 秒后可以重新开始新的一轮计数
Thread.sleep(1000);
// 模拟在新的一秒内进行 5 次操作
for (int i = 0; i < 5; i++) {
// 调用 RedisLimiterManager 的 doRateLimit 方法进行限流操作
redisLimiterManager.doRateLimit(userId);
// 若操作未被限流,打印“成功”信息
// 由于限流器规则是每秒最多 2 次操作,后续的操作可能会触发限流异常
System.out.println("成功");
}
}
}
更多推荐

所有评论(0)