1. 概述

本文将介绍 Guava 库中的 RateLimiter 类。这个类用于控制操作执行的速率,是处理流量限制的利器。当我们创建一个 RateLimiter 并设置 N 个许可时,意味着每秒最多能处理 N 个许可的请求。

2. Maven 依赖

首先添加 Guava 依赖(最新版本可在 Maven 仓库 查询):

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

3. 创建和使用 RateLimiter

假设我们需要将 doSomeLimitedOperation() 方法的执行速率限制为每秒 2 次。通过 create() 工厂方法创建 RateLimiter 实例:

RateLimiter rateLimiter = RateLimiter.create(2);

获取执行许可需调用 acquire() 方法:

rateLimiter.acquire(1);

验证效果:连续调用两次受限方法,并记录耗时:

long startTime = ZonedDateTime.now().getSecond();
rateLimiter.acquire(1);
doSomeLimitedOperation();
rateLimiter.acquire(1);
doSomeLimitedOperation();
long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;

假设 doSomeLimitedOperation() 立即完成,两次 acquire() 调用应无阻塞,耗时应在 1 秒内:

assertThat(elapsedTimeSeconds <= 1);

关键点:也可一次性获取多个许可,例如发送 100 字节/秒的场景:

@Test
public void givenLimitedResource_whenRequestOnce_thenShouldPermitWithoutBlocking() {
    // given
    RateLimiter rateLimiter = RateLimiter.create(100);

    // when
    long startTime = ZonedDateTime.now().getSecond();
    rateLimiter.acquire(100);  // 一次性获取100许可
    doSomeLimitedOperation();
    long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;

    // then
    assertThat(elapsedTimeSeconds <= 1);
}

4. 阻塞式获取许可

考虑更复杂场景:创建 100 许可/秒的限流器,执行需要 1000 许可的操作。根据设计,这至少需要 10 秒完成:

@Test
public void givenLimitedResource_whenUseRateLimiter_thenShouldLimitPermits() {
    // given
    RateLimiter rateLimiter = RateLimiter.create(100);

    // when
    long startTime = ZonedDateTime.now().getSecond();
    IntStream.range(0, 1000).forEach(i -> {
        rateLimiter.acquire();  // 每次获取1许可
        doSomeLimitedOperation();
    });
    long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;

    // then
    assertThat(elapsedTimeSeconds >= 10);
}

⚠️ 注意acquire() 是阻塞方法!调用时会阻塞线程直到获取许可。无参数调用等价于 acquire(1)

5. 带超时的许可获取

RateLimiter 提供了带超时的 tryAcquire() 方法,避免无限期阻塞:

@Test
public void givenLimitedResource_whenTryAcquire_shouldNotBlockIndefinitely() {
    // given
    RateLimiter rateLimiter = RateLimiter.create(1);

    // when
    rateLimiter.acquire();  // 消耗唯一许可
    boolean result = rateLimiter.tryAcquire(2, 10, TimeUnit.MILLISECONDS);  // 尝试获取2个许可

    // then
    assertThat(result).isFalse();  // 必然失败
}

关键特性

  • 超时时间内获取到许可 → 返回 true
  • 超时未获取到许可 → 立即返回 false
  • 适用于需要快速响应的场景

6. 总结

本文深入探讨了 Guava RateLimiter 的核心用法:

  • 创建限流器:通过 create() 指定许可速率
  • 基本限流:使用 acquire() 进行阻塞式许可获取
  • 超时控制:通过 tryAcquire() 避免无限等待
  • 灵活场景:支持一次性获取多个许可

⚠️ 踩坑提醒:阻塞式 acquire() 在高并发场景可能导致线程堆积,建议优先使用带超时的 tryAcquire()

所有示例代码可在 GitHub 项目 中获取(Maven 项目可直接运行)。


原始标题:Quick Guide to the Guava RateLimiter