常见限流算法详解及优缺点对比

在高并发系统中,限流是保护服务稳定性的重要手段。以下是几种常见限流算法的核心原理、优缺点及适用场景分析:

A. 固定窗口计数器(Fixed Window Counter)

核心原理

  • 将时间划分为固定大小的窗口(如 1 秒)。
  • 每个窗口内维护一个计数器,记录请求次数。
  • 当请求到达时,若计数器超过阈值,则拒绝请求。
  • 窗口结束后,计数器重置为 0。
    示例代码
	public class FixedWindowRateLimiter {
	
	       private final long windowSizeMs;  // 窗口大小(毫秒)
	       private final int limit;          // 窗口内最大请求数
	       private long windowStartMs;       // 窗口开始时间
	       private int counter;             // 当前窗口内请求计数器
	              
	       public FixedWindowRateLimiter(long windowSizeMs, int limit) {
	       
	           this.windowSizeMs = windowSizeMs;
	           this.limit = limit;
	           this.windowStartMs = System.currentTimeMillis();
	           
	       }
	       
	       public synchronized boolean tryAcquire() {
	       
	            long now = System.currentTimeMillis();
	            
	            if (now - windowStartMs > windowSizeMs) {       // 检查是否需要重置窗口
	                windowStartMs = now;
	                counter = 0;
	            }
	            
	            if (counter < limit) {          // 检查是否超过限流阈值
	                counter++;return true;
	            }
	            
	            return false;
	        }
	}

优缺点

  • 优点:实现简单,内存占用少。
  • 缺点:存在临界问题(突刺现象),例如在窗口切换瞬间可能允许双倍请求通过。
  • 适用场景:对精度要求不高的场景,如统计类接口限流。

B. 滑动窗口计数器(Sliding Window Counter)

核心原理

  • 将固定窗口划分为更小的时间槽(如 1 秒窗口分为 10 个 100ms 的槽)。
  • 每个时间槽维护独立计数器。
  • 当请求到达时,统计当前时间窗口内所有时间槽的计数器总和,超过阈值则拒绝。
  • 时间窗口滑动时,丢弃最早时间槽的数据。
    示例代码
	public class SlidingWindowRateLimiter {
	    private final int windowSize;     // 窗口大小(槽数量)
	    private final long slotSizeMs;    // 每个槽的大小(毫秒)
	    private final int limit;          // 窗口内最大请求数
	    private final AtomicInteger[] slots; // 每个槽的计数器
	    private int currentSlot;          // 当前槽索引
	
	    public SlidingWindowRateLimiter(int windowSize, long slotSizeMs, int limit) {
	        this.windowSize = windowSize;
	        this.slotSizeMs = slotSizeMs;
	        this.limit = limit;
	        this.slots = new AtomicInteger[windowSize];
	        for (int i = 0; i < windowSize; i++) {
	            slots[i] = new AtomicInteger(0);
	        }
	    }
	
	    public boolean tryAcquire() {
	        long now = System.currentTimeMillis();
	        // 计算当前时间对应的槽索引
	        int slotIndex = (int) ((now / slotSizeMs) % windowSize);
	        // 重置过期的槽
	        resetExpiredSlots(now);
	        // 计算当前窗口内总请求数
	        int total = getTotalRequests();
	        // 检查是否超过限流阈值
	        if (total < limit) {
	            slots[slotIndex].incrementAndGet();
	            return true;
	        }
	        return false;
	    }
	
	    private void resetExpiredSlots(long now) {
	        // 计算当前时间对应的槽时间戳
	        long currentSlotTime = (now / slotSizeMs) * slotSizeMs;
	        // 重置所有过期的槽
	        for (int i = 0; i < windowSize; i++) {
	            long slotTime = currentSlotTime - (windowSize - 1) * slotSizeMs + i * slotSizeMs;
	            if (slotTime < currentSlotTime - (windowSize - 1) * slotSizeMs) {
	                slots[i].set(0);
	            }
	        }
	    }
	
	    private int getTotalRequests() {
	        int total = 0;
	        for (AtomicInteger slot : slots) {
	            total += slot.get();
	        }
	        return total;
	    }
	}

优缺点

  • 优点:解决了固定窗口的临界问题,限流更精确。
  • 缺点:实现复杂度增加,内存占用变大(需维护多个计数器)。
  • 适用场景:对限流精度有一定要求的场景,如 API 网关限流。

C. 令牌桶算法(Token Bucket)

核心原理

  • 系统以固定速率向桶中添加令牌(如每秒 100 个)。
  • 桶有固定容量,满时令牌不再增加。
  • 请求到达时需从桶中获取令牌,成功则处理,失败则拒绝。
  • 允许一定程度的突发请求(桶中令牌足够时)。

示例代码

	public class TokenBucketRateLimiter {
	    private final long capacity;      // 令牌桶容量
	    private final double rate;        // 令牌生成速率(个/秒)
	    private double tokens;            // 当前令牌数
	    private long lastRefillTime;      // 上次填充时间(毫秒)
	
	    public TokenBucketRateLimiter(long capacity, double rate) {
	        this.capacity = capacity;
	        this.rate = rate;
	        this.tokens = capacity;
	        this.lastRefillTime = System.currentTimeMillis();
	    }
	
	    public synchronized boolean tryAcquire() {
	        // 填充令牌
	        refill();
	        // 尝试获取令牌
	        if (tokens >= 1) {
	            tokens -= 1;
	            return true;
	        }
	        return false;
	    }
	
	    private void refill() {
	        long now = System.currentTimeMillis();
	        // 计算从上次填充到现在的时间间隔(秒)
	        double elapsedSeconds = (now - lastRefillTime) / 1000.0;
	        if (elapsedSeconds <= 0) {
	            return;
	        }
	        // 计算这段时间应生成的令牌数
	        double generatedTokens = elapsedSeconds * rate;
	        // 更新令牌数(不超过容量)
	        tokens = Math.min(capacity, tokens + generatedTokens);
	        // 更新上次填充时间
	        lastRefillTime = now;
	    }
	}

优缺点

  • 优点:平滑突发流量,允许一定程度的突发请求,适合应对短暂高并发。
  • 缺点:实现较复杂,需要维护令牌生成逻辑。
  • 适用场景:需要处理突发流量的场景,如秒杀系统、API 网关。

D. 漏桶算法(Leaky Bucket)

核心原理

  • 请求进入漏桶后,以固定速率流出处理(如每秒 100 个)。
  • 桶满时,新请求被直接拒绝。
  • 无论请求速率如何,处理速率始终保持恒定。

示例代码

	public class LeakyBucketRateLimiter {
	
	    private final long capacity;      // 漏桶容量
	    private final long rate;          // 漏水速率(个/秒)
	    private long water;               // 当前水量
	    private long lastLeakTime;        // 上次漏水时间(毫秒)
	
	    public LeakyBucketRateLimiter(long capacity, long rate) {
	        this.capacity = capacity;
	        this.rate = rate;
	        this.water = 0;
	        this.lastLeakTime = System.currentTimeMillis();
	    }
	
	    public synchronized boolean tryAcquire() {
	        // 漏水
	        leak();
	        // 尝试加水
	        if (water + 1 <= capacity) {
	            water++;
	            return true;
	        }
	        return false;
	    }
	
	    private void leak() {
	        long now = System.currentTimeMillis();
	        // 计算从上次漏水到现在的时间间隔(秒)
	        double elapsedSeconds = (now - lastLeakTime) / 1000.0;
	        // 计算这段时间漏出的水量
	        long leakedWater = (long) (elapsedSeconds * rate);
	        // 更新当前水量(不能小于0)
	        water = Math.max(0, water - leakedWater);
	        // 更新上次漏水时间
	        lastLeakTime = now;
	    }
	}

优缺点

  • 优点:严格控制请求处理速率,平滑流量峰值,适合需要严格限速的场景。
  • 缺点:不允许突发流量,即使系统有处理能力。
  • 适用场景:对流量稳定性要求极高的场景,如数据库访问限流、流媒体服务。

E. 对比与选择建议

算法固定窗口滑动窗口令牌桶漏桶
实现复杂度
内存消耗
突发流量处理差(临界问题)一般
流量平滑度一般较好
处理速率稳定性一般一般
适用场景统计类接口通用API限流秒杀、网关数据库、流媒体

高级应用与优化

  1. 分布式限流:
  • 将令牌桶或漏桶状态存储在 Redis 中,通过 Lua 脚本保证原子性操作。
  • 示例:前面提供的基于 Redis 的令牌桶限流实现。
  1. 自适应限流:
  • 根据系统负载(如 CPU、RT)动态调整限流阈值。
  • Sentinel 的系统自适应保护就是典型实现。
  1. 预热限流:
  • 系统启动初期逐步提高限流阈值,避免冷启动问题。
  • Sentinel 的预热限流算法支持此特性。
  1. 熔断降级:
  • 结合限流与熔断机制,当错误率超过阈值时自动熔断服务。
  • Sentinel、Resilience4j 等框架支持此功能。

选择限流算法时,需根据业务场景权衡精度、性能和实现复杂度,必要时可组合多种算法实现多级限流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值