使用redisson RRateLimiter + 切面 来实现对方法或者接口的统一限流方法
1.依赖:
<!-- redis spring boot 内部自带版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2.propeties 配置 和 redisson 、redis 配置:
#-----------------redis-----------------#
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=1
# 连接超时时间(毫秒)
spring.redis.timeout=50000ms
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* @author Eng
* @date
* @Description
*/
@Component
@Configuration
@PropertySource("classpath:application.properties")
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.database}")
private int database;
@Value("${spring.redis.password}")
private String password;
@Bean
public Redisson redisson() {
Config config = new Config();
// config.useClusterServers()
// .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
// //可以用"rediss://"来启用SSL连接
// .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
// .addNodeAddress("redis://127.0.0.1:7002");
config.useSingleServer().setAddress("redis://" + host + ":" + port);
config.useSingleServer().setDatabase(database);
// config.setLockWatchdogTimeout(20*1000);
if(StringUtils.isNotEmpty(password)) {
config.useSingleServer().setPassword(password);
}
return (Redisson) Redisson.create(config);
}
}
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用fastjson
template.setValueSerializer(fastJsonRedisSerializer);
// hash的value序列化方式采用fastjson
template.setHashValueSerializer(fastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
- annotion:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @author Eng
* @date 2022/3/18 11:19
* @Description 接口限流
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface APILimit {
/**
* 要获取的令牌数
*/
int permits() default 1;
/**
* 等待时间,在没有令牌可获取的情况下
*/
int waitingTime() default 0;
/**
* 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
4.切面实现:
import com.tool.own.annotion.APILimit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.Redisson;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateLimiterConfig;
import org.redisson.api.RateType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import static com.tool.own.result.FastResult.error;
/**
* @author Eng
* @date 2022/3/18 11:24
* @Description redisson 实现限流
*/
@Aspect
@Component
@Slf4j
public class APILimitAdvice implements CommandLineRunner {
@Autowired
private Redisson redisson;
@Autowired
private RedisTemplate redisTemplate;
private static final String API_LOCK_KEY = "API_Lock_key";
private RRateLimiter rateLimiter;
private Integer rate;
private Integer rateInterval;
private RateIntervalUnit rateIntervalUnit;
@Around(value = "@annotation(apiLimit)")
public Object around(ProceedingJoinPoint pjp, APILimit apiLimit) throws Throwable {
int waitingTime = apiLimit.waitingTime();
int permits = apiLimit.permits();
TimeUnit timeUnit = apiLimit.timeUnit();
boolean whetherPass = waitingTime == 0 ? rateLimiter.tryAcquire(permits) : rateLimiter.tryAcquire(permits,waitingTime,timeUnit);
if (whetherPass){
return pjp.proceed();
}else {
return error("服务请求达到最大限制,请求被拒绝!");
}
}
@Override
public void run(String... args) {
this.setAPILimitConfig(1,1,RateIntervalUnit.SECONDS);
this.config();
}
/**
* 获取限流配置
*/
public RateLimiterConfig getRateLimiterConfig(){
return rateLimiter.getConfig();
}
/**
* 配置是被存储在redis 中,只有第一次加载时会被加载到redis
* 后面修改是不会生效的,redis中存在配置后重新启动服务进行加载,redis 配置也不会被修改
*/
public void setAPILimitConfig(Integer rate, Integer rateInterval, RateIntervalUnit rateIntervalUnit){
this.rate = rate;
this.rateInterval = rateInterval;
this.rateIntervalUnit = rateIntervalUnit;
}
/**
* 启动时创建限流配置
*/
private void config(){
rateLimiter = redisson.getRateLimiter(API_LOCK_KEY);
rateLimiter.trySetRate(RateType.OVERALL,rate,rateInterval,rateIntervalUnit);
RateLimiterConfig config = rateLimiter.getConfig();
log.info("API Limit Config : Rate:{} RateInterval:{} RateType:{}",config.getRate(),config.getRateInterval(),config.getRateType());
}
/**
* 伪刷新 因为没有在限流方法中看到修改或者删除刷新方法 为了达到效果只能取巧
* 不确定是否可能会导致一些问题,慎用!!! 分布式如果只刷新了一台服务配置可能会导致两台配置不一致导致异常情况
* 通过key 和 hashKey 修改配置达到不手动移除key 刷新配置需求
*/
public void refreshConfig(){
redisTemplate.opsForHash().put(API_LOCK_KEY,"rate",rate);
redisTemplate.opsForHash().put(API_LOCK_KEY,"interval",rateInterval);
// redisTemplate.opsForHash().put(API_LOCK_KEY,"type",RateType.OVERALL.ordinal());
}
}
redisson 内部实现主要方法:刷新方法 hashKey 就是来自于这里
@Override
public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"
+ "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"
+ "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",
Collections.<Object>singletonList(getName()), rate, unit.toMillis(rateInterval), type.ordinal());
}
测试方法:
/**
* 注解中可自定义参数 控制等待时间、时间单位 消耗令牌数
* 先调用此方法 在调用下面的方法 会返回拒绝提示
*/
@GetMapping(value = "/d")
@ApiOperation(value = "",httpMethod = "GET")
@APILimit
public Object enter4() throws Exception {
Thread.sleep(1000 * 5);
return apiLimitAdvice.getRateLimiterConfig();
}
private final ReentrantLock reentrantLock = new ReentrantLock();
@Autowired
private APILimitAdvice apiLimitAdvice;
@GetMapping(value = "/e")
@ApiOperation(value = "",httpMethod = "GET")
@APILimit
public String enter5() throws Exception {
return "ok";
}
还可以在注解参数中增加不同限流类型,加载不同限流类,达到不同接口不同限流次数,同一切面和方法。另外对于redis 中的 lua 脚本还有不足,需要继续学习!!!
请留下您宝贵的想法或者建议~~~只有交流才会突破思想束缚,实现更好方法。
不同限流配置实现:
竹节参数增加:
import com.tool.own.advice.StrategyEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @author Eng
* @date 2022/3/18 11:19
* @Description 接口限流
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface APILimit {
/**
* 要获取的令牌数
*/
int permits() default 1;
/**
* 等待时间,在没有令牌可获取的情况下
*/
int waitingTime() default 0;
/**
* 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 限流策略
*/
StrategyEnum limitStrategy() default StrategyEnum.LIMIT_1000;
}
限流策略枚举
import lombok.Getter;
/**
* @author Eng
* @date 2022/3/18 17:44
* @Description
*/
@Getter
public enum StrategyEnum {
LIMIT_1000,
LIMIT_200
}
配置参数承载文本类
import org.redisson.Redisson;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
/**
* @author Eng
* @date 2022/3/18 17:00
* @Description
*/
//@Data
public class LimitStrategyContent implements Comparable{
/**
* 限流配置key
*/
private String apiLockKey;
/**
* 限流类 org.redisson.api.RRateLimiter
*/
private RRateLimiter rateLimiter;
/**
* 总令牌数
*/
private Integer rate;
/**
* 限流时间
*/
private Integer rateInterval;
/**
* 时间单位
*/
private RateIntervalUnit rateIntervalUnit;
/**
* 排序
*/
private Integer sort;
public RRateLimiter getRateLimiter(){
return rateLimiter;
}
LimitStrategyContent(){
}
LimitStrategyContent(String apiLockKey,Redisson redisson,Integer rate,Integer rateInterval,RateIntervalUnit rateIntervalUnit,Integer sort){
this.apiLockKey = apiLockKey;
this.rateLimiter = redisson.getRateLimiter(apiLockKey);
this.rate = rate;
this.rateInterval = rateInterval;
this.rateIntervalUnit = rateIntervalUnit;
this.sort = sort;
}
public boolean trySetRate(){
return rateLimiter.trySetRate(RateType.OVERALL,rate,rateInterval,rateIntervalUnit);
}
@Override
public int compareTo(Object o) {
int oSort = (int) o;
if (this.sort < oSort){
return -1;
}
if (this.sort > oSort){
return 1;
}
return 0;
}
}
重新实现切面代码
package com.tool.own.advice;
import com.tool.own.annotion.APILimit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.Redisson;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.tool.own.result.FastResult.error;
/**
* @author Eng
* @date 2022/3/18 11:24
* @Description redisson 实现限流
*/
@Aspect
@Component
@Slf4j
public class APILimitAdvice implements CommandLineRunner {
@Autowired
private Redisson redisson;
@Autowired
private RedisTemplate redisTemplate;
private static final String API_LOCK_KEY = "API_Lock_key:";
// 这里本来想使用Set 但是Set 不能按照下标取值,并且不如枚举方便查找
private final Map<StrategyEnum,LimitStrategyContent> contentSet = new HashMap<>();
/*-------------------------------------------可选限流实现方式----------------------------------------------------------------------*/
@Override
public void run(String... args) throws Exception {
LimitStrategyContent content1 = new LimitStrategyContent(API_LOCK_KEY + 1,redisson,1000,1, RateIntervalUnit.SECONDS,1);
content1.trySetRate();
LimitStrategyContent content2 = new LimitStrategyContent(API_LOCK_KEY + 2,redisson,2,1, RateIntervalUnit.SECONDS,2);
content2.trySetRate();
contentSet.put(StrategyEnum.LIMIT_1000,content1);
contentSet.put(StrategyEnum.LIMIT_200,content2);
}
@Around(value = "@annotation(apiLimit)")
public Object around(ProceedingJoinPoint pjp, APILimit apiLimit) throws Throwable {
/**
* TODO 做一些校验
*/
int waitingTime = apiLimit.waitingTime();
int permits = apiLimit.permits();
TimeUnit timeUnit = apiLimit.timeUnit();
StrategyEnum strategy = apiLimit.limitStrategy();
LimitStrategyContent content = contentSet.get(strategy);
RRateLimiter rateLimiter = content.getRateLimiter();
boolean whetherPass = waitingTime == 0 ? rateLimiter.tryAcquire(permits) : rateLimiter.tryAcquire(permits,waitingTime,timeUnit);
if (whetherPass){
return pjp.proceed();
}else {
return error("服务请求达到最大限制,请求被拒绝!");
}
}
}