java Redisson 实现限流每秒/分钟/小时限制N个请求 -V2.0
概述
在先前的版本中,推出了基于 Java Redisson 的限流功能,可实现每秒/分钟/小时限制 N 个请求。然而,在实际生产环境中,我们发现了一个问题:当修改限流配置后,部分旧用户未能即时采用新的限流规则,而新用户则顺利应用了新规则。这导致了用户体验的不一致以及潜在的流量控制风险。
为了解决这一问题,对限流功能进行了全面优化,推出了第二版。在新版本中,限流规则的修改能够实时生效,无论是新用户还是旧用户,都将立即遵循最新的限流策略。这一改进确保了流量控制的准确性和一致性,为系统的稳定运行提供了更有力的保障。
旧版本链接:
https://blog.csdn.net/hyc123123123123/article/details/144849939?spm=1011.2124.3001.6209
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.23.3</version>
</dependency>
2.配置文件
rate-limiter:# 是否启用限流user-rate-limiter-run: true# 限流拦截器 限制多少个请求user-rate-limiter-number: 30# 限流拦截器时间范围(秒)user-rate-limiter-time: 10
3.配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;/*** @Description 限流配置文件* @Author hanyc* @Date 20250912*/
@Data
@Configuration
@ConfigurationProperties(prefix = "rate-limiter")
public class RateLimiterProperties {/*** 限流拦截器10秒限制多少个请求.*/private long userRateLimiterNumber;/*** 限流拦截器 N秒 限制多少个请求.*/private long userRateLimiterTime;/*** 是否启用限流*/private Boolean userRateLimiterRun;
}
4.代码实现:
a.RedissonConfig 初始化
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.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** redisson** @author hanyc* @date 2025/09/12 13:30*/
@Configuration
public class RedissonConfig {@Autowiredprivate RedisProperties redisProperties;@Beanpublic RedissonClient redissonClient() {Config config = new Config();String address = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();config.useSingleServer().setKeepAlive(true).setAddress(address).setPassword(redisProperties.getPassword())// 2秒心跳,保证链接可用性.setPingConnectionInterval(2000);RedissonClient redisson = Redisson.create(config);return redisson;}
}
b.UserRateLimiterInterceptor 用户请求限流拦截器
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
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;import java.util.concurrent.TimeUnit;/*** @Description: 用户请求限流拦截器* @Author hanyc* @Date 2025/09/12**/
@Component
@Slf4j
public class UserRateLimiterInterceptor implements HandlerInterceptor {@Autowiredprivate ScientificPermitAllUrlProperties scientificPermitAllUrlProperties;@Autowiredprivate RedissonClient redissonClient;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 替换为自定义获取用户id方法Long userId = 1L;if (!scientificPermitAllUrlProperties.getUserRateLimiterRun()) {// 不需要限流.return true;}// 假设这个方法能够从请求中解析出用户IDString userIdStr = "";String userIp = ServletUtil.getClientIP(request);if (userId == null) {// 所有未登录账号 不需要验证的请求,都使用userId=10000userIdStr = userIp;} else {userIdStr = userId.toString();}// 为每个用户生成唯一的限流键String rateLimiterKey = USER_RATE_LIMITER + userIdStr;// 获取分布式的 RRateLimiter 实例RRateLimiter rateLimiter = getRedissonRateLimiter(rateLimiterKey);log.debug("当前路由为:{} userIdStr:{} userIp:{}", request.getRequestURI(), userIdStr, userIp);// 尝试获取令牌,如果获取不到说明超过请求限制if (rateLimiter.tryAcquire()) {// 允许继续处理请求return true;} else {// 如果获取不到令牌,则说明请求超过了限制,可以在这里抛出异常或者返回错误信息log.warn("当前多余的路由为:{} userIdStr:{} userIp :{}", request.getRequestURI(), userIdStr, userIp);throw new ScientificBusinessException("访问过于频繁,请稍后重试");}}/*** 获取Redisson的RRateLimiter** @param key KEY* @return*/private RRateLimiter getRedissonRateLimiter(String key) {// 限流次数,注意count的值不能小于1,必须大于等于1long count = scientificPermitAllUrlProperties.getUserRateLimiterNumber();// 限流时间 秒long timeOut = scientificPermitAllUrlProperties.getUserRateLimiterTime();RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);// 如果限流器不存在,就创建一个RRateLimiter限流器if (!rateLimiter.isExists()) {rateLimiter.trySetRate(RateType.OVERALL, count, timeOut, RateIntervalUnit.SECONDS);return rateLimiter;}// 获取限流的配置信息RateLimiterConfig rateLimiterConfig = rateLimiter.getConfig();// 上次配置的限流时间毫秒值Long rateInterval = rateLimiterConfig.getRateInterval();// 上次配置的限流次数Long rate = rateLimiterConfig.getRate();// 将timeOut转换成毫秒之后再跟rateInterval进行比较if (TimeUnit.MILLISECONDS.convert(timeOut, TimeUnit.SECONDS) != rateInterval || count != rate) {// 如果rateLimiterConfig的配置跟我们注解上面的值不一致,说明服务器重启过,程序员又修改了限流的配置// 删除原有配置rateLimiter.delete();// 以程序员重启后的限流配置为准,重新设置rateLimiter.trySetRate(RateType.OVERALL, count, timeOut, RateIntervalUnit.SECONDS);}return rateLimiter;}
}
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 {@Beanpublic UserRateLimiterInterceptor userRateLimiterInterceptor() {return new UserRateLimiterInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userRateLimiterInterceptor()).addPathPatterns("/**");}
}