Redisson 的 RRateLimiter 是一个分布式限流器,用于在分布式系统中控制访问速率。
基本用法
导入jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.24.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
配置类
package com.qfedu.ratelimit.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setRetryInterval(5000)
.setTimeout(10000)
.setConnectTimeout(10000);
return Redisson.create(config);
}
}
通过控制器测试
@RestController
public class TestController {
@Resource
private RedissonClient redissonClient;
@GetMapping("/test")
public String test() {
// 根据key获取限流器实例
RRateLimiter rateLimiter = redissonClient.getRateLimiter("limit:1");
// 初始化限流器,本例表示5秒内产生3个许可
rateLimiter.trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);
// 尝试获取许可,获取到返回true,否则返回false
boolean ret1 = rateLimiter.tryAcquire(1);
System.out.println(ret1);
boolean ret2 = rateLimiter.tryAcquire(1);
System.out.println(ret2);
boolean ret3 = rateLimiter.tryAcquire(1);
System.out.println(ret3);
// 第四次获取许可,返回false,表示不能访问
boolean ret4 = rateLimiter.tryAcquire(1);
System.out.println(ret4);
return "success";
}
}
关于trySetRate的说明
其中trySetRaet()的第一个参数表示RateType有两个值:
-
RateType.OVERALL: 全局限流(所有实例共享) -
RateType.PER_CLIENT: 按客户端限流(需要配置客户端标识)
第二个参数表示限流的最大次数
第三个参数表示限流的时间间隔
第四个参数表示限流的时间间隔的单位
通过查看源码,其内部执行了如下的lua脚本
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]);
其内部使用hsetnx,如果对应的key和field存在,则不会创建field
通过自定义注解和AOP进行封装
工具类
package com.qfedu.ratelimit.aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class IpAddressUtil {
/**
* 获取客户端真实IP地址
*/
public static String getClientIpAddress() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String[] headers = {
"X-Forwarded-For",
"X-Real-IP",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
};
// 1. 检查各种代理头信息
for (String header : headers) {
String ip = request.getHeader(header);
if (isValidIp(ip)) {
return getFirstIp(ip);
}
}
// 2. 直接获取远程地址
String remoteAddr = request.getRemoteAddr();
if (isValidIp(remoteAddr)) {
return remoteAddr;
}
throw new RuntimeException("无法获取远程ip地址");
}
/**
* 验证IP地址是否有效
*/
private static boolean isValidIp(String ip) {
return ip != null &&
!ip.isEmpty() &&
!"unknown".equalsIgnoreCase(ip) &&
!"0:0:0:0:0:0:0:1".equals(ip) &&
!"127.0.0.1".equals(ip);
}
/**
* 处理多个IP的情况(如X-Forwarded-For: client, proxy1, proxy2)
*/
private static String getFirstIp(String ip) {
if (ip.contains(",")) {
String[] ips = ip.split(",");
for (String singleIp : ips) {
String trimmedIp = singleIp.trim();
if (isValidIp(trimmedIp)) {
return trimmedIp;
}
}
}
return ip.trim();
}
}
package com.qfedu.ratelimit.aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class TokenUtils {
/**
* 通过token获取用户id
*
* @return
*/
public static Integer getUidFromToken() {
// 动态获取HttpServletRequest对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
// if (token == null || token.isEmpty()) {
// throw new RuntimeException("请重新登录");
// }
// Claims claims = JwtUtils.parseJWT(token);
// Object uid = claims.get("uid");
// return Integer.valueOf(uid.toString());
// 本例为了方便测试,写死返回的用户id
return 123;
}
}
枚举
package com.qfedu.ratelimit.aspect;
public enum RateLimitTypeEnum {
TYPE_IP(1, "根据IP限流"),
TYPE_USER(2, "根据用户限流");
private int type;
private String desc;
RateLimitTypeEnum(int type, String desc) {
this.type = type;
this.desc = desc;
}
}
自定义注解
package com.qfedu.ratelimit.aspect;
import org.redisson.api.RateIntervalUnit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
RateLimitTypeEnum type() default RateLimitTypeEnum.TYPE_USER;
// String key() default "";
long rate() default 10;
long interval() default 60;
RateIntervalUnit unit() default RateIntervalUnit.SECONDS;
String message() default "请求过于频繁,请稍后再试";
}
切面类
package com.qfedu.ratelimit.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String suffix = null;
if (rateLimit.type() == RateLimitTypeEnum.TYPE_IP) {
suffix = IpAddressUtil.getClientIpAddress();
} else if (rateLimit.type() == RateLimitTypeEnum.TYPE_USER) {
suffix = TokenUtils.getUidFromToken().toString();
}
String key = "rate:limit:" + suffix;
RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
rateLimiter.trySetRate(RateType.OVERALL,
rateLimit.rate(),
rateLimit.interval(),
rateLimit.unit());
if (!rateLimiter.tryAcquire()) {
throw new RuntimeException(rateLimit.message());
}
return joinPoint.proceed();
}
}
测试控制层方法
@RateLimit(type = RateLimitTypeEnum.TYPE_USER, rate = 10, interval = 60)
@GetMapping("/test2")
public String test2() {
return "success";
}
根据配置,执行到第11次时,报错

RRateLimiter使用的限流算法
RRateLimiter 使用的是令牌桶算法(Token Bucket Algorithm)
令牌桶算法的工作机制:
-
令牌生成:系统以固定速率向桶中添加令牌
-
令牌消耗:请求需要获取令牌才能被处理
-
桶容量:桶有最大容量,防止令牌无限累积
-
限流逻辑:当桶中有足够令牌时请求通过,否则被限流
令牌桶 vs 漏桶算法
| 特性 | 令牌桶算法 | 漏桶算法 |
|---|---|---|
| 突发流量 | 允许一定程度的突发 | 严格限制,平滑输出 |
| 灵活性 | 更灵活,可应对突发 | 更严格,输出恒定 |
| 实现复杂度 | 相对简单 | 相对复杂 |
| 适用场景 | API限流、流量控制 | 网络流量整形 |
漏桶算法(Leaky Bucket Algorithm)核心概念:
漏桶算法是一种流量整形算法,它以恒定的速率处理请求,无论输入流量多么不规则。
工作机制:
请求到达,请求以任意速率进入桶中,相当于水流入,水量增加
恒定处理,以固定速率从桶中取出请求处理,相当于水流出,水以恒定速率从底部漏出
溢出拒绝:当桶满时,新请求被丢弃或拒绝,类似水满则溢
1377

被折叠的 条评论
为什么被折叠?



