Guava RateLimiter限流器使用示例

大鵬123發表於2020-07-05

Guava中的RateLimiter可以限制單程式中某個方法的速率,本文主要介紹如何使用,實現原理請參考文件:推薦:超詳細的Guava RateLimiter限流原理解析推薦:RateLimiter 原始碼分析(Guava 和 Sentinel 實現)

1 基於spring-mvc的controller測試限流

完整程式碼可參考:https://github.com/sxpujs/spring-cloud-examples/tree/master/rest-service

1.1 增加Maven依賴:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

1.2 AccessLimitService 限流Service類

@Service
public class AccessLimitService {

    // 每秒發出5個令牌
    RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**
     * 嘗試獲取令牌
     */
    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
}

1.3 控制器類

@RestController
@Slf4j
public class HelloController {

    @Autowired
    private AccessLimitService accessLimitService;

    @RequestMapping("/access")
    public String access() {
        if (accessLimitService.tryAcquire()) {
            log.info("start");
            // 模擬業務執行500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "access success [" + LocalDateTime.now() + "]";
        } else {
            //log.warn("限流");
            return "access limit [" + LocalDateTime.now() + "]";
        }
    }
}

1.4 使用wrk工具模擬客戶端發起多個請求

我們使用HTTP基準工具wrk來生成大量HTTP請求。在終端輸入如下命令來測試:

wrk -t1 -c10 -d2s http://127.0.0.1:8080/access

服務端日誌如下所示(稍做簡化),可以看出前6行的執行時間是一樣的,這是因為RateLimiter的預設實現SmoothBursty會快取1秒的許可,在定義RateLimiter例項時,每秒5個許可,加上新佔用的1個許可,一共有6個。從第7行開始,每0.2秒執行1次,符合預期。

2020-07-05 15:46:16.605  INFO --- [nio-8080-exec-2] HelloController : start
2020-07-05 15:46:16.605  INFO --- [nio-8080-exec-3] HelloController : start
2020-07-05 15:46:16.605  INFO --- [nio-8080-exec-7] HelloController : start
2020-07-05 15:46:16.605  INFO --- [nio-8080-exec-8] HelloController : start
2020-07-05 15:46:16.605  INFO --- [nio-8080-exec-9] HelloController : start
2020-07-05 15:46:16.605  INFO --- [nio-8080-exec-4] HelloController : start
2020-07-05 15:46:16.804  INFO --- [nio-8080-exec-1] HelloController : start
2020-07-05 15:46:17.005  INFO --- [io-8080-exec-11] HelloController : start
2020-07-05 15:46:17.204  INFO --- [nio-8080-exec-9] HelloController : start
2020-07-05 15:46:17.404  INFO --- [nio-8080-exec-5] HelloController : start
2020-07-05 15:46:17.604  INFO --- [nio-8080-exec-8] HelloController : start
2020-07-05 15:46:17.804  INFO --- [nio-8080-exec-2] HelloController : start
2020-07-05 15:46:18.004  INFO --- [nio-8080-exec-7] HelloController : start
2020-07-05 15:46:18.204  INFO --- [nio-8080-exec-6] HelloController : start
2020-07-05 15:46:18.404  INFO --- [nio-8080-exec-5] HelloController : start

2 基於單個類的main方法測試限流

package com.demo.guava;

import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

@Slf4j
public class RateLimiterDemo {

    static void submitTasks1() {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        RateLimiter rateLimiter = RateLimiter.create(5); // rate is "5 permits per second"
        IntStream.range(0, 10).forEach(i -> pool.submit(() -> {
            if (rateLimiter.tryAcquire()) {
                try {
                    log.info("start");
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            } else {
                log.warn("限流");
            }
        }));
        pool.shutdown();
        /*
16:18:18.784 [pool-1-thread-1] INFO  RateLimiterDemo - start
16:18:18.784 [pool-1-thread-7] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-2] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-4] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-5] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-6] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-9] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-3] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-10] WARN  RateLimiterDemo - 限流
16:18:18.784 [pool-1-thread-8] WARN  RateLimiterDemo - 限流
         */
    }

    static void submitTasks2() {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        RateLimiter rateLimiter = RateLimiter.create(5); // rate is "5 permits per second"
        IntStream.range(0, 10).forEach(i -> pool.submit(() -> {
            rateLimiter.acquire();
            log.info("start");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));
        pool.shutdown();
        /*
16:18:56.030 [pool-1-thread-1] INFO  RateLimiterDemo - start
16:18:56.227 [pool-1-thread-10] INFO  RateLimiterDemo - start
16:18:56.428 [pool-1-thread-9] INFO  RateLimiterDemo - start
16:18:56.627 [pool-1-thread-8] INFO  RateLimiterDemo - start
16:18:56.827 [pool-1-thread-7] INFO  RateLimiterDemo - start
16:18:57.028 [pool-1-thread-6] INFO  RateLimiterDemo - start
16:18:57.226 [pool-1-thread-5] INFO  RateLimiterDemo - start
16:18:57.426 [pool-1-thread-4] INFO  RateLimiterDemo - start
16:18:57.629 [pool-1-thread-3] INFO  RateLimiterDemo - start
16:18:57.826 [pool-1-thread-2] INFO  RateLimiterDemo - start
         */
    }

    static void submitTasks3() {
        RateLimiter r = RateLimiter.create(5);
        log.info("start");
        for (;;) {
            log.info("get 1 tokens: " + r.acquire() + "s");
        }
        /*
16:15:46.310 [main] INFO RateLimiterDemo - start
16:15:46.315 [main] INFO RateLimiterDemo - get 1 tokens: 0.0s
16:15:46.513 [main] INFO RateLimiterDemo - get 1 tokens: 0.193752s
16:15:46.709 [main] INFO RateLimiterDemo - get 1 tokens: 0.194875s
16:15:46.911 [main] INFO RateLimiterDemo - get 1 tokens: 0.199033s
16:15:47.113 [main] INFO RateLimiterDemo - get 1 tokens: 0.197833s
16:15:47.312 [main] INFO RateLimiterDemo - get 1 tokens: 0.195898s
         */
    }

    static void submitTasks4() {
        RateLimiter r = RateLimiter.create(5);
        log.info("start");
        for (;;) {
            if (r.tryAcquire()) {
                log.info("run");
            }
        }
        /*
16:17:17.098 [main] INFO  RateLimiterDemo - start
16:17:17.100 [main] INFO  RateLimiterDemo - run
16:17:17.296 [main] INFO  RateLimiterDemo - run
16:17:17.496 [main] INFO  RateLimiterDemo - run
16:17:17.696 [main] INFO  RateLimiterDemo - run
         */
    }

    public static void main(String[] args) throws InterruptedException {
        //submitTasks1();
        submitTasks2();
        //submitTasks3();
        //submitTasks4();
    }
}

參考文件:

相關文章