SpringBoot集成Redisson实现自定义注解限流

​ Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
​ 其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。
​ Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

引入坐标文件

SpringBoot版本

<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.6.9</version>
<relativePath/>
</parent>

redisson版本

<!-- springbootweb依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springbootaop自定义切面依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- redisson版本 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.4</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

具体代码实现

application.yml,redisson配置文件

# redisson配置(单机配置)
redisson:
# 线程池数量
threads: 2
# Netty线程池数量
nettyThreads: 4
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: xxxxxx
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 以下配置在下一篇文章中关于redisson作为springcache的存储容器时需要用到
cacheGroup:
# 用例: @Cacheable(cacheNames="groupId", key="#XXX") 方可使用缓存组配置
- groupId: springCacheTest
# 组过期时间/min
ttl: 30
# 组最大空闲时间/min
maxIdleTime: 30
# 组最大容量(使用LRU算法驱逐多余的)
maxSize: 500

RedissonProperties.java,用于获取application.yml中的配置文件

@Data
@Component
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {

/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
private int threads;

/**
* Netty线程池数量,默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;

/**
* 单机服务配置
*/
private SingleServerConfig singleServerConfig;

/**
* 集群服务配置
*/
private ClusterServersConfig clusterServersConfig;

/**
* 缓存组
*/
private List<CacheGroup> cacheGroup;

// 单机属性
@Data
@NoArgsConstructor
public static class SingleServerConfig {

/**
* 客户端名称
*/
private String clientName;

/**
* 最小空闲连接数
*/
private int connectionMinimumIdleSize;

/**
* 连接池大小
*/
private int connectionPoolSize;

/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;

/**
* 命令等待超时,单位:毫秒
*/
private int timeout;

/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;

}

// 集群属性
@Data
@NoArgsConstructor
public static class ClusterServersConfig {

/**
* 客户端名称
*/
private String clientName;

/**
* master最小空闲连接数
*/
private int masterConnectionMinimumIdleSize;

/**
* master连接池大小
*/
private int masterConnectionPoolSize;

/**
* slave最小空闲连接数
*/
private int slaveConnectionMinimumIdleSize;

/**
* slave连接池大小
*/
private int slaveConnectionPoolSize;

/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;

/**
* 命令等待超时,单位:毫秒
*/
private int timeout;

/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;

/**
* 读取模式
*/
private ReadMode readMode;

/**
* 订阅模式
*/
private SubscriptionMode subscriptionMode;

}

// springcache缓存
@Data
@NoArgsConstructor
public static class CacheGroup {

/**
* 组id
*/
private String groupId;

/**
* 组过期时间
*/
private long ttl;

/**
* 组最大空闲时间
*/
private long maxIdleTime;

/**
* 组最大长度
*/
private int maxSize;
}
}

RedissonConfig.java,Redisson的配置文件

@RequiredArgsConstructor
@EnableCaching
@Configuration
public class RedissonConfig extends CachingConfigurerSupport{

private final RedissonProperties redissonProperties;
private final ObjectMapper objectMapper;

/**
* redisson配置(因约定大于配置,可选)
* 单机配置方式
*
* @return
*/
@Bean
public RedissonAutoConfigurationCustomizer redisCustomizer() {
return config -> {
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
// 使用jackson序列化
.setCodec(new JsonJacksonCodec(objectMapper));
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式
config.useSingleServer()
.setTimeout(singleServerConfig.getTimeout())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
}
};
}
}

MyRateLimit.java,自定义限流注解,加载需要限流的方法上

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyRateLimit {
/**
* 默认10s内请求50次
*/

/**
* 请求次数
*
* @return
*/
int count() default 50;

/**
* 时间段/s
*
* @return
*/
int time() default 10;
}

MyRateLimitAspect.java,aop切面处理器

@Component
@Aspect
// 优先级最高
@Order(1)
@Slf4j
public class MyRateLimitAspect {

/**
* TODO 根据ip限流,其它限流方式待定
*
* @param point
* @param myRateLimit
*/
@Before("@annotation(myRateLimit)")
public void doBefore(JoinPoint point, MyRateLimit myRateLimit) {
int count = myRateLimit.count();
int time = myRateLimit.time();
log.info("次数:" + count + ",间隔:" + time);
// redisson限流
String key = getCombineKey(point);
try {
long number = RedisUtil.rateLimiter(key, RateType.OVERALL, count, time);
if (number == -1) {
// 达到限流次数
throw new RateLimitException("请求速度过快,请稍后重试");
}
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, key);
} catch (RateLimitException e1) {
throw new RateLimitException("请求速度过快,请稍后重试");
} catch (Exception e) {
throw new RuntimeException("服务器限流异常,请稍候再试");
}
}


// 构造限流键值
// TODO 存储在redis中的键的名称
private String getCombineKey(JoinPoint point) {
// TODO ServletUtils.getClientIp()获取客户端ip,自定义实现
StringBuilder stringBuilder = new StringBuilder(ServletUtils.getClientIp())
.append("-");

MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
stringBuilder.append(targetClass.getName())
.append("-")
.append(method.getName());
return stringBuilder.toString();
}
}

将MyRateLimit注解加在需要限流的方法上,示例:

@MyRateLimit(time = 10,count = 10)
@GetMapping("/toRateLimit")
public R<Void> toRateLimit() {
return R.success();
}

注:SpringBoot集成Redisson,使用Redis替换SpringCache的底层实现,见:下篇文章