引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.3</version>
</dependency>
进行config配置
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@ConfigurationProperties(prefix = "spring.redis")
@Configuration
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()
.setDatabase(database)
.setAddress("redis://" + host + ":" + port)
.setPassword(password);
RedissonClient redisson = Redisson.create();
return redisson;
}
}
定义限流规则
package com.yupi.springbootinit.mapper;
import com.yupi.springbootinit.exception.ThrowUtils;
import com.yupi.springbootinit.common.ErrorCode;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class RedisLimiterManager {
@Resource
private RedissonClient redissonClient;
/**
* 限流操作
*
* @param key 区分不同的限流器,比如不同的用户 id 应该分别统计
*/
public void doRateLimit(String key) {
// 创建一个限流器
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
// 每秒最多访问 2 次
// 参数1 type:限流类型,可以是自定义的任何类型,用于区分不同的限流策略。
// 参数2 rate:限流速率,即单位时间内允许通过的请求数量。
// 参数3 rateInterval:限流时间间隔,即限流速率的计算周期长度。
// 参数4 unit:限流时间间隔单位,可以是秒、毫秒等。
rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
// 每当一个操作来了后,请求一个令牌
boolean canOp = rateLimiter.tryAcquire(1);
ThrowUtils.throwIf(!canOp, ErrorCode.OPERATION_ERROR);
}
}
设置限流器的限流参数
第一个参数为限流类型:整体速率限制,表示所有请求共同享用一个速率的限制。
第二个参数为限流速率,我们设置的为2,就是单位时间内允许通过的请求数量为2,
第三个自然就是限流的时间单位。所以整体允许请求一秒两次。然后再通过限流器对象去获取令牌,如果能拿到返回true执行后面业务逻辑,如果拿不到返回false就停止后面代码执行,响应给前端系统错误
在需要限流的接口处,进入业务逻辑之前,引入该方法就可以实现限流。
不难看出我们其实没有太多操作,因为redission底层已经封装好了。内部大概思路
- 使用Redis的String类型存储当前令牌数量,用ZSet(有序集合)存储令牌的过期时间和令牌标识。
- Lua脚本首先获取当前令牌数量和相应的限流参数,在令牌桶中查询是否有可用的令牌(未过期)。
- 如果有可用的令牌,则分配令牌并更新令牌桶信息;如果没有足够的令牌,则请求被限流。
- 最后,更新令牌桶的过期时间,保证令牌桶中的令牌能够定时清理。