1.引入maven包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
2.代码实现:
a.RedissonConfig 初始化
package com.hanyc.demo.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* redisson
*
* @author hanyc
* @date 2024/12/31 13:30
* @company:
*/
@Configuration
public class RedissonConfig {
@Autowired
private RedisProperties redisProperties;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
if (redisProperties.getCluster() != null && !redisProperties.getCluster().getNodes().isEmpty()) {
RedisProperties.Cluster cluster = redisProperties.getCluster();
List<String> nodes = cluster.getNodes();
List<String> newNodes = new ArrayList<>();
nodes.forEach(index -> newNodes.add(
index.startsWith("redis://") ? index : "redis://" + index));
config.useClusterServers()
.addNodeAddress(newNodes.toArray(new String[0]))
.setPassword(redisProperties.getPassword());
} else {
String address = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();
config.useSingleServer().setAddress(address).setPassword(redisProperties.getPassword());
}
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
b.UserRateLimiterInterceptor 用户请求限流拦截器
package com.hanyc.demo.config;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description: 用户请求限流拦截器
* @Author hanyc
* @Date 2024/12/12
**/
@Component
@Slf4j
public class UserRateLimiterInterceptor implements HandlerInterceptor {
@Autowired
private RedissonClient redissonClient;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 假设这个方法能够从请求中解析出用户ID
String userIdStr = "";
String userIp = ServletUtil.getClientIP(request);
// 假设用户ID 为 1
String userId = "1";
if (userId == null) {
// 所有未登录账号 不需要验证的请求,都使用userId=10000
userIdStr = userIp;
} else {
userIdStr = userId.toString();
}
// 为每个用户生成唯一的限流键
String rateLimiterKey = "USER_RATE_LIMITER" + userIdStr;
// 删除 Redis 中存储的限流器状态(清除缓存)
// RRateLimiter 在内部将配置保存在 Redis 中,并且这些配置是持久化的。删除该键后,Redis 会丢失之前存储的限流配置,从而确保新的配置能够被应用。
redissonClient.getBucket(rateLimiterKey).delete();
// 获取分布式的 RRateLimiter 实例
RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateLimiterKey);
// 初始化限流器,限制每两秒最多 10 次请求
// 参数说明:
// RateType.OVERALL 全局
// rate 时间限制内可以请求多少次
// rateInterval 多少时间内限制
// 时间单位 可以秒/分钟/小时等
rateLimiter.trySetRate(RateType.OVERALL, 10, 2, RateIntervalUnit.SECONDS);
log.info("当前路由为:{} userIdStr:{} userIp:{}", request.getRequestURI(), userIdStr, userIp);
// 尝试获取令牌,如果获取不到说明超过请求限制
if (rateLimiter.tryAcquire()) {
// 允许继续处理请求
return true;
} else {
// 如果获取不到令牌,则说明请求超过了限制,可以在这里抛出异常或者返回错误信息
log.warn("当前异常的路由为:{} userIdStr:{} userIp :{}", request.getRequestURI(), userIdStr, userIp);
return false;
}
}
}
c. WebMvcConfig 添加自定义拦截器
package com.hanyc.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* MVC自定义配置
*
* @author hanyc
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public UserRateLimiterInterceptor userRateLimiterInterceptor() {
return new UserRateLimiterInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userRateLimiterInterceptor()).addPathPatterns("/**");
}
}
3.测试
使用JMeter 发送请求. 每秒15个.
查看打印结果:
前10个请求正常响应. 后五个请求被拦截