RateLimiter基于令牌桶算法(Token Bucket Algorithm)实现。该算法通过以恒定的速度向桶中添加令牌,并且每当有请求来时,需要从桶中取出一个或多个令牌才能继续执行。如果桶中没有足够的令牌,请求将被限流,即延迟处理或拒绝服务。
Guava的RateLimiter具有以下主要特性:
RateLimiter提供了以下主要功能:
RateLimiter.create(double permitsPerSecond)
方法可以创建一个RateLimiter实例,指定每秒生成的令牌数。acquire()
方法可以获取一个令牌,如果桶中没有令牌,该方法会阻塞直到有令牌可用。此外,还提供了tryAcquire()
方法用于非阻塞地尝试获取令牌。RateLimiter.create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)
方法可以创建一个带有预热期的RateLimiter实例。使用RateLimiter的基本步骤如下:
acquire()
或tryAcquire()
方法获取令牌。RateLimiter适用于多种场景,包括但不限于:
以下是一个RateLimiter
使用案例,其中包含了限制API请求频率和用户登录次数的场景。
import com.google.common.util.concurrent.RateLimiter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class AdvancedRateLimiterDemo {
// 存储每个用户的API请求RateLimiter
private static final Map<String, RateLimiter> apiRateLimiters = new HashMap<>();
// 存储每个用户的登录尝试RateLimiter
private static final Map<String, RateLimiter> loginRateLimiters = new HashMap<>();
// 用于创建API请求RateLimiter的工厂方法
public static RateLimiter createApiRateLimiter(double permitsPerSecond) {
return RateLimiter.create(permitsPerSecond); // 每秒生成的令牌数
}
// 用于创建登录尝试RateLimiter的工厂方法
public static RateLimiter createLoginRateLimiter(double permitsPerSecond) {
return RateLimiter.create(permitsPerSecond);
}
// 获取或创建用户的API请求RateLimiter
public static RateLimiter getApiRateLimiter(String userId) {
return apiRateLimiters.computeIfAbsent(userId, k -> createApiRateLimiter(10.0)); // 每秒最多10个API请求
}
// 获取或创建用户的登录尝试RateLimiter
public static RateLimiter getLoginRateLimiter(String userId) {
return loginRateLimiters.computeIfAbsent(userId, k -> createLoginRateLimiter(1.0)); // 每秒最多1次登录尝试
}
// 模拟API请求
public static boolean tryApiRequest(String userId) {
RateLimiter rateLimiter = getApiRateLimiter(userId);
if (!rateLimiter.tryAcquire()) {
System.out.println("API请求过于频繁,请稍后再试。用户ID: " + userId);
return false;
}
// 这里可以执行实际的API请求逻辑
System.out.println("API请求成功处理。用户ID: " + userId);
return true;
}
// 模拟用户登录尝试
public static boolean tryLoginAttempt(String userId) {
RateLimiter rateLimiter = getLoginRateLimiter(userId);
if (!rateLimiter.tryAcquire()) {
System.out.println("登录尝试过于频繁,请稍后再试。用户ID: " + userId);
return false;
}
// 这里可以执行实际的登录验证逻辑
System.out.println("登录尝试成功处理。用户ID: " + userId);
return true;
}
public static void main(String[] args) {
// 模拟同一用户连续发送多个API请求
String apiUserId = "api-user-123";
for (int i = 0; i < 15; i++) {
new Thread(() -> tryApiRequest(apiUserId)).start();
try {
TimeUnit.MILLISECONDS.sleep(500); // 每隔500毫秒发送一个请求
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 模拟同一用户连续尝试登录
String loginUserId = "login-user-456";
for (int i = 0; i < 10; i++) {
new Thread(() -> tryLoginAttempt(loginUserId)).start();
try {
TimeUnit.MILLISECONDS.sleep(200); // 每隔200毫秒尝试一次登录
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在这个示例中,我们定义了两个Map
来分别存储用户的API请求RateLimiter和登录尝试RateLimiter。我们使用了computeIfAbsent
方法来确保每个用户都拥有自己独立的RateLimiter实例。
tryApiRequest
方法模拟了API请求的限流逻辑。如果用户请求过于频繁(即RateLimiter没有可用的令牌),则输出提示信息并返回false
。否则,执行API请求的逻辑(在此处为打印语句)并返回true
。
类似地,tryLoginAttempt
方法模拟了用户登录尝试的限流逻辑。如果用户登录尝试过于频繁,则同样输出提示信息并返回false
。否则,执行登录验证的逻辑(在此处为打印语句)并返回true
。
在main
方法中,我们模拟了同一用户连续发送多个API请求和连续尝试登录的场景。由于RateLimiter的限制,部分请求和登录尝试将会因为频率过高而被拒绝。
Guava的RateLimiter基于令牌桶算法实现,但进行了优化以支持平滑的突发流量处理。它内部使用了一个稳定的令牌产生速率和一个可配置的桶容量。当请求到达时,RateLimiter会根据当前的令牌数量和产生速率来决定是否立即处理请求、延迟处理请求还是拒绝请求。这种机制确保了系统在处理突发流量时能够保持稳定的性能。
在实际项目中运用RateLimiter时,以下是一些建议的最佳实践:
总之,Guava的RateLimiter是一个强大且灵活的组件,能够帮助开发者优雅地实现速率限制。通过深入了解其原理、特性、功能和使用方法,并结合实际项目的需求进行最佳实践的运用,我们可以更好地保护系统免受过量请求的损害并提高系统的稳定性和可伸缩性。
术因分享而日新,每获新知,喜溢心扉。 诚邀关注公众号 『
码到三十五
』 ,获取更多技术资料。