拒绝服务雪崩!4种经典限流算法图文详解(附Java实战代码)

简介: 限流是保护系统的“保险丝”,防止突发流量导致服务雪崩。常见算法有:固定窗口(简单但有突刺)、滑动窗口(精准平滑)、漏桶(恒定处理速率)和令牌桶(允许突发,最常用)。单机限流可用计数器或Guava,分布式场景则依赖Redis实现全局控制。

为什么要限流?

想象一下,你开了一家网红奶茶店,只有3名店员做奶茶。

  • 正常情况:每分钟来5个客人,店员从容应对,大家都很开心。
  • 突发情况:由于某音爆火,突然一分钟涌入100个客人!

如果不做任何限制,店员会瞬间手忙脚乱,点单系统崩溃,甚至做错饮料,最后所有客人都得不到服务(服务雪崩)。

限流(Rate Limiting) 就是为了解决这个问题:在流量过大时,通过限制请求速率,牺牲一部分请求,来保证系统的整体可用性。


1. 固定窗口算法 (Fixed Window)

原理

这是最简单粗暴的算法。我们规定单位时间(比如1秒)内,只能处理固定数量(比如5个)的请求。

  • 维护一个计数器。
  • 每来一个请求,计数器+1。
  • 如果计数器超过阈值,拒绝请求。
  • 每过单位时间,计数器清零。

图解

image.png

Java 代码示例

import java.util.concurrent.atomic.AtomicInteger;

public class FixedWindowRateLimiter {
   
    // 阈值:每秒最多5个请求
    private static final int LIMIT = 5; 
    // 窗口大小:1000毫秒
    private static final long WINDOW_SIZE = 1000; 

    private static AtomicInteger counter = new AtomicInteger(0);
    private static long lastTime = System.currentTimeMillis();

    public static synchronized boolean tryAcquire() {
   
        long currentTime = System.currentTimeMillis();
        // 如果当前时间已经超过了上一个窗口,重置计数器
        if (currentTime - lastTime > WINDOW_SIZE) {
   
            counter.set(0);
            lastTime = currentTime;
        }

        // 如果未达到阈值,允许通过
        if (counter.get() < LIMIT) {
   
            counter.incrementAndGet();
            return true;
        }
        return false;
    }
}

致命缺陷:临界突刺

假设限制是 1秒5个
如果在 0.8秒到1.0秒 来了5个请求,1.0秒到1.2秒 又来了5个请求。
虽然每个独立的1秒窗口都没超标,但在 0.8秒到1.2秒 这短短0.4秒内,处理了 10个 请求!这可能会瞬间压垮系统。


2. 滑动窗口算法 (Sliding Window)

原理

为了解决固定窗口的“临界突刺”问题,我们把时间窗口划分得更细。
比如把1秒分成5个小格子(每个200ms)。随着时间流逝,窗口像幻灯片一样平滑地向右移动,每次只丢弃最老的一个小格子。

图解

image.png

Java 代码示例 (简化版思路)

import java.util.LinkedList;
import java.util.Iterator;

public class SlidingWindowRateLimiter {
   
    private static final int LIMIT = 5;
    private static final long WINDOW_SIZE = 1000;

    // 使用链表记录每个请求的时间戳
    private LinkedList<Long> timestamps = new LinkedList<>();

    public synchronized boolean tryAcquire() {
   
        long now = System.currentTimeMillis();

        // 1. 移除窗口之外的过期请求(比如1秒前的)
        while (!timestamps.isEmpty() && (now - timestamps.peekFirst() > WINDOW_SIZE)) {
   
            timestamps.removeFirst();
        }

        // 2. 检查当前窗口内的请求数
        if (timestamps.size() < LIMIT) {
   
            timestamps.addLast(now);
            return true;
        }

        return false;
    }
}

优缺点

  • 优点:解决了流量突刺问题,限流更精准。
  • 缺点:算法稍复杂,需要存储更多的时间戳数据。Sentinel 等主流框架常用此算法。

3. 漏桶算法 (Leaky Bucket)

原理

把请求想象成水,倒入一个漏桶中。

  • 进水:请求进来的速度是不确定的(可能暴增)。
  • 出水:桶底有一个孔,水以固定速率流出(处理请求)。
  • 溢出:如果桶满了,新进来的水(请求)就会被丢弃。

图解

image.png

Java 代码示例

public class LeakyBucketRateLimiter {
   
    private long capacity = 10; // 桶容量
    private long water = 0;     // 当前水量
    private long leakRate = 2;  // 出水速率:每秒2滴
    private long lastTime = System.currentTimeMillis();

    public synchronized boolean tryAcquire() {
   
        long now = System.currentTimeMillis();
        // 计算过去这段时间流出了多少水
        long leakedWater = (now - lastTime) / 1000 * leakRate;

        // 更新水量(不能小于0)
        water = Math.max(0, water - leakedWater);
        lastTime = now;

        // 尝试加水(处理请求)
        if (water < capacity) {
   
            water++;
            return true;
        }
        return false;
    }
}

核心特点

  • 强行削峰填谷:无论外面流量多大,系统内部处理速率永远是恒定的。
  • 适用场景:适合保护那些处理能力有限的稀缺资源(如数据库连接)。

4. 令牌桶算法 (Token Bucket) - 推荐

原理

这是目前最常用的算法(如 Google Guava 的 RateLimiter)。

  • 有一个令牌桶,系统以固定速率往里面放入令牌
  • 请求来了,必须从桶里拿到一个令牌才能被处理。
  • 如果桶满了,令牌就溢出(浪费掉)。
  • 如果桶空了,请求就被拒绝(或等待)。

图解

image.png

漏桶 vs 令牌桶

  • 漏桶:限制了流出速率,不能应对突发流量。
  • 令牌桶:限制了流入速率,但允许突发流量(只要桶里有存货,可以瞬间拿走大量令牌)。

Java 代码示例 (使用 Guava)

工业级开发中,我们通常直接使用 Google Guava 库:

import com.google.common.util.concurrent.RateLimiter;

public class TokenBucketDemo {
   
    // 每秒生成5个令牌
    private static RateLimiter rateLimiter = RateLimiter.create(5.0);

    public static void doSomething() {
   
        // 尝试获取令牌,如果拿不到会阻塞等待(也可以用 tryAcquire 非阻塞)
        rateLimiter.acquire(); 
        System.out.println("处理请求: " + System.currentTimeMillis());
    }
}

5. 进阶:分布式限流

上面的算法都是针对单机的。如果是微服务架构,几十台服务器,怎么办?
这时通常使用 Redis 来实现。

Redisson 分布式限流

利用 Redis 的原子性,结合 Lua 脚本实现全系统的流量控制。

// 伪代码示例
RRateLimiter rateLimiter = redissonClient.getRateLimiter("myKey");
// 初始化:每秒最多10个
rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

if (rateLimiter.tryAcquire()) {
   
    // 处理业务
} else {
   
    // 限流
}

总结

算法 特点 优点 缺点 适用场景
固定窗口 简单计数 实现极其简单 存在临界突刺问题 流量平稳的简单场景
滑动窗口 细分时间格 解决临界问题,精度高 实现稍繁琐 很多限流框架的底层实现 (如 Sentinel)
漏桶 宽进严出 输出速率恒定,平滑流量 无法处理突发流量 保护下游严格受限的系统
令牌桶 严进宽出 允许突发流量 实现稍复杂 最常用,兼顾稳定与突发 (如 Guava)

希望这篇文章能帮你搞懂限流!如果觉得有用,记得点赞收藏哦!

目录
相关文章
|
2天前
|
人工智能 Java Go
2026年免费AI编程助手测评:通义灵码领衔,谁是国产开发者的最佳Copilot?
随着 Qwen 2.5-Coder 等开源模型的爆发,2026年 AI 编程工具已进入“百模大战”的深水区。本文基于代码生成准确率、中文语境理解能力及免费额度三大维度,对市场主流工具进行实测
|
4天前
|
自然语言处理 监控 测试技术
互联网大厂“黑话”完全破译指南
互联网大厂黑话太多听不懂?本文整理了一份“保姆级”职场黑话词典,涵盖PRD、A/B测试、WLB、埋点、灰度发布等高频术语,用大白话+生活化类比,帮你快速听懂同事在聊什么。非技术岗也能轻松理解,建议收藏防踩坑。
286 161
|
1天前
|
运维 安全 算法
别再把端到端加密当护身符了:多租户系统里,合规比加密更难
别再把端到端加密当护身符了:多租户系统里,合规比加密更难
49 17
|
2天前
|
人工智能 前端开发 程序员
前端天塌啦,后端程序员福利,这个开源UI/UX外挂,给 Cursor/Windsurf 加个 审美插件
小华同学推荐开源项目UI UX Pro Max:为AI生成UI提供设计数据库支持,解决配色、风格、UX规范混乱难题。集成主流AI编程工具,一键调用,提升开发效率50%以上,10万+开发者已订阅!
232 3
|
4天前
|
缓存 算法 关系型数据库
深入浅出分布式 ID 生成方案:从原理到业界主流实现
本文深入探讨分布式ID的生成原理与主流解决方案,解析百度UidGenerator、滴滴TinyID及美团Leaf的核心设计,涵盖Snowflake算法、号段模式与双Buffer优化,助你掌握高并发下全局唯一ID的实现精髓。
326 160
|
8天前
|
机器学习/深度学习 自然语言处理 算法
从贝叶斯视角解读Transformer的内部几何:mHC的流形约束与大模型训练稳定性
大模型训练常因架构改动破坏内部贝叶斯几何结构,导致不稳定。研究表明,Transformer通过残差流、注意力与值表征在低维流形上实现类贝叶斯推理。mHC通过约束超连接保护这一几何结构,确保规模化下的训练稳定与推理一致性。
187 7
从贝叶斯视角解读Transformer的内部几何:mHC的流形约束与大模型训练稳定性
|
4天前
|
人工智能 测试技术 API
一线工程师 2025 总结:LLM 只用了不到 10%,剩下 90% 卡在哪?
2025年,LLM能力爆发,但多数企业仅用到其10%。真正瓶颈不在模型强弱,而在工程落地:延迟不可控、并发崩溃、换模成本高、成本失控成常态。当LLM从“工具”变为“基础设施”,中转层与系统稳定性成为关键。释放剩余90%潜力,需扎实的架构设计与工程治理。
|
21天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:六十九、Bootstrap采样在大模型评估中的应用:从置信区间到模型稳定性
Bootstrap采样是一种通过有放回重抽样来评估模型性能的统计方法。它通过从原始数据集中随机抽取样本形成多个Bootstrap数据集,计算统计量(如均值、标准差)的分布,适用于小样本和非参数场景。该方法能估计标准误、构建置信区间,并量化模型不确定性,但对计算资源要求较高。Bootstrap特别适合评估大模型的泛化能力和稳定性,在集成学习、假设检验等领域也有广泛应用。与传统方法相比,Bootstrap不依赖分布假设,在非正态数据中表现更稳健。
454 113