- 作者:老汪软件技巧
- 发表时间:2024-09-01 07:01
- 浏览量:
一、背景
最近,我接手了一个新项目,其中使用了Spring Cloud Gateway技术。这个项目有些接口的qps比较大,但是项目中的限流功能并没有采用公司统一封装的Sentinel组件,而是直接使用了Spring Cloud Gateway的限流组件。之前对于Spring Cloud Gateway使用比较少,还是有点头疼哦,他的代码大部分用的响应式编程,和平时写的代码相比,更难理解,最近花了很多时间去理解业务代码。。。
为了尽快熟悉并掌握这部分功能的实现原理,我深入研究了Spring Cloud Gateway的限流机制,特别是其核心实现方式。本文将按照有输入必须要有输出的理念,通过源码分析详细介绍Spring Cloud Gateway的限流原理。
二、源码导读
在Spring Cloud Gateway中,限流的关键组件包括RedisRateLimiter类和RequestRateLimiterGatewayFilterFactory过滤器工厂。RedisRateLimiter通过Lua脚本与Redis交互,实现令牌桶算法的限流逻辑,而RequestRateLimiterGatewayFilterFactory则将该限流功能应用于具体的路由。
三、限流核心源码解析1、RedisRateLimiter类解析
RedisRateLimiter是Spring Cloud Gateway中实现限流逻辑的核心类。其主要通过令牌桶算法实现限流,这里详细分析一下它的主要方法isAllowed。
public Mono isAllowed(String routeId, String id) {
// 检查是否已初始化
if (!this.initialized.get()) {
throw new IllegalStateException("RedisRateLimiter is not initialized");
} else {
// 加载配置
Config routeConfig = this.loadConfiguration(routeId);
int replenishRate = routeConfig.getReplenishRate(); // 每秒令牌恢复速率
int burstCapacity = routeConfig.getBurstCapacity(); // 桶的容量,即最多能存多少令牌
try {
// 构建Redis操作的Key
List keys = getKeys(id);
// Lua脚本的参数
List scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", Instant.now().getEpochSecond() + "", "1");
// 执行Lua脚本,限流判断
Flux> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
return flux.onErrorResume((throwable) -> {
return Flux.just(Arrays.asList(1L, -1L)); // 出错时默认允许请求通过
}).reduce(new ArrayList(), (longs, l) -> {
longs.addAll(l);
return longs;
}).map((results) -> {
boolean allowed = (Long)results.get(0) == 1L; // 判断请求是否被允许
Long tokensLeft = (Long)results.get(1); // 剩余令牌数
RateLimiter.Response response = new RateLimiter.Response(allowed, this.getHeaders(routeConfig, tokensLeft));
if (this.log.isDebugEnabled()) {
this.log.debug("response: " + response);
}
return response;
});
} catch (Exception var9) {
Exception e = var9;
this.log.error("Error determining if user allowed from redis", e);
return Mono.just(new RateLimiter.Response(true, this.getHeaders(routeConfig, -1L))); // 出现异常时允许请求通过
}
}
}
这个方法是整个限流逻辑的核心,负责检查某个请求是否被允许通过。通过加载配置,我们可以获取到replenishRate(令牌的生成速率)和burstCapacity(桶的最大容量)。接着,通过构建Redis的Key并传入Lua脚本参数,执行限流判断。如果脚本返回的第一个值为1,则表示允许请求,否则不允许。
2、Lua脚本的关键逻辑
Lua脚本在Redis中执行,确保限流操作的原子性。下面是Lua脚本的核心逻辑:
-- 获取上次的令牌数和刷新时间
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = 0
end
-- 计算从上次到现在的时间差,增加相应的令牌数
local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(capacity, last_tokens + (delta * rate))
-- 判断令牌是否足够
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
new_tokens = filled_tokens - requested
allowed_num = 1
end
-- 更新Redis中的令牌数和时间戳
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed_num, new_tokens }
关键点在于这个Lua脚本会根据当前时间与上次操作时间的差值,计算应该补充的令牌数,然后判断当前令牌是否足够请求使用。如果足够,则扣减相应的令牌,并返回允许的标志。下面是lua脚本限流逻辑的流程图。
3、RequestRateLimiterGatewayFilterFactory 过滤器工厂
RequestRateLimiterGatewayFilterFactory是Spring Cloud Gateway的一个过滤器工厂类,用于将限流逻辑应用到指定的路由中。以下是该类的关键实现:
public GatewayFilter apply(Config config) {
// 获取限流的KeyResolver和RateLimiter
KeyResolver resolver = (KeyResolver)this.getOrDefault(config.keyResolver, this.defaultKeyResolver);
RateLimiter
这个过滤器会在请求到达时解析出限流Key(如用户ID或IP),然后调用RateLimiter进行限流判断。如果超过限流,则返回相应的状态码并终止请求链,否则允许请求继续执行。
四、使用示例
为了帮助大家理解,下面是一个使用RequestRateLimiterGatewayFilterFactory的示例配置:
spring:
cloud:
gateway:
routes:
- id: demo_service
uri: http://localhost:8081
predicates:
- Path=/demo/api/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒生成10个令牌
redis-rate-limiter.burstCapacity: 20 # 令牌桶最大容量为20
key-resolver: "#{@userKeyResolver}" # 自定义KeyResolver,根据用户ID进行限流
在这个配置中,我们为/demo/api/**路径的请求配置了限流规则:
replenishRate: 每秒生成10个令牌。
burstCapacity: 令牌桶的最大容量为20个。
key-resolver: 使用自定义的KeyResolver,根据用户ID来区分请求。
五、总结
源码看完后,Spring Cloud Gateway的限流功能还是比较好理解的,和公司统一封装的Sentinel框架相比,它没有可视化页面配置限流规则的功能,这个需要到配置中心配置限流规则。
Spring Cloud Gateway的限流机制通过Redis的Lua脚本实现了一个轻量级的令牌桶算法,并通过配置灵活地控制各个服务的请求速率。通过对源码的分析和实际的使用示例,我们能够更好地理解其工作原理,并在项目中合理配置限流策略。