项目场景一:基于RateLimiter的接口限流方案

技术介绍

RateLimiter 从概念上来讲,速率限制器会在可配置的速率下分配许可证。如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证。一旦获取到许可证,不需要再释放许可证。

原理

RateLimiter 使用的是一种叫令牌桶的流控算法,令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。

令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。

RateLimiter 会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。

RateLimiter 经常用于限制对一些物理资源或者逻辑资源的访问速率。与Semaphore 相比,Semaphore 限制了并发访问的数量而不是使用速率。(注意尽管并发性和速率是紧密相关的,比如参考Little定律)

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

方法摘要

在这里插入图片描述

解决方案

  • 采用aop思想实现,对全部controller层方法进行限制。
  • 限流方式:RateLimiter + Cache(Guava),采用Cache存储某个”ip+方法“对应的RateLimiterr,并设置过期策略。

具体实现

1.引入依赖

<!--Guava-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.1-jre</version>
</dependency>
<!--aop-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.自定义注解

@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
/**
 * @功能描述 平滑突发限流注解,接口限流自定义注解
 * @author zjq
 * @date 2024-03-04
 */
public @interface RateLimit {

    /**
     * @return 每秒向令牌桶发放的令牌数量,不填默认100
     * */
    double rate() default 100;

    /**
     * @return 超时等待,不填默认0
     * */
    long timeOut() default 0;

    /**
     * @return 超时等待时间单位
     * */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

}

3.编写切面

@Aspect
@Component
/**
 * @功能描述 接口限流AOP切面
 * @author zjq
 * @date 2024-03-04
 */
@Order(1001)
public class RateLimitConfig {

    private static RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE);

    /**
     * 业务层切点
     */
    @Pointcut("@annotation(com.maizhiyu.yzt.annotation.RateLimit)"
            /*+ "|| execution(* com.jeethink.boot..*Controller.*(..))"*/)
    public void RateLimiterAspect() {
    }

    @Around("RateLimiterAspect()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        long timeOut = 0;
        TimeUnit timeUnit = TimeUnit.MILLISECONDS;
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            if (method.isAnnotationPresent(RateLimit.class)) {
                //获取方法上的注解对象@RateLimit
                RateLimit rateLimitAnnotation = method.getAnnotation(RateLimit.class);
                //核心方法,内部采用令牌捅算法实现,每秒只发出X个令牌,既允许每秒X个不同线程访问
                rateLimiter.setRate(rateLimitAnnotation.rate());
                timeOut = rateLimitAnnotation.timeOut();
                timeUnit = rateLimitAnnotation.timeUnit();
            } else {
                //100QPS
                rateLimiter.setRate(100);
            }
            //tryAcquire()是非阻塞, rateLimiter.acquire()是阻塞的
            if (rateLimiter.tryAcquire(timeOut, timeUnit)) {
                obj = joinPoint.proceed();
            } else {
                //拒绝了请求(服务降级)
                return Result.failure(10000, "系统繁忙,请稍后再试!");
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return obj;
    }
}

4.添加注解

    @GetMapping("/getBillListNew")
    @RateLimit(rate = 1,timeOut = 50,timeUnit = TimeUnit.MILLISECONDS)
    public Result<BuAchievementsVo> getBillListNew(Integer timeType, String time, String departmentId, String name) {
        HsUser user = jwtUtil.getCurrentUser();
        Long customerId =user.getCustomerId();
        String doctorId =user.getHisId();
        Long userId = user.getId();
        BuAchievementsVo vo = buPrescriptionItemTaskService.cureAchievements(customerId,timeType,time,departmentId,name,doctorId,userId);
        return Result.success(vo);
    }
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值