RateLimiter

参考:

https://www.jianshu.com/p/5d4fe4b2a726

https://blog.csdn.net/charleslei/article/details/53152883 

Guava 的 RateLimiter 采用令牌桶算法

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

示例

如果我们要以 1000 的 tps 完成某个数据同步任务。

例1:传输速度比 RateLimiter 设定 tps 快,通过 RateLimiter 降低传输 tps。

    public static void main(String[] args) throws Exception {
        int[] messageCountList = new int[] {10000, 1000, 100};

        // 1000 tps
        RateLimiter rateLimiter = RateLimiter.create(1000);
        for (int messageCount : messageCountList) {
            rateLimiter.acquire(messageCount);
            fastRun(messageCount);
        }
    }

    private static void fastRun(int messageCount) {
        SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss:SSS");
        System.out.println("transfer start time : " + formatter.format(new Date()));
        System.out.println("transfered message count : " + messageCount);
        System.out.println("transfer end time : " + formatter.format(new Date()));
    }

输出:
start time : 03-十一月-2019 00:08:41:497
transfer start time : 03-十一月-2019 00:08:41:515
transfered message count : 10000
transfer end time : 03-十一月-2019 00:08:41:515
transfer start time : 03-十一月-2019 00:08:51:520
transfered message count : 1000
transfer end time : 03-十一月-2019 00:08:51:520
transfer start time : 03-十一月-2019 00:08:52:519
transfered message count : 100
transfer end time : 03-十一月-2019 00:08:52:519

可见:
1. 第一次尽管需要 10000 个 permits,但仍然是立即就拿到了。
2. 第二次只需要 1000 个 permits,但需要补足第一次欠的 10000 个 permits,所以等了 10s。
3. 第三次补足第二次欠的 1000 个 permits,所以等了 1s。
4. 前后共耗时:11s。显然尽管消息传输很快,但总传输速度被降下来了。
5. 从上可知:获取 permits 时,是先获取,再补足的方式。


例2:传输速度比 RateLimiter 设定的 tps 慢。

   private static void slowRun(int messageCount) throws Exception {
        Thread.sleep(messageCount * 2);
        SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss:SSS");
        System.out.println("transfer start time : " + formatter.format(new Date()));
        System.out.println("transfered message count : " + messageCount);
        System.out.println("transfer end time : " + formatter.format(new Date()));
    }

    public static void main(String[] args) throws Exception {
        int[] messageCountList = new int[] {10000, 1000, 100};

        // 1000 tps
        RateLimiter rateLimiter = RateLimiter.create(1000);
        for (int messageCount : messageCountList) {
            rateLimiter.acquire(messageCount);
            slowRun(messageCount);
        }
    }

输出:
start time : 03-十一月-2019 00:06:43:688
transfer start time : 03-十一月-2019 00:06:43:704
transfered message count : 10000
transfer end time : 03-十一月-2019 00:07:03:707
transfer start time : 03-十一月-2019 00:07:03:707
transfered message count : 1000
transfer end time : 03-十一月-2019 00:07:05:709
transfer start time : 03-十一月-2019 00:07:05:709
transfered message count : 100
transfer end time : 03-十一月-2019 00:07:05:912


可见:
1. 第一次获取 10000 个 permits,仍然是立即就拿到了。
2. 第二次获取 1000 个 permits 时,由于第一次的 10000 条数据传输过慢,RateLimiter 已经累积了许多 permits,所以不需等待,直接获取了 1000 个 permits。
3. 总共耗时 22s,由于 RateLimiter 的 tps 比传输速度快,所以 RateLimter 并不会额外限流。

RateLimite 源代码

public double acquire() {
        return acquire(1);
    }

 public double acquire(int permits) {
        checkPermits(permits);  //检查参数是否大于0
        long microsToWait;
        synchronized (mutex) {  //应对并发情况需要同步
            microsToWait = reserveNextTicket(permits, readSafeMicros());   //获得需要等待的时间,前面例子已经看出,RateLimiter 对于第一次 acquire 令牌,是立即准许,而在第二次 acquire 令牌时,再补偿性加等待时间以限流的。
        }
        ticker.sleepMicrosUninterruptibly(microsToWait);   //等待,当未达到限制时,microsToWait为0
        return 1.0 * microsToWait / TimeUnit.SECONDS.toMicros(1L);
    }

private long reserveNextTicket(double requiredPermits, long nowMicros) {
        resync(nowMicros);  //补充之前 acquire 超用的令牌
        long microsToNextFreeTicket = nextFreeTicketMicros - nowMicros;
        double storedPermitsToSpend = Math.min(requiredPermits, this.storedPermits);   //获取这次请求消耗的令牌数目
        double freshPermits = requiredPermits - storedPermitsToSpend;

        long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
                + (long) (freshPermits * stableIntervalMicros); 

        this.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
        this.storedPermits -= storedPermitsToSpend;  // 减去消耗的令牌
        return microsToNextFreeTicket;
    }

private void resync(long nowMicros) {
        // if nextFreeTicket is in the past, resync to now
        // 这里就是补偿超用的令牌
        if (nowMicros > nextFreeTicketMicros) {
            storedPermits = Math.min(maxPermits,
                    storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
            nextFreeTicketMicros = nowMicros;
        }
    }

可见 RateLimiter 并非是很直观地开一个线程不停往令牌桶中放令牌,而是比较巧妙地采用先使用后补偿的形式。

 

要在Spring Boot中关闭RateLimiter,你可以采取以下步骤: 1. 导入所需的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> ``` 2. 创建一个自定义注解,用于标记需要限流的方法: ```java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimited { String value() default ""; } ``` 3. 创建一个切面类,用于实现限流逻辑: ```java import com.google.common.util.concurrent.RateLimiter; 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.springframework.stereotype.Component; @Aspect @Component public class RateLimitAspect { private final RateLimiter rateLimiter = RateLimiter.create(10); // 设置每秒最大访问次数 @Pointcut("@annotation(com.example.RateLimited)") public void rateLimited() {} @Around("rateLimited()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { if (rateLimiter.tryAcquire()) { return joinPoint.proceed(); } else { throw new RuntimeException("请求过于频繁,请稍后再试!"); } } } ``` 4. 在需要进行限流的方法上添加自定义注解: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MyController { @RateLimited // 添加此注解进行限流 @GetMapping("/my-api") public String myApi() { // 处理业务逻辑 return "Hello World!"; } } ``` 这样,当请求频率超过每秒最大访问次数时,将会抛出一个RuntimeException。你可以根据实际需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值