pom文件引入以下依赖
com.google.guava</groupId>
guava</artifactId>
28.1-jre</version>
</dependency>
RateLimiter简单使用
@Test
public void test1() {
long start = System.currentTimeMillis();
RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个
for (int i = 1; i <= 10; i++) {
limiter.acquire();// 请求RateLimiter, 超过permits会被阻塞
System.out.println("call execute:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
long end = System.currentTimeMillis();
System.out.println("time:" + (end - start) + "'ms");
}
结果
call execute:2019-11-07 10:52:20
call execute:2019-11-07 10:52:21
call execute:2019-11-07 10:52:22
call execute:2019-11-07 10:52:23
call execute:2019-11-07 10:52:24
call execute:2019-11-07 10:52:25
call execute:2019-11-07 10:52:26
call execute:2019-11-07 10:52:27
call execute:2019-11-07 10:52:28
call execute:2019-11-07 10:52:29
time:9000'ms
Controller
@RequestMapping("/ratelimiter")
@ResponseBody
public String ratelimiter() {
if (loadingCacheService.tryAcquire()) {
// 获得令牌(不限制访问)
return "success";
} else {
// 未获得令牌(限制访问)
int a = 1 / 0;// 方便看测试结果
System.out.println(a);
return "error";
}
}
Service
@Override
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
// 每秒控制5个许可
RateLimiter rateLimiter = RateLimiter.create(5.0);
jmeter10个线程测试结果
10个并发请求有6个能获得令牌,结论:RateLimiter.create(n)能获取到n+1个令牌
Controller
@RequestMapping("/iplimiter")
@ResponseBody
public String ipLimiter(HttpServletRequest request) {
String ipAddr = IPUtil.getIpAddr(request);
try {
RateLimiter limiter = loadingCacheService.getIPLimiter(ipAddr);
if (limiter.tryAcquire()) {
// 获得令牌(不限制访问)
return "success";
} else {
// 未获得令牌(限制访问)
int a = 1 / 0;
System.out.println(a);
return "error";
}
} catch (ExecutionException e) {
e.printStackTrace();
}
return ipAddr;
}
Service
@Override
public RateLimiter getIPLimiter(String ipAddr) throws ExecutionException {
return ipRequestCaches.get(ipAddr);
}
LoadingCache, RateLimiter> ipRequestCaches = CacheBuilder.newBuilder()
.maximumSize(1000)// 设置缓存个数
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader, RateLimiter>() {
@Override
public RateLimiter load(String s) throws Exception {
return RateLimiter.create(0.1);// 新的IP初始化 (限流每秒0.1个令牌响应,即10s一个令牌)
}
});
注:主要就是把每一个用户IP的令牌桶RateLimiter给缓存起来,然后每次用户请求接口的时候就去拿一次令牌,拿到令牌则不限制访问,没拿到则限制访问
IPUtil
/**
* 获取客户端IP地址
*/
public class IPUtil {
/**
* 客户端真实IP地址的方法一:
*/
public static String getRemortIP(HttpServletRequest request) {
if (request.getHeader("x-forwarded-for") == null) {
return request.getRemoteAddr();
}
return request.getHeader("x-forwarded-for");
}
/**
* 客户端真实IP地址的方法二:
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = "";
if (request != null) {
ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
}
return ip;
}
}
jmeter10个线程测试结果
同一IP10s才能访问一次接口
pom.xml
org.springframework.boot</groupId>
spring-boot-starter-aop</artifactId>
</dependency>
自定义注解
/**
* 自定义注解可以不包含属性,成为一个标识注解
*/
@Inherited
@Documented
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface IPLimit {
}
切面
@Component
@Scope
@Aspect
public class IPLimitAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private LoadingCacheService loadingCacheService;
@Pointcut("@annotation(cn.com.javakf.ratelimiter.annotation.IPLimit)")
public void ipLimit() {
}
@Around("ipLimit()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object obj = null;
String ipAddr = IPUtil.getIpAddr(request);
RateLimiter limiter = loadingCacheService.getIPLimiter(ipAddr);
if (limiter.tryAcquire()) {
// 获得令牌(不限制访问)
obj = joinPoint.proceed();
} else {
// 未获得令牌(限制访问)
obj = new Result(true, StatusCode.OK, "error");
}
return obj;
}
}
测试
/**
* 基于 自定义注解+切面 的方式实现
*
* @return
*/
@ResponseBody
@IPLimit // 可以非常方便的通过这个注解来实现限流
@RequestMapping("/annotation")
public Result test() {
return new Result(true, StatusCode.OK, "success");
}
参考:
限流算法之漏桶算法、令牌桶算法
Guava RateLimiter实现接口API限流
guava 限流的两种方式
SpringBoot基于RateLimiter+AOP动态的为不同接口限流
代码托管:springboot_ratelimiter