每天一道面试题:分布式限流架构设计与实战

45 阅读4分钟

面试官:"面对千万级并发的系统,如何设计一个可靠的分布式限流架构?说说你的设计方案。"

限流是分布式系统稳定性保障的基石,特别是在高并发场景下,一个合理的限流架构能够防止系统雪崩,保障核心业务可用性。今天我们就深入探讨分布式限流的设计思路。

一、核心难点:分布式限流的挑战

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<StringRateLimiter> 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 集群模式下的限流方案
  • 分析不同业务场景下的限流策略选择

本文由微信公众号"程序员小胖"整理发布,转载请注明出处。

明日面试题预告:微博推送架构。