前言
只适用于单体应用
1. 依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
2. 测试
代码中只设置了每秒放一个令牌到,令牌桶
- acquire()
该方法获取令牌,如果你没获取到,就会一直等待,不会丢失请求。 - tryAcquire()
他首先会尝试获取令牌,如果拿到了返回true,没拿到返回false。他在设定的时间内去拿。
package com.jm.study.controller;
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class OrderController {
RateLimiter rateLimiter = RateLimiter.create(1.0);
@RequestMapping("/order")
public String order(){
System.out.println("生成令牌等待时间:" + rateLimiter.acquire());
boolean acquire = rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS);
if (!acquire) {
System.out.println("你在怎么抢,也抢不到,因为会一直等待的,你先放弃吧!");
return "你在怎么抢,也抢不到,因为会一直等待的,你先放弃吧!";
}
return "恭喜您,抢购成功!";
}
@RequestMapping("/order1")
public String order1(){
System.out.println("生成令牌等待时间:" + rateLimiter.acquire());
return "恭喜您,抢购成功!";
}
@RequestMapping("/test")
public void test(){
for(int i = 0;i<10;i++){
order1();
}
}
}
3. 注解版
tryAcquire 默认为false, 就是我们执行 rateLimiter.acquire() 这个方法,他不会丢失请求,
当tryAcquire为true的时候此时timeout参数生效,timeout为超时等待时间,如果等了该时间还没有获取到锁就报错,此时执行rateLimiter.tryAcquire()这个方法,该方法如果获取不到令牌就会导致请求丢失。
在使用过程中根据自己的实际情况使用
package com.jm.study.annotation;
import java.lang.annotation.*;
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRateLimiter {
//向令牌桶放入令牌的速率
double rate() default CommonRate.limitRate;
boolean tryAcquire() default false;
//从令牌桶获取令牌的超时时间
long timeout() default 0;
}
package com.jm.study.aspect;
import com.google.common.util.concurrent.RateLimiter;
import com.jm.study.annotation.MyRateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Configuration
public class MyRateLimiterAspect {
private RateLimiter rateLimiter = RateLimiter.create(2);
@Pointcut("@annotation(com.jm.study.annotation.MyRateLimiter)")
public void pointcut() {
}
/*** 核心切面方法 */
@Around("pointcut()")
public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
//使用反射获取方法上是否存在@MyRateLimiter注解
MyRateLimiter myRateLimiter = signature.getMethod().getDeclaredAnnotation(MyRateLimiter.class);
if (myRateLimiter == null) {
//程序正常执行,执行目标方法
return proceedingJoinPoint.proceed();
}
//获取注解上的参数
// 获取配置的速率
double rate = myRateLimiter.rate();
//获取客户端等待令牌的时间
long timeout = myRateLimiter.timeout();
//设置限流速率
rateLimiter.setRate(rate);
//判断客户端获取令牌是否超时
if(myRateLimiter.tryAcquire()){
boolean tryAcquire = rateLimiter.tryAcquire(timeout, TimeUnit.MILLISECONDS);
if (!tryAcquire) {
//服务降级
fullback();
return null;
}
}else {
rateLimiter.acquire();
}
//获取到令牌,直接执行
return proceedingJoinPoint.proceed();
}
/*** 降级处理 */
private void fullback() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response=attributes.getResponse();
response.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.println("当前访问人数过多,请稍后再试!");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
}
@RequestMapping("/order1")
@MyRateLimiter(rate = 2.0)
public String order1(){
System.out.println("生成令牌等待时间:" + rateLimiter.acquire());
return "恭喜您,抢购成功!";
}
@RequestMapping("/order2")
@MyRateLimiter(rate = 1.0, tryAcquire = true, timeout = 500)
public String order2(){
System.out.println("order2");
return "order2";
}