RateLimiter API 限流

本文介绍了Guava库中的RateLimiter和JDK的Semaphore在限流策略上的区别,RateLimiter基于速率限流,Semaphore基于并发量限流。通过一个自定义注解和AOP的示例展示了RateLimiter的使用,用于限制QPS。在分布式场景下,可以结合Redis实现分布式限流。

RateLimiter API限流


注:并没有做深入研究,只是简单记录 RateLimiter 的使用方法。

RateLimiter 与 Semphore :

  • RateLimiter 是 google 的 guava 工具包中提供的限流工具类,而 Semphore 是 jdk 中提供的信号量类。两者都可做限流。
  • RateLimiter 是基于速率限流,常见算法有漏桶算法和令牌桶算法;Sempgore 是基于并发量限流(也就是数量),它是通过线程个数去限流的。
  • RateLimiter 的原理是系统以一个恒定的速度往桶里放令牌,请求到达时先从桶里获取令牌,然后处理请求,若没有获取到令牌则不处理。当桶里的令牌被获取完,之后到达的请求只能等待,直到下一个令牌产生。当桶里的令牌到达某一个数量时,新产生的令牌会被丢弃。
  • Semphore 的原理时通过限制线程个数来达到限流的目的。处理完请求后需要被释放,才能处理下一个。例如 Semphore 的并发量是10个,当第十一个请求到达后,只能等前十个中的某一个请求处理完并释放令牌后才能被处理;RateLimiter 的并发量是10个,当第十一个请求到达后,如果桶里有令牌那就可被处理,如果没有,则等待下一个令牌产生就可被处理。

以 RateLimiter 为例 :

​ 自定义注解和 aop 搭配的方式,如果是分布式则可使用 redis 配合。

1、自定义注解(此处只作为一个标识)

@Inherited   // 允许被继承
@Target(ElementType.METHOD)   // 使用范围: 方法
@Retention(RetentionPolicy.RUNTIME)   // 有效范围: 运行时
public @interface RateLimit {
   // 只作为标识
}

2、aop 切面处理

@Aspect
@Component
public class RateLimitAop {

    @Autowired
    private HttpServletResponse response;
    private RateLimiter limiter = RateLimiter.create(3.0);   // QPS

    /**
     * 切点
     */
    @Pointcut("@annotation(org.xgllhz.base.common.annotation.RateLimit)")
    public void rateLimitPointcut() {
        //
    }

    /**
     * 通知
     * @param joinPoint
     * @return
     */
    @Around("rateLimitPointcut()")
    public Object rateLimitAround(ProceedingJoinPoint joinPoint) {
        Boolean flag = limiter.tryAcquire();   // 尝试获取令牌
        Object object = null;
        try {
            if (flag) {   // 如果获取到令牌
                object = joinPoint.proceed();   // 处理请求
                System.out.println("flag: " + flag + " object: " + object);
            } else {   // 如果未获取到则返回
                System.out.println("flag: " + flag + " object: " + object);
                ObjectMapper mapper = new ObjectMapper();
                PrintWriter writer = response.getWriter();
                writer.write(mapper.writeValueAsString(new APIResponse<>(ConstConfig.RE_REQUEST_BUSY_CODE,
                        ConstConfig.RE_REQUEST_BUSY_MSG)));
                writer.flush();
                writer.close();
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return object;
    }
}

3、demo

@RestController
@RequestMapping("/common")
public class CommonController {

    @RateLimit
    @RequestMapping("/test")
    public APIResponse<Map<String, Object>> test() {
        return new APIResponse<>();
    }
}

4、测试结果

  • 控制台

    flag: true object: APIResponse(recode=200, remsg=请求成功!, body={})
    flag: true object: APIResponse(recode=200, remsg=请求成功!, body={})
    flag: true object: APIResponse(recode=200, remsg=请求成功!, body={})
    flag: true object: APIResponse(recode=200, remsg=请求成功!, body={})
    flag: false object: null
    flag: false object: null
    flag: false object: null
    flag: false object: null
    flag: false object: null
    flag: true object: APIResponse(recode=200, remsg=请求成功!, body={})
    
  • JMeter

在这里插入图片描述

在这里插入图片描述

注:关于 aop 中 response 对象的获取,提以下几点 :

  • 注解方式

    @Autowried
    private HttpServletResponse response;
    
  • 参数方式

    public void test(HttpServletResponse response){}
    // 这种方式在普通方法中可以获取到,但在被 @Around、@Before 等切面通知注解  
    //作用的方法上获取不到,因为这类方法的参数不接受 HttpServletResponse 的类型参数,所以这里不适用。
    
  • 上下文获取

    // 1、在 web.xml 文件中加入以下配置
    <listener>
          	<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    // 2、在程序中加入以下代码
    HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder
                                  .getRequestAttributes()).getRequest();
    HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder
                                  .getRequestAttributes()).getResponse();
    // 注:在低版本的 spring 中,ServletRequestAttributes 是没有 getResponse() 方法的,
    //只有 getRequest()方法,所以在低版本的 spring 中用这种方式获取不到 response 对象。
    
  • 第四种方式

    /**
    当 spring 版本过低 时可以使用如下方式:
    在被 @RateLimit 注解作用的方法中加入 HttpServletResponse 类型的形参,
    然后再通知方法里通过获取被作用方法的参数来获取。
    */
    
    // 被 @RateLimit 注解作用的方法(response 参数的顺序很重要)
    public void test(HttpServletResponse response){}
    
    // 通知方法
    @Around("rateLimitPointcut()")
    public Object rateLimitAround(ProceedingJoinPoint joinPoint) {
        Object[] objects = joinPoint.getArgs();   // 被作用方法的参数列表
        HttpServletResponse response = (HttpServletResponse) objects[0];
    }
    
    // 注:前置通知(@Before)的通知方式中不能添加 response 响应
    

@XGLLHZ - Journey.mp3

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红衣女妖仙

行行好,给点吃的吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值