配置类封装-网关限流
2022/9/18大约 3 分钟后端Spring框架封装
概述
网关层限流是保护系统的第一道防线,常用方案包括 令牌桶、滑动窗口、漏桶算法。这里介绍基于 Spring Cloud Gateway + Redis + Lua 的通用限流实现。
核心组件
| 组件 | 职责 |
|---|---|
RedisRateLimiter | 基于 Redis 的限流器核心实现 |
GatewayFilter | 网关过滤器,集成限流逻辑 |
Redis Lua Script | 原子性执行限流计数 |
实现方案
1. 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2. Redis Lua 限流脚本
-- 令牌桶算法实现
-- KEYS[1] = 限流 key
-- ARGV[1] = 令牌桶容量
-- ARGV[2] = 每秒补充令牌数
-- ARGV[3] = 当前时间戳
-- ARGV[4] = 请求令牌数
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refillRate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
-- 获取当前令牌数
local bucket = redis.call('hgetall', key)
local tokens = tonumber(bucket[2]) or capacity
local last_refill = tonumber(bucket[4]) or now
-- 计算应补充的令牌数
local elapsed = now - last_refill
local refill = math.floor(elapsed * refillRate / 1000)
tokens = math.min(capacity, tokens + refill)
-- 检查是否允许通过
if tokens >= requested then
tokens = tokens - requested
redis.call('hmset', key, 'tokens', tokens, 'last_refill', now)
redis.call('expire', key, math.ceil(capacity / refillRate) + 1)
return 1 -- 通过
else
return 0 -- 拒绝
end
3. 限流器实现
@Component
public class RedisRateLimiter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LUA_SCRIPT = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refillRate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local bucket = redis.call('hgetall', key)
local tokens = tonumber(bucket[2]) or capacity
local last_refill = tonumber(bucket[4]) or now
local elapsed = now - last_refill
local refill = math.floor(elapsed * refillRate / 1000)
tokens = math.min(capacity, tokens + refill)
if tokens >= requested then
tokens = tokens - requested
redis.call('hmset', key, 'tokens', tokens, 'last_refill', now)
redis.call('expire', key, math.ceil(capacity / refillRate) + 1)
return 1
else
return 0
end
""";
public boolean isAllowed(String key, int capacity, int refillRate) {
Long result = redisTemplate.execute(
new DefaultRedisScript<>(LUA_SCRIPT, Long.class),
Collections.singletonList(key),
String.valueOf(capacity),
String.valueOf(refillRate),
String.valueOf(System.currentTimeMillis()),
"1"
);
return result != null && result == 1;
}
}
4. 网关过滤器
@Component
public class RateLimitGatewayFilterFactory
extends AbstractGatewayFilterFactory<RateLimitGatewayFilterFactory.Config> {
@Autowired
private RedisRateLimiter rateLimiter;
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 1. 获取限流 key(基于 IP / UserId / Path)
String key = resolveKey(request, config);
// 2. 检查限流
boolean allowed = rateLimiter.isAllowed(
key,
config.getCapacity(),
config.getRefillRate()
);
if (!allowed) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("X-RateLimit-Rejected", "1");
return response.setComplete();
}
// 3. 添加限流响应头
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("X-RateLimit-Remaining", getRemaining(key));
return chain.filter(exchange);
};
}
private String resolveKey(ServerHttpRequest request, Config config) {
String path = request.getPath().value();
String remoteIp = request.getRemoteAddress() != null
? request.getRemoteAddress().getAddress().getHostAddress()
: "unknown";
return switch (config.getKeyType()) {
case "ip" -> "rate_limit:ip:" + remoteIp;
case "user" -> "rate_limit:user:" + path;
case "path" -> "rate_limit:path:" + path;
default -> "rate_limit:default:" + path;
};
}
@Data
public static class Config {
private int capacity = 100; // 令牌桶容量
private int refillRate = 10; // 每秒补充令牌数
private String keyType = "ip"; // ip / user / path
}
}
5. 配置示例
spring:
cloud:
gateway:
routes:
- id: api-route
uri: lb://service-name
predicates:
- Path=/api/**
filters:
- name: RateLimit
args:
capacity: 100 # 最大令牌数
refillRate: 10 # 每秒补充令牌数
keyType: ip # 限流维度:ip/user/path
限流策略选择
| 算法 | 特点 | 适用场景 |
|---|---|---|
| 令牌桶 | 允许突发流量,有上限 | API 限流 |
| 滑动窗口 | 计数平滑 | 精确限流 |
| 漏桶 | 流量匀速,不允许突发 | 保护慢速服务 |
常见配置场景
# 场景1:保护登录接口,每IP每秒最多5次
- Path=/api/auth/**
filters:
- RateLimit=capacity=5,refillRate=5,keyType=ip
# 场景2:保护敏感接口,每IP每分钟最多100次
- Path=/api/user/**
filters:
- RateLimit=capacity=100,refillRate=1.67,keyType=ip # 100/60≈1.67
# 场景3:全局通用接口,根据路径限流
filters:
- RateLimit=capacity=1000,refillRate=100,keyType=path
