题目
设计支持动态策略的分布式限流系统
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
分布式一致性, 限流算法选择, 高并发优化, 容错设计, 动态策略管理
快速回答
核心设计要点:
- 采用分层令牌桶架构:本地桶+全局Redis集群
- 使用Redis+Lua保证原子计数和滑动窗口限流
- 动态策略加载:ZooKeeper监听配置变更+本地缓存
- 容错机制:降级本地限流 + 熔断策略
- 性能优化:本地预取令牌 + 异步刷新
1. 系统核心需求
在分布式电商系统中,需实现:
• 支持每秒 50 万+请求的限流
• 动态调整策略(如秒杀时临时提额)
• 毫秒级响应延迟
• 99.99% 可用性
2. 架构设计
+----------------+ +-----------------+
| Client App | | Config Manager |
| (Local Bucket)|---->| (ZooKeeper) |
+-------+--------+ +--------+--------+
| |
| Async Refresh | Policy Update
+-------v--------+ +--------v--------+
| Redis Cluster |<----| Control Plane |
| (Global Count)| | (Dashboard API)|
+----------------+ +-----------------+3. 关键技术实现
3.1 限流算法组合
- 本地层:令牌桶算法(应对突发流量)
// Java 伪代码 class LocalTokenBucket { long lastRefillTime; int currentTokens; int capacity; synchronized boolean tryAcquire() { refillTokens(); // 按固定速率补充 if (currentTokens > 0) { currentTokens--; return true; } return false; } } - 全局层:滑动窗口计数(精确控制)
-- Redis Lua 脚本 local key = KEYS[1] -- 限流键(如 user:123) local limit = tonumber(ARGV[1]) -- 阈值 local window = tonumber(ARGV[2]) -- 时间窗(秒) local now = tonumber(ARGV[3]) -- 当前时间戳 -- 清除过期记录 redis.call('ZREMRANGEBYSCORE', key, 0, now - window) -- 获取当前计数 local count = redis.call('ZCARD', key) if count < limit then redis.call('ZADD', key, now, now) -- 添加新请求 redis.call('EXPIRE', key, window) -- 设置过期 return 1 -- 允许访问 end return 0 -- 拒绝请求
3.2 动态策略管理
// ZooKeeper 监听示例
public class PolicyWatcher implements Watcher {
void process(WatchedEvent event) {
if (event.getType() == EventType.NodeDataChanged) {
loadNewPolicy(event.getPath()); // 重载本地策略
}
}
}3.3 容错设计
- 降级模式:Redis 故障时切换本地限流
- 熔断机制:连续超时自动切到本地桶
- 过载保护:基于系统负载动态调整阈值
4. 最佳实践
- 分层过滤:先本地桶再全局计数,减少Redis压力
- 批量预取:每次从Redis获取一批令牌本地缓存
- 热点分离:对高频用户(如黄牛)使用独立Redis分片
- 监控指标:实时采集 QPS、拒绝率、延迟百分位
5. 常见错误
- 时间同步问题:使用Redis时间避免时钟漂移
- 竞争条件:必须用Lua保证原子操作
- 策略雪崩:配置变更时逐步滚动更新
- 内存泄漏:ZADD需配合EXPIRE设置TTL
6. 扩展知识
- 自适应限流:结合CPU/负载动态调整(如TCP BBR算法)
- 机器学习应用:基于历史流量预测配额
- 服务网格集成:通过Envoy Filter实现基础设施层限流
- 配额银行模式:允许用户借用未来配额(需偿还机制)