在高并发场景下,限流是控制系统负载、提升稳定性和防止资源过度消耗的重要手段。Guava
提供了 RateLimiter
类来实现简单高效的请求限流。通过 RateLimiter
,你可以轻松限制操作的执行频率,例如每秒、每分钟允许多少次执行。然而,如果没有正确处理等待行为,可能会导致线程被无限期阻塞,影响系统的响应性。
本文将介绍如何使用 Guava RateLimiter
限制操作频率,并探讨如何避免因无限等待导致的线程阻塞问题。
什么是 RateLimiter?
RateLimiter
是 Google Guava 提供的一个类,用于限流控制,它基于令牌桶算法实现。简单来说,RateLimiter
会按照指定的速率生成令牌,每次操作前,线程需要从 RateLimiter
获取一个令牌,只有成功获取令牌后,才能继续执行操作。
例如,如果你设置了 RateLimiter.create(1.0)
,表示每秒最多允许一次操作;如果速率设置为 3.0 / 60.0
,则表示每分钟最多允许 3 次操作。
如何使用 RateLimiter 限制请求频率
首先,RateLimiter
可以通过 RateLimiter.create(double permitsPerSecond)
来设置允许的请求频率。permitsPerSecond
表示每秒允许获取的令牌数量。
示例:每秒限制 1 次操作
import com.google.common.util.concurrent.RateLimiter; public class RateLimiterExample { public static void main(String[] args) { // 创建每秒 1 次的 RateLimiter RateLimiter rateLimiter = RateLimiter.create(1.0); // 模拟多个操作请求 for (int i = 0; i < 5; i++) { // 请求 rateLimiter,获取令牌 rateLimiter.acquire(); // 这里会阻塞直到获取到令牌 performTask(); } } private static void performTask() { System.out.println("Task performed at: " + System.currentTimeMillis()); } }
在这个示例中,rateLimiter.acquire()
会阻塞当前线程,直到能够获得令牌。每秒只能执行一次操作,acquire()
会确保每次请求间隔至少 1 秒。
无限等待的风险
如果你没有设置超时,acquire()
方法将会一直阻塞,直到能够获取到令牌。这种行为可能导致线程无限期地等待,特别是在高并发场景下,可能造成系统性能瓶颈。
无限等待示例
import com.google.common.util.concurrent.RateLimiter; public class InfiniteWaitExample { public static void main(String[] args) { // 每秒 1 次的 RateLimiter RateLimiter rateLimiter = RateLimiter.create(1.0); // 无限等待,直到获得令牌 System.out.println("Acquiring token..."); rateLimiter.acquire(); // 这里会阻塞直到获取到令牌 System.out.println("Token acquired, continuing task..."); // 执行任务 performTask(); } private static void performTask() { System.out.println("Task performed at: " + System.currentTimeMillis()); } }
在上面的代码中,rateLimiter.acquire()
会无限等待,直到能够获取到令牌。这种方式可能导致系统某些部分无法及时响应,特别是在请求量很大时。
如何避免无限等待
为了避免无限等待导致的线程阻塞,我们可以使用 tryAcquire()
方法。该方法允许你设置等待时间,若超时则返回 false
,而不是一直阻塞。
使用 tryAcquire()
设置超时
import com.google.common.util.concurrent.RateLimiter; public class TimeoutRateLimiterExample { public static void main(String[] args) { // 每秒 1 次的 RateLimiter RateLimiter rateLimiter = RateLimiter.create(1.0); // 设置超时等待时间,尝试在 2 秒内获取令牌 if (rateLimiter.tryAcquire(2, java.util.concurrent.TimeUnit.SECONDS)) { System.out.println("Token acquired, continuing task..."); performTask(); } else { System.out.println("Timeout, unable to acquire token within 2 seconds."); } } private static void performTask() { System.out.println("Task performed at: " + System.currentTimeMillis()); } }
在上面的代码中,tryAcquire(2, TimeUnit.SECONDS)
方法会尝试在 2 秒内获取一个令牌。如果在 2 秒内无法获得令牌,它会返回 false
,而不是一直阻塞线程。这样可以避免因等待令牌而导致的线程无限期阻塞。
RateLimiter
与 RateLimiter.create()
方法
RateLimiter.create(double permitsPerSecond)
方法是最常用的创建速率限制器的方式,它接受一个 permitsPerSecond
参数,表示每秒允许获取的令牌数量。这个参数通常用于控制每秒或者每分钟的请求频率。
permitsPerSecond
参数越大,表示单位时间内能够执行的操作越多。- 如果你希望设置每分钟的请求次数,可以将
permitsPerSecond
设置为次数/60
,例如每分钟 3 次操作的速率就是3.0 / 60.0
。
小结
- 限流原理:
RateLimiter
通过令牌桶算法实现限流控制,可以控制操作的执行频率。 - 无限等待的风险:如果没有设置超时,
acquire()
会阻塞当前线程直到获取到令牌,这可能导致系统响应迟缓,特别是在高并发场景下。 - 避免无限等待:通过
tryAcquire()
方法,你可以为RateLimiter
设置超时,避免线程因等待令牌而被阻塞。
使用 RateLimiter
限流时,确保合理设置等待时间和速率限制,可以有效避免系统过载和性能瓶颈问题。