14.4 限流算法实现

面试重要程度:⭐⭐⭐⭐⭐

常见提问方式: "设计API限流系统" "令牌桶vs漏桶算法" "分布式限流实现"

预计阅读时间:40分钟

🎯 限流算法基础

常见限流算法

限流是保护系统稳定性的重要手段,常见算法包括:

  • 固定窗口计数器:简单但有突刺问题
  • 滑动窗口计数器:平滑限流,内存开销大
  • 令牌桶算法:允许突发流量,平滑限流
  • 漏桶算法:强制限制输出速率

🪙 令牌桶算法

令牌桶实现

/**
 * 令牌桶限流算法
 */
public class TokenBucketRateLimiter {
    
    private final int capacity;           // 桶容量
    private final double refillRate;      // 令牌补充速率(个/秒)
    private final Map<String, TokenBucket> buckets;
    
    public TokenBucketRateLimiter(int capacity, double refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        this.buckets = new ConcurrentHashMap<>();
    }
    
    /**
     * 令牌桶
     */
    static class TokenBucket {
        private volatile double tokens;
        private volatile long lastRefillTime;
        private final int capacity;
        private final double refillRate;
        
        public TokenBucket(int capacity, double refillRate) {
            this.capacity = capacity;
            this.refillRate = refillRate;
            this.tokens = capacity;
            this.lastRefillTime = System.currentTimeMillis();
        }
        
        public synchronized boolean tryAcquire(int tokensRequested) {
            refill();
            
            if (tokens >= tokensRequested) {
                tokens -= tokensRequested;
                return true;
            }
            
            return false;
        }
        
        private void refill() {
            long currentTime = System.currentTimeMillis();
            double timePassed = (currentTime - lastRefillTime) / 1000.0;
            
            if (timePassed > 0) {
                double tokensToAdd = timePassed * refillRate;
                tokens = Math.min(capacity, tokens + tokensToAdd);
                lastRefillTime = currentTime;
            }
        }
        
        public double getAvailableTokens() {
            refill();
            return tokens;
        }
    }
    
    /**
     * 尝试获取许可
     */
    public boolean tryAcquire(String key) {
        return tryAcquire(key, 1);
    }
    
    /**
     * 尝试获取指定数量的许可
     */
    public boolean tryAcquire(String key, int permits) {
        TokenBucket bucket = buckets.computeIfAbsent(key, 
            k -> new TokenBucket(capacity, refillRate));
        
        return bucket.tryAcquire(permits);
    }
}

🌐 分布式限流

基于Redis的分布式限流

/**
 * 基于Redis的分布式限流器
 */
@Component
public class DistributedRateLimiter {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final String keyPrefix = "rate_limiter:";
    
    public DistributedRateLimiter(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * 固定窗口限流
     */
    public boolean tryAcquireFixedWindow(String key, int maxRequests, long windowSizeInSeconds) {
        String redisKey = keyPrefix + "fixed:" + key;
        long currentWindow = System.currentTimeMillis() / (windowSizeInSeconds * 1000);
        String windowKey = redisKey + ":" + currentWindow;
        
        // 使用Lua脚本保证原子性
        String luaScript = 
            "local current = redis.call('GET', KEYS[1]) " +
            "if current == false then " +
            "  redis.call('SET', KEYS[1], 1) " +
            "  redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
            "  return 1 " +
            "else " +
            "  if tonumber(current) < tonumber(ARGV[1]) then " +
            "    return redis.call('INCR', KEYS[1]) " +
            "  else " +
            "    return -1 " +
            "  end " +
            "end";
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = redisTemplate.execute(script, 
            Collections.singletonList(windowKey), 
            maxRequests, windowSizeInSeconds);
        
        return result != null && result != -1;
    }
    
    /**
     * 令牌桶限流
     */
    public boolean tryAcquireTokenBucket(String key, int capacity, double refillRate) {
        String redisKey = keyPrefix + "token:" + key;
        long currentTime = System.currentTimeMillis();
        
        String luaScript = 
            "local bucket = redis.call('HMGET', KEYS[1], 'tokens', 'lastRefill') " +
            "local tokens = tonumber(bucket[1]) or tonumber(ARGV[1]) " +
            "local lastRefill = tonumber(bucket[2]) or tonumber(ARGV[4]) " +
            "local timePassed = (tonumber(ARGV[4]) - lastRefill) / 1000.0 " +
            "if timePassed > 0 then " +
            "  tokens = math.min(tonumber(ARGV[1]), tokens + timePassed * tonumber(ARGV[2])) " +
            "end " +
            "if tokens >= tonumber(ARGV[3]) then " +
            "  tokens = tokens - tonumber(ARGV[3]) " +
            "  redis.call('HMSET', KEYS[1], 'tokens', tokens, 'lastRefill', ARGV[4]) " +
            "  redis.call('EXPIRE', KEYS[1], 3600) " +
            "  return 1 " +
            "else " +
            "  redis.call('HMSET', KEYS[1], 'tokens', tokens, 'lastRefill', ARGV[4]) " +
            "  redis.call('EXPIRE', KEYS[1], 3600) " +
            "  return 0 " +
            "end";
        
        DefaultRedisScript<Long> script = new DefaultRedisS

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论
耐面王
点赞 回复 分享
发布于 09-06 08:18 浙江
很有收获,谢谢分享!
点赞 回复 分享
发布于 09-02 16:55 陕西

相关推荐

落糖糖:同学,瞅瞅我司,医疗独角兽,校招刚开,名额有限,先到先得,我的主页最新动态,绿灯直达,免笔试~
点赞 评论 收藏
分享
昨天 09:46
四川大学 Java
黑曼巴在线招人:我当时面试的时候,旁边的老哥是小地方坐火车来的,甚至jd临时改约定的时间还多待了两天
点赞 评论 收藏
分享
评论
4
10
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务