分布式限流器 - 设计理念与架构
2025/11/13大约 6 分钟
分布式限流器 - 设计理念与架构
一、设计理念
1.1 为什么需要分布式限流?
在微服务架构中,API网关作为流量入口,面临以下挑战:
- 流量突发:秒杀、促销等场景下,瞬时流量可能超过系统承载能力
- 恶意攻击:DDoS攻击、爬虫等恶意请求消耗系统资源
- 资源保护:保护后端服务不被打垮,避免雪崩效应
- 多节点协同:网关集群需要跨节点的统一限流控制
1.2 核心设计思想
多级防护策略
设计原理:
本地限流作为第一道防线
- 设置为配置值的 1.2 倍,留有 20% 余量
- 避免所有请求都打到 Redis,保护 Redis 不被打垮
- 单机限流,响应速度极快(纳秒级)
Redis 限流作为精确控制
- 使用 Lua 脚本保证原子性
- 实现跨网关节点的精确限流
- 分布式协同,全局一致性
降级策略
- Redis 异常时自动降级为本地限流
- 保证服务可用性,避免限流器成为单点故障
1.3 限流粒度设计
限流器支持四个维度的限流粒度,从粗到细,层层防护:
| 粒度级别 | 限流目标 | 应用场景 | 优先级 |
|---|---|---|---|
| GLOBAL | 整个网关集群 | 保护网关整体承载能力 | 最高 |
| SERVICE | 特定后端服务 | 保护后端服务不被打垮 | 高 |
| INTERFACE | 特定接口路径 | 保护核心接口(如登录、支付) | 中 |
| IP | 客户端IP地址 | 防止恶意攻击、爬虫 | 低 |
粒度选择原则:
// 限流检查顺序(从粗到细)
1. 全局限流检查 (GLOBAL)
↓ 通过
2. 服务级限流检查 (SERVICE)
↓ 通过
3. 接口级限流检查 (INTERFACE)
↓ 通过
4. IP级限流检查 (IP)
↓ 通过
5. 放行请求粒度组合示例:
场景:保护用户服务的登录接口
- GLOBAL: 10000 QPS (保护网关整体)
- SERVICE: 2000 QPS (保护用户服务)
- INTERFACE: 500 QPS (保护登录接口)
- IP: 10 QPS (防止单个IP暴力破解)二、系统架构
2.1 整体架构图
2.2 核心组件
2.2.1 配置监听器 (RateLimitConfigListener)
职责:
- 启动时从 Redis 加载所有限流配置
- 订阅 Redis 的配置更新消息
- 动态更新本地限流器配置
关键代码:
@Component
public class RateLimitConfigListener implements MessageListener {
@PostConstruct
public void init() {
// 启动时加载所有配置
loadAllRateLimitConfigs();
// 订阅配置更新消息
redisMessageListenerContainer.addMessageListener(
this,
new PatternTopic("rate-limit-config-update")
);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String body = new String(message.getBody());
if ("RELOAD_ALL".equals(body)) {
// 全量重载
loadAllRateLimitConfigs();
} else {
// 增量更新
RateLimitConfig config = JSON.parseObject(body, RateLimitConfig.class);
rateLimiter.updateConfig(buildConfigKey(config), config);
}
}
}2.2.2 分布式限流器 (DistributedRateLimiter)
职责:
- 管理本地限流器(Guava RateLimiter)
- 执行 Redis 分布式限流判断
- 实现降级策略
核心方法:
public boolean tryAcquire(String key, RateLimitConfig config) {
// 1. 检查配置是否启用
if (!config.getEnabled()) {
return true;
}
// 2. 本地限流检查
if (!tryAcquireLocal(key, config)) {
return false;
}
// 3. Redis 分布式限流检查
try {
return tryAcquireDistributed(key, config);
} catch (Exception e) {
log.error("Redis限流异常,降级为本地限流", e);
return true; // 降级策略:Redis异常时放行
}
}2.2.3 限流前置处理器 (RateLimitPreHandler)
职责:
- 作为 Handler 集成到请求处理链
- 按粒度顺序执行限流检查
- 限流失败时快速返回
关键代码:
@Component
@Order(10)
public class RateLimitPreHandler implements CustomPreHandler {
@Override
public Result<Void> handle(HttpStatement httpStatement, FullHttpRequest request) {
// 1. 全局限流
if (!checkGlobalLimit()) {
return Result.fail(ResultCode.RATE_LIMIT_EXCEEDED, "全局限流");
}
// 2. 服务级限流
if (!checkServiceLimit(httpStatement.getServiceName())) {
return Result.fail(ResultCode.RATE_LIMIT_EXCEEDED, "服务限流");
}
// 3. 接口级限流
if (!checkInterfaceLimit(httpStatement.getServiceName(), httpStatement.getPath())) {
return Result.fail(ResultCode.RATE_LIMIT_EXCEEDED, "接口限流");
}
// 4. IP级限流
String clientIp = getClientIp(request);
if (!checkIpLimit(clientIp)) {
return Result.fail(ResultCode.RATE_LIMIT_EXCEEDED, "IP限流");
}
return Result.success();
}
@Override
public int getOrder() {
return 10; // 在鉴权之后执行
}
@Override
public boolean canRunParallel() {
return false; // 串行执行,确保准确性
}
@Override
public boolean isFailFast() {
return true; // 限流失败时快速返回
}
}2.3 本地限流与 Redis 的协同机制
为什么需要本地限流?
- 保护 Redis:如果所有请求都打到 Redis,高并发下 Redis 可能成为瓶颈
- 提升性能:本地限流响应速度极快(纳秒级),减少网络开销
- 降级保障:Redis 异常时,本地限流仍可工作
协同工作原理
// 本地限流器容量设置
double permitsPerSecond = config.getLimitCount() * 1.2 / config.getTimeWindow();
RateLimiter localLimiter = RateLimiter.create(permitsPerSecond);容量设置策略:
- 本地限流器设置为配置值的 1.2 倍
- 例如:配置 1000 QPS,本地限流器设置为 1200 QPS
- 本地限流器会拦截 20% 的超限请求,剩余 80% 由 Redis 精确控制
协同效果:
假设配置:1000 QPS
实际流量:1500 QPS
本地限流器(1200 QPS):
- 放行:1200 QPS
- 拒绝:300 QPS
Redis 限流器(1000 QPS):
- 放行:1000 QPS
- 拒绝:200 QPS
最终结果:
- 放行:1000 QPS
- 拒绝:500 QPS
- Redis 压力:1200 次判断(而非 1500 次)配置缓存机制
// 本地限流器缓存
private final Map<String, RateLimiter> localLimiters = new ConcurrentHashMap<>();
// 配置缓存
private final Map<String, RateLimitConfig> configCache = new ConcurrentHashMap<>();
// 获取或创建本地限流器
private RateLimiter getOrCreateLocalLimiter(String key, RateLimitConfig config) {
return localLimiters.computeIfAbsent(key, k -> {
double permitsPerSecond = config.getLimitCount() * 1.2 / config.getTimeWindow();
return RateLimiter.create(permitsPerSecond);
});
}
// 更新配置时清除缓存
public void updateConfig(String key, RateLimitConfig config) {
configCache.put(key, config);
localLimiters.remove(key); // 清除旧的本地限流器
}