跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Sentinel 限流算法

Sentinel 限流算法涵盖计数器固定窗口、计数器滑动窗口、漏桶算法及令牌桶算法。固定窗口存在临界值问题,滑动窗口通过细分时间窗口平滑流量过渡。漏桶算法以固定速率流出请求,适合平滑突发流量但不支持突发流出。令牌桶算法允许在桶满时丢弃令牌,支持突发流量且限制平均流量。文中详细阐述了各算法原理并给出了对应的 Java 代码实现示例。

晚风告白发布于 2026/3/16更新于 2026/9/1280 浏览
Sentinel 限流算法
1、计数器固定窗口算法

计数器固定窗口算法是最简单的限流算法,实现方式也比较简单。就是通过维护一个单位时间内的计数值,每当一个请求通过时,就将计数值加 1,当计数值超过预先设定的阈值时,就拒绝单位时间内的其他请求。如果单位时间已经结束,则将计数器清零,开启下一轮的计数。

image.png

package com.on.sentinel;
import com.on.sentinel.exception.BlockException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CounterRateLimit implements RateLimit, Runnable {
    /**
     * 阈值
     */
    private Integer limitCount;

    /**
     * 当前通过请求数
     */
    private AtomicInteger passCount;

    /**
     * 统计时间间隔
     */
    private long period;
    private TimeUnit timeUnit;

    private ScheduledExecutorService scheduledExecutorService;

    public CounterRateLimit(Integer limitCount) {
        this(limitCount, 1000, TimeUnit.MILLISECONDS);
    }

    public CounterRateLimit(Integer limitCount, long period, TimeUnit timeUnit) {
        this.limitCount = limitCount;
        this.period = period;
        this.timeUnit = timeUnit;
        passCount =  ();
        .startResetTask();
    }

    
        BlockException {
         (passCount.incrementAndGet() > limitCount) {
              ();
        }
         ;
    }

    
       {
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(, , period, timeUnit);
    }

    
       {
        passCount.set();
    }
}
new
AtomicInteger
0
this
@Override
public
boolean
canPass
()
throws
if
throw
new
BlockException
return
true
//启动一定定时任务线程,每过一个周期就将我们通过个数重置为 0
private
void
startResetTask
()
this
0
@Override
public
void
run
()
0

但是这种实现会有一个问题,举个例子:

假设我们设定 1 秒内允许通过的请求阈值是 99,如果有用户在时间窗口的最后几毫秒发送了 99 个请求,紧接着又在下一个时间窗口开始时发送了 200 个请求,那么这个用户其实在一秒内成功请求了 198 次,显然超过了阈值但并不会被限流。其实这就是临界值问题,那么临界值问题要怎么解决呢?

image.png

2、计数器滑动窗口算法

算法演示

计数器滑动窗口法就是为了解决上述固定窗口计数存在的问题而诞生。前面说了固定窗口存在临界值问题,要解决这种临界值问题,显然只用一个窗口是解决不了问题的。假设我们仍然设定 1 秒内允许通过的请求是 200 个,但是在这里我们需要把 1 秒的时间分成多格,假设分成 5 格(格数越多,流量过渡越平滑),每格窗口的时间大小是 200 毫秒,每过 200 毫秒,就将窗口向前移动一格。为了便于理解,可以看下图

image.png

图中将窗口划为 5 份,每个小窗口中的数字表示在这个窗口中请求数,所以通过观察上图,可知在当前时间快(200 毫秒)允许通过的请求数应该是 70 而不是 200(只要超过 70 就会被限流),因为我们最终统计请求数时是需要把当前窗口的值进行累加,进而得到当前请求数来判断是不是需要进行限流。

那么滑动窗口限流法是完美的吗?

细心观察的我们应该能马上发现问题,滑动窗口限流法其实就是计数器固定窗口算法的一个变种。流量的过渡是否平滑依赖于我们设置的窗口格数也就是统计时间间隔,格数越多,统计越精确,但是具体要分多少格我们也说不上来呀...

package com.on.sentinel;

import com.on.sentinel.exception.BlockException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class SlidingWindowRateLimit implements RateLimit, Runnable {

    /**
     * 阈值
     */
    private Integer limitCount;

    /**
     * 当前通过的请求数
     */
    private AtomicInteger passCount;

    /**
     * 窗口数
     */
    private Integer windowSize;

    /**
     * 每个窗口时间间隔大小
     */
    private long windowPeriod;
    private TimeUnit timeUnit;

    private Window[] windows;
    private volatile Integer windowIndex = 0;

    private Lock lock = new ReentrantLock();

    public SlidingWindowRateLimit(Integer limitCount) {
        // 默认统计 qps, 窗口大小 5
        this(limitCount, 5, 200, TimeUnit.MILLISECONDS);
    }

    /**
     * 统计总时间 = windowSize * windowPeriod
     */
    public SlidingWindowRateLimit(Integer limitCount, Integer windowSize, Integer windowPeriod, TimeUnit timeUnit) {
        this.limitCount = limitCount;
        this.windowSize = windowSize;
        this.windowPeriod = windowPeriod;
        this.timeUnit = timeUnit;
        this.passCount = new AtomicInteger(0);
        this.initWindows(windowSize);
        this.startResetTask();
    }

    @Override
    public boolean canPass() throws BlockException {
        lock.lock();
        if (passCount.get() > limitCount) {
            throw new BlockException();
        }
        windows[windowIndex].passCount.incrementAndGet();
        passCount.incrementAndGet();
        lock.unlock();
        return true;
    }

    private void initWindows(Integer windowSize) {
        windows = new Window[windowSize];
        for (int i = 0; i < windowSize; i++) {
            windows[i] = new Window();
        }
    }

    private ScheduledExecutorService scheduledExecutorService;
    private void startResetTask() {
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(this, windowPeriod, windowPeriod, timeUnit);
    }

    @Override
    public void run() {
        // 获取当前窗口索引
        Integer curIndex = (windowIndex + 1) % windowSize;
        // 重置当前窗口索引通过数量,并获取上一次通过数量
        Integer count = windows[curIndex].passCount.getAndSet(0);
        windowIndex = curIndex;
        // 总通过数量 减去 当前窗口上次通过数量
        passCount.addAndGet(-count);
    }

    @Data
    class Window {
        private AtomicInteger passCount;
        public Window() {
            this.passCount = new AtomicInteger(0);
        }
    }
}
3、漏桶算法

上面所介绍的两种算法都不能非常平滑的过渡,下面就是漏桶算法登场了

什么是漏桶算法?

漏桶算法以一个常量限制了出口流量速率,因此漏桶算法可以平滑突发的流量。其中漏桶作为流量容器我们可以看做一个 FIFO 的队列,当入口流量速率大于出口流量速率时,因为流量容器是有限的,当超出流量容器大小时,超出的流量会被丢弃。

下图比较形象的说明了漏桶算法的原理,其中水龙头是入口流量,漏桶是流量容器,匀速流出的水是出口流量。

image.png

漏桶算法的特点

  • 漏桶具有固定容量,出口流量速率是固定常量(流出请求)
  • 入口流量可以以任意速率流入到漏桶中(流入请求)
  • 如果入口流量超出了桶的容量,则流入流量会溢出(新请求被拒绝)
package com.on.sentinel;

import com.on.sentinel.exception.BlockException;
import lombok.extern.slf4j.Slf4j;

import javax.swing.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.locks.LockSupport;

@Slf4j
public class LeakyBucketRateLimit implements RateLimit, Runnable {

    /**
     * 出口限制 qps
     */
    private Integer limitSecond;
    /**
     * 漏桶队列
     */
    private BlockingQueue<Thread> leakyBucket;

    private ScheduledExecutorService scheduledExecutorService;

    public LeakyBucketRateLimit(Integer bucketSize, Integer limitSecond) {
        this.limitSecond = limitSecond;
        this.leakyBucket = new LinkedBlockingDeque<>(bucketSize);

        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        //1 秒=1000000000 纳秒
        // 计算出一个请求需要多少纳秒
        long interval = (1000 * 1000 * 1000) / limitSecond;
        scheduledExecutorService.scheduleAtFixedRate(this, 0, interval, TimeUnit.NANOSECONDS);
    }

    @Override
    public boolean canPass() throws BlockException {
        if (leakyBucket.remainingCapacity() == 0) {
            throw new BlockException();
        }
        //如果立即可行且不违反容量限制,则将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),
        // 并在成功时返回 true;如果当前没有空间可用,则返回 false。
        leakyBucket.offer(Thread.currentThread());
        //所有请求进来,只要有容量,就会停止到这里
        // 如果没有容量,就停止在上行代码
        LockSupport.park();
        return true;
    }

    @Override
    public void run() {
        // 以固定的速率去唤醒他,这样执行的速度就固定值
        Thread thread = leakyBucket.poll();
        if (Objects.nonNull(thread)) {
            LockSupport.unpark(thread);
        }
    }
}

不过因为漏桶算法限制了流出速率是一个固定常量值,所以漏桶算法不支持出现突发流出流量。但是在实际情况下,流量往往是突发的。

4、令牌桶算法

令牌桶算法是漏桶算法的改进版,可以支持突发流量。不过与漏桶算法不同的是,令牌桶算法的漏桶中存放的是令牌而不是流量。

那么令牌桶算法是怎么突发流量的呢?

最开始,令牌桶是空的,我们以恒定速率往令牌桶里加入令牌,令牌桶被装满时,多余的令牌会被丢弃。当请求到来时,会先尝试从令牌桶获取令牌(相当于从令牌桶移除一个令牌),获取成功则请求被放行,获取失败则阻塞活拒绝请求。

image.png

令牌桶算法的特点

  • 最多可以存发 b 个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃
  • 每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。

令牌桶算法限制的是平均流量,因此其允许突发流量(只要令牌桶中有令牌,就不会被限流)

package com.on.sentinel;
import com.on.sentinel.exception.BlockException;
import org.apache.commons.lang3.StringUtils;
import java.util.concurrent.*;

public class TokenBucketRateLimit implements RateLimit, Runnable {
    /** * token 生成 速率 (每秒) */
    private Integer tokenLimitSecond;
    /** * 令牌桶队列 */
    private BlockingQueue<String /* token */> tokenBucket;
    private static final String TOKEN = "__token__";
    private ScheduledExecutorService scheduledExecutorService;

    public TokenBucketRateLimit(Integer bucketSize, Integer tokenLimitSecond) {
        this.tokenLimitSecond = tokenLimitSecond;
        this.tokenBucket = new LinkedBlockingDeque<>(bucketSize);
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        long interval = (1000 * 1000 * 1000) / tokenLimitSecond;
        scheduledExecutorService.scheduleAtFixedRate(this, 0, interval, TimeUnit.NANOSECONDS);
    }

    @Override
    public boolean canPass() throws BlockException {
        String token = tokenBucket.poll();
        if (StringUtils.isEmpty(token)) {
            throw new BlockException();
        }
        return true;
    }

    @Override
    public void run() {
        if (tokenBucket.remainingCapacity() == 0) {
            return;
        }
        tokenBucket.offer(TOKEN);
    }
}

目录

  1. 1、计数器固定窗口算法
  2. 2、计数器滑动窗口算法
  3. 3、漏桶算法
  4. 4、令牌桶算法

更多推荐文章

查看全部
  • Flutter 三方库 webdriver 在鸿蒙系统的适配与使用指南
  • Langchain-Chatchat 基于 LLM 构建本地智能知识库
  • Echidna 智能合约模糊测试工具:增强包功能解析与专业应用
  • WebSite-Downloader 网站整站下载工具使用指南
  • Flutter 三方库 dart_webrtc 鸿蒙化适配指南
  • YOLOv10n-SOEP-PST 助老机器人目标检测与识别系统详解
  • 带随机指针的链表复制算法:三步法原地实现
  • 开源逆袭!Wan2.1让家用电脑也能跑AI视频生成
  • 大模型微调(Fine-tuning)原理与实战指南
  • Dify 接入企业微信群聊机器人配置与集成指南
  • 2024 年 3 月编程语言排行榜:Python 领先优势显著
  • Python 基础语法完全指南:变量、数据类型与运算符
  • Ubuntu 下 llama.cpp 编译与性能调优实战
  • Flutter for OpenHarmony TabBar 高级标签系统与导航交互优化
  • SBUS 协议详解:从原理、硬件设计到代码实战
  • Git 远程仓库拉取或推送鉴权失败解决方案
  • Java 并发常见问题总结
  • CosyVoice 安装 openai-whisper 报错:ModuleNotFoundError: No module named 'pkg_resources'
  • VSCode Git 工作树多任务并行开发实践
  • 前端自动化部署最佳实践

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online