面试官:"面对千万级并发的系统,如何设计一个可靠的分布式限流架构?说说你的设计方案。"
限流是分布式系统稳定性保障的基石,特别是在高并发场景下,一个合理的限流架构能够防止系统雪崩,保障核心业务可用性。今天我们就深入探讨分布式限流的设计思路。
一、核心难点:分布式限流的挑战
1. 精准性与性能的平衡
- 严格限流:需要精确计数,但原子操作带来性能开销
- 时间窗口同步:分布式节点间时间同步误差可能导致限流不准
- 内存与精度权衡:滑动窗口精度越高,内存消耗越大
2. 突发流量处理
- 突发流量吸收:如何合理处理突发请求而不直接拒绝
- 预热机制:冷启动时如何避免大量请求压垮系统
- 弹性伸缩:限流阈值如何随系统负载动态调整
3. 分布式一致性
- 计数同步:多个节点如何共享限流计数状态
- 数据一致性:在保证性能的同时确保限流准确性
- 故障恢复:节点宕机后限流状态如何快速恢复
二、架构设计方案:分层限流与弹性控制
2.1 算法层:四种限流算法对比选择
固定窗口计数器
- 优点:实现简单,内存占用小
- 缺点:窗口临界点可能通过两倍流量
滑动窗口计数器
- 优点:解决固定窗口的临界问题
- 缺点:精度与内存消耗成正比
漏桶算法
- 优点:输出速率恒定,平滑流量
- 缺点:无法处理突发流量,响应延迟
令牌桶算法
- 优点:兼顾平均速率与突发处理
- 缺点:实现相对复杂
2.2 分布式实现:Redis+Lua 原子操作
// 基于Redis+Lua的令牌桶限流实现
@Component
public class DistributedRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LUA_SCRIPT =
"local tokens_key = KEYS[1] " +
"local timestamp_key = KEYS[2] " +
"local rate = tonumber(ARGV[1]) " +
"local capacity = tonumber(ARGV[2]) " +
"local now = tonumber(ARGV[3]) " +
"local requested = tonumber(ARGV[4]) " +
"local fill_time = capacity / rate " +
"local ttl = math.floor(fill_time * 2) " +
"local last_tokens = tonumber(redis.call('get', tokens_key) or capacity) " +
"local last_refreshed = tonumber(redis.call('get', timestamp_key) or 0) " +
"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 " +
"if allowed then new_tokens = filled_tokens - requested end " +
"redis.call('setex', tokens_key, ttl, new_tokens) " +
"redis.call('setex', timestamp_key, ttl, now) " +
"return allowed";
public boolean tryAcquire(String key, int capacity, int rate) {
List<String> keys = Arrays.asList(key + ":tokens", key + ":timestamp");
long now = System.currentTimeMillis() / 1000;
Object result = redisTemplate.execute(
new DefaultRedisScript<>(LUA_SCRIPT, Boolean.class),
keys, rate, capacity, now, 1
);
return Boolean.TRUE.equals(result);
}
}
2.3 分层防护:多级限流架构
本地预取层
// 本地令牌桶+Redis后备的两级限流
@Service
public class HierarchicalRateLimiter {
private Map<String, RateLimiter> localLimiters = new ConcurrentHashMap<>();
@Autowired
private DistributedRateLimiter distributedLimiter;
public boolean tryAcquire(String serviceId, int capacity, int rate) {
// 1. 本地限流器快速判断
RateLimiter localLimiter = localLimiters.computeIfAbsent(
serviceId, id -> RateLimiter.create(capacity)
);
if (!localLimiter.tryAcquire()) {
// 2. 本地令牌不足,尝试从Redis获取批次令牌
return acquireFromRemote(serviceId, capacity, rate);
}
return true;
}
private boolean acquireFromRemote(String serviceId, int capacity, int rate) {
// 批量获取令牌,减少Redis调用
return distributedLimiter.tryAcquire(serviceId, capacity, rate);
}
}
垂直分片层
// 基于业务特征的垂直分片限流
@Component
public class ShardedRateLimiter {
public String getShardKey(String originalKey, String clientIp) {
// 根据IP地址进行分片
int hash = Math.abs(clientIp.hashCode()) % 1024;
return originalKey + ":" + (hash % 16); // 分为16个片
}
public boolean tryAcquire(String resource, String clientIp, int capacity) {
String shardKey = getShardKey(resource, clientIp);
// 每个分片独立限流,分散压力
return distributedLimiter.tryAcquire(shardKey, capacity / 16, capacity / 16);
}
}
三、架构亮点与面试加分项
3.1. 分层消峰设计
第一层:本地限流,快速响应
第二层:分布式限流,全局控制
第三层:垂直分片,分散热点
3.2. 弹性限流策略
动态阈值:根据系统负载自动调整限流阈值
预热模式:冷启动时逐步增加流量上限
熔断降级:异常时自动降级限流策略
四、总结回顾
分布式限流架构的核心设计思想:请求接入
➡️ 本地限流: (快速判断) | (本地令牌桶)
➡️ 分布式限流: [Redis+Lua原子操作] → [全局计数]
➡️ 分层防护: (垂直分片) | (弹性扩缩容) | (动态调优)
➡️ 异常处理: [熔断降级] → [优雅降级] → [过载保护]
五、面试建议
回答技巧:
- 先说明限流的重要性:防止系统雪崩、保障核心业务
- 对比不同算法的适用场景:令牌桶适合突发流量,漏桶适合平滑流量
- 强调分布式实现的难点:原子性、一致性、性能平衡
- 展示分层设计思路:从本地到分布式,从简单到复杂
- 提及容错机制:降级策略、熔断保护、动态调整
加分回答:
- 提到 Guava RateLimiter 的单机实现原理
- 讨论 Redis 集群模式下的限流方案
- 分析不同业务场景下的限流策略选择
本文由微信公众号"程序员小胖"整理发布,转载请注明出处。
明日面试题预告:微博推送架构。