详解【限流算法】:令牌桶、漏桶、计算器算法及Java实现

详解【限流算法】:令牌桶、漏桶、计算器算法及Java实现

令牌桶算法

令牌桶(Token Bucket)算法以一个设定的速率产生令牌(Token)并放入令牌桶,每次用户请求都得申请令牌,如果令牌不足,则拒绝请求。

令牌的数量也是有上限的。令牌的数量与时间和发放速率强相关,时间流逝的时间越长,会不断往桶里加入越多的令牌,如果令牌发放的速度比申请速度快,令牌桶会放满令牌,直到令牌占满整个令牌桶。

  • 想象一个固定容量的 “令牌桶”,系统按固定速率向桶中添加令牌(比如每秒放 10 个)。
  • 当有请求到达时,需要从桶中获取一个令牌才能被处理;如果桶中没有令牌,请求则被丢弃或排队。
  • 令牌桶的容量决定了允许的最大突发流量(比如桶容量 100,意味着最多允许 100 个请求同时突发)。

关键概念:

  • 令牌生成速率(r):每秒生成的令牌数量(控制长期平均速率)。
  • 令牌桶容量(b):桶最多能存放的令牌数(控制最大突发流量)。
  • 令牌消耗:每个请求消耗 1 个令牌(可调整为按请求大小消耗)。

特点:

  • 平滑流量:通过固定速率生成令牌,限制长期平均请求速率。消费速度不固定。
  • 允许突发:桶中累积的令牌可应对短时间的突发流量(只要不超过桶容量)。
  • 灵活性:可调整令牌生成速率和桶容量,适配不同场景。

Java代码实现

 /** * 令牌桶限流算法 */ public class TokenBucketLimiter { // 桶的容量 private final int capacity; // 令牌生成速度 (个/秒) private final int rate; // 当前令牌数量 private final AtomicInteger tokens; /** * 构造函数 * @param capacity 桶的容量 * @param rate 令牌生成速度 (个/秒) */ public TokenBucketLimiter(int capacity, int rate) { this.capacity = capacity; this.rate = rate; this.tokens = new AtomicInteger(capacity); // 初始化时令牌桶是满的 // 启动令牌生成器线程 startTokenProducer(); } /** * 启动一个定时任务,以固定速率向桶中添加令牌 */ private void startTokenProducer() { ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor(); // 延迟0秒后开始执行,每隔1秒执行一次 scheduledThreadPool.scheduleAtFixedRate(() -> { // 尝试增加令牌,确保不超过容量 // getAndUpdate 是一个原子操作,它会获取当前值,根据函数计算新值,并更新它 tokens.getAndUpdate(current -> Math.min(capacity, current + rate)); // System.out.println("令牌已补充,当前令牌数: " + tokens.get()); // 可以注释掉,用于调试 }, 0, 1, TimeUnit.SECONDS); } /** * 尝试获取一个令牌 * @return 如果获取成功则返回 true,否则返回 false */ public boolean tryAcquire() { return tryAcquire(1); } /** * 尝试获取指定数量的令牌 * @param numberOfTokens 需要获取的令牌数量 * @return 如果获取成功则返回 true,否则返回 false */ public boolean tryAcquire(int numberOfTokens) { if (numberOfTokens <= 0) { throw new IllegalArgumentException("令牌数量必须大于0"); } while (true) { int current = tokens.get(); // 如果当前令牌数不足以获取所需数量,则直接返回 false if (current < numberOfTokens) { return false; } // 尝试原子地减少令牌数 if (tokens.compareAndSet(current, current - numberOfTokens)) { // 减少成功,返回 true return true; } // 如果 compareAndSet 失败,说明在获取和设置之间有其他线程修改了令牌数, // 循环会重试,直到成功或确定无法获取为止。 } } /** * 获取当前桶中的令牌数 (主要用于调试和监控) * @return 当前令牌数 */ public int getCurrentTokens() { return tokens.get(); } }

漏桶算法

想象一个底部有漏洞的 “漏桶”,水(请求 / 数据包)从顶部流入桶中,桶底以固定速率漏水(处理请求)。无论流入速度多快(突发流量),漏水速度始终保持不变,从而限制了输出速率。如果流入速度超过漏水速度,桶会逐渐装满,当桶满后,多余的水会溢出(丢弃请求)。

关键概念:

  • 漏桶容量(b):桶最多能容纳的请求数(或数据量),决定了允许的最大突发流量缓冲。
  • 漏水速率(r):每秒从桶中处理的请求数(固定速率,控制长期输出速率)。
  • 流入速率:请求到达的速率(可变,可能突发)。

令牌桶算法是 “漏桶算法” 的改进版:漏桶算法仅限制输出速率,不允许突发流量;令牌桶允许突发,但长期速率受控。

特点:

  • 平滑输出:无论输入流量如何突发,输出速率始终保持固定(由漏水速率决定)。
  • 限制突发:桶容量限制了最大缓冲请求数,超过容量的突发流量会被丢弃。
  • 无累积令牌:与令牌桶不同,漏桶不会累积 “处理能力”,仅被动缓冲和匀速处理。
 /** * 漏桶限流算法 */ public class LeakyBucketLimiter { // 漏桶的容量 private final int capacity; // 漏水速率 (个/秒) private final int rate; // 当前桶中的请求数量 private final AtomicInteger water = new AtomicInteger(0); /** * 构造函数 * @param capacity 漏桶的容量 * @param rate 漏水速率 (个/秒) */ public LeakyBucketLimiter(int capacity, int rate) { this.capacity = capacity; this.rate = rate; // 启动漏水线程 startLeaker(); } /** * 启动一个定时任务,以固定速率从桶中"漏水"(处理请求) */ private void startLeaker() { ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor(); // 延迟0秒后开始执行,每隔1秒执行一次 scheduledThreadPool.scheduleAtFixedRate(() -> { // 每次漏水,尝试将当前水量减少rate个 // getAndUpdate 是原子操作,确保线程安全 water.getAndUpdate(current -> { int newWaterLevel = current - rate; // 不能让水量小于0 return Math.max(0, newWaterLevel); }); // System.out.println("漏水后,当前水量: " + water.get()); // 用于调试 }, 0, 1, TimeUnit.SECONDS); } /** * 尝试添加一个请求到漏桶中 * @return 如果添加成功(桶未满)则返回 true,否则返回 false(桶满,请求被丢弃) */ public boolean tryAddRequest() { return tryAddRequest(1); } /** * 尝试添加指定数量的请求到漏桶中 * @param numberOfRequests 需要添加的请求数量 * @return 如果添加成功(桶未满)则返回 true,否则返回 false(桶满,请求被丢弃) */ public boolean tryAddRequest(int numberOfRequests) { if (numberOfRequests <= 0) { throw new IllegalArgumentException("请求数量必须大于0"); } while (true) { int currentWaterLevel = water.get(); // 检查添加后是否会溢出 if (currentWaterLevel + numberOfRequests > capacity) { return false; // 桶满,拒绝请求 } // 尝试原子地增加水量 if (water.compareAndSet(currentWaterLevel, currentWaterLevel + numberOfRequests)) { return true; // 添加成功 } // 如果 compareAndSet 失败,则循环重试 } } /** * 获取当前桶中的请求数量 (主要用于调试和监控) * @return 当前水量 */ public int getCurrentWaterLevel() { return water.get(); } }

计数器算法

计数器算法的核心思想非常简单:在一个固定的时间窗口内,统计请求的数量,如果请求数超过了预设的阈值,就拒绝后续的请求。

可以想象成一个收费站,在一个小时内(时间窗口)最多允许 100 辆车通过(阈值)。当第 101 辆车到达时,收费站就会关闭栏杆,拒绝其通过。等到下一个小时开始,计数器清零,栏杆重新打开。

优点

  • 实现简单:逻辑非常清晰,代码容易编写和理解。
  • 高效:只涉及简单的时间比较和计数操作,性能开销极小。

缺点(临界问题)

        最严重的问题是 “临界区” 或 “突刺流量” 问题。假设时间窗口是 1 秒,阈值是 100。如果在第 0.9 秒时来了 100 个请求,全部被允许。然后在第 1.1 秒时(进入了新的窗口),又来了 100 个请求,也全部被允许。那么在 0.9 秒到 1.1 秒这短短 0.2 秒内,系统实际上处理了 200 个请求,这可能会瞬间压垮下游服务。

        这个问题的根源在于时间窗口的切换是瞬间完成的,没有平滑过渡。

 /** * 计数器限流算法 */ public class CounterLimiter { // 一个时间窗口内的最大请求数 private final int maxRequests; // 时间窗口大小(毫秒) private final long windowSize; // 当前窗口的请求计数器 private final AtomicInteger currentCount = new AtomicInteger(0); // 当前窗口的起始时间戳(毫秒) private final AtomicLong windowStartTime = new AtomicLong(System.currentTimeMillis()); /** * 构造函数 * @param maxRequests 一个时间窗口内的最大请求数 * @param windowSize 时间窗口大小(秒) */ public CounterLimiter(int maxRequests, int windowSize) { this.maxRequests = maxRequests; this.windowSize = windowSize * 1000L; // 转换为毫秒 // 启动一个定时任务,定期检查并重置窗口,避免长时间不请求导致的窗口不更新问题 startWindowResetTask(); } /** * 尝试获取一个请求许可 * @return 如果获取成功则返回 true,否则返回 false */ public boolean tryAcquire() { long now = System.currentTimeMillis(); // 1. 检查是否进入了新的时间窗口,如果是则重置计数器和窗口起始时间 // 使用 compareAndSet 确保重置操作的原子性 if (now - windowStartTime.get() > windowSize) { // 尝试原子地将窗口起始时间更新为当前时间 // 只有当当前窗口起始时间仍然是 oldStartTime 时才会更新成功 // 这可以防止多个线程同时重置窗口 long oldStartTime = windowStartTime.get(); if (windowStartTime.compareAndSet(oldStartTime, now)) { // 重置计数器 currentCount.set(0); System.out.println("时间窗口已重置,新窗口起始时间: " + now); } } // 2. 检查当前窗口内的请求数是否已超限,并原子地增加计数 int current = currentCount.getAndIncrement(); return current < maxRequests; } /** * 启动一个定时任务,定期检查窗口是否过期。 * 这是一个守护线程,主要用于在长时间没有请求的情况下,也能保证窗口能被重置。 */ private void startWindowResetTask() { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // 每隔 windowSize/2 的时间检查一次,确保窗口能及时重置 scheduler.scheduleAtFixedRate(() -> { long now = System.currentTimeMillis(); if (now - windowStartTime.get() > windowSize) { windowStartTime.set(now); currentCount.set(0); // System.out.println("定时任务触发窗口重置"); // 用于调试 } }, this.windowSize / 2, this.windowSize / 2, TimeUnit.MILLISECONDS); } }

可以通过Redis + Lua来实现计数器算法的功能。典型的场景就是用户登录场景,可以用计数器+验证码的功能来实现削峰,降低并发量。可以设定时间窗口为1s,请求根据具体业务设置请求数阈值,每当发出一个用户登录请求就通过Lua脚本在Redis中更新计数器。如果达到了阈值就使当前请求进入验证码流程,然后重置计数器。

Read more

【开源神器】只需3分钟,教你打造属于自己的微信自动化发送工具!

【开源神器】只需3分钟,教你打造属于自己的微信自动化发送工具!

🚀彻底解放双手!微信消息自动化发送脚本工具实战教程 🌈 个人主页:创客白泽 - ZEEKLOG博客 🔥 系列专栏:🐍《Python开源项目实战》 💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦 📌 概述 在当今数字化办公场景中,自动化工具已成为提升工作效率的利器。本文将深入剖析一个基于Python的微信自动化工具开发全过程,该工具集成了即时消息发送、定时任务管理和微信进程控制三大核心功能模块。 技术栈亮点: * PyQt5构建美观的GUI界面 * uiautomation实现Windows UI自动化 * psutil进行进程管理 * 多线程处理保持UI响应 * 完整的异常处理机制 🛠️ 功能全景 1. 核心功能模块 模块名称功能描述即时消息发送支持文本+文件混合发送,智能识别联系人定时任务管理精确到秒的定时发送,支持循环任务配置微信进程控制启动/激活/退出微信的一键操作 2. 特色功能 * 智能窗口激活:自动置顶微信窗口并居中显示

By Ne0inhk
个人所得税的APP模拟器,纯java版代码开源,截图录屏都可以【仅供参考】

个人所得税的APP模拟器,纯java版代码开源,截图录屏都可以【仅供参考】

文件下载地址:https://wenshushu.vip/pan/index.php?id=36    提取码:7bf9 给大家分享一个用纯Java实现的个人所得税计算模拟器,包含完整的GUI界面和核心计算逻辑,适合Java学习者和税务计算需求者参考使用。 一、项目简介 这是一个使用Java Swing开发的个人所得税计算模拟器,模拟了官方个税APP的核心功能,包括: · 综合所得年度汇算计算 · 税率表查询 · 专项扣除项目设置 · 税务计算结果展示 项目特点: · 100%纯Java实现,无第三方依赖 · 完整GUI界面,支持用户交互 · 详细的代码注释 · 遵循2023年最新个税政策 二、核心代码实现 1. 主程序入口 ```java package com.tax.calculator; import javax.swing.*; /**  * 个人所得税计算模拟器 - 主程序  * @author TaxDeveloper  * @version

By Ne0inhk
OpenAI 开源模型 gpt-oss 本地部署详细教程

OpenAI 开源模型 gpt-oss 本地部署详细教程

OpenAI 最近发布了其首个开源的开放权重模型gpt-oss,这在AI圈引起了巨大的轰动。对于广大开发者和AI爱好者来说,这意味着我们终于可以在自己的机器上,完全本地化地运行和探索这款强大的模型了。 本教程将一步一步指导你如何在Windows和Linux系统上,借助极其便捷的本地大模型运行框架Ollama,轻松部署和使用 gpt-oss 模型。 一、准备工作:系统配置与性能预期 在开始之前,了解运行环境非常重要。本次部署将在我的个人电脑上进行,下面是推荐配置: * CPU: 现代多核 CPU,如 Intel Core i7 或 AMD Ryzen 7 系列 * 内存 (RAM): 32 GB 或更多 * 显卡 (GPU): 强烈推荐 NVIDIA GeForce RTX 4090 (24 GB 显存)。这是确保大型模型流畅运行与高效微调的理想选择。 * 操作系统: Linux 或 Windows

By Ne0inhk
最新版 Kimi K2.5 进阶实战全攻略:从开源部署到 Agent 集群搭建(视频理解 + 多模态开发 + 高并发调优)

最新版 Kimi K2.5 进阶实战全攻略:从开源部署到 Agent 集群搭建(视频理解 + 多模态开发 + 高并发调优)

1 技术背景与核心架构原理 1.1 技术定位与版本说明 Kimi K2.5 是月之暗面于2026年初发布的开源多模态大语言模型,聚焦长上下文理解、原生多模态交互、Agent 原生支持三大核心能力,针对工业级落地场景完成了全链路优化。本次实战覆盖的开源版本包括: * kimi-k2.5-chat-70b:基础对话版,支持2000K token 上下文窗口,原生适配工具调用 * kimi-k2.5-multimodal-70b:多模态完整版,新增图像、长视频时序理解能力,支持最长10小时连续视频输入 * kimi-k2.5-agent-70b:Agent 优化版,强化多轮工具链执行、分布式状态同步能力,适配集群化部署 * 量化衍生版本:AWQ 4bit/8bit、FP8 量化版,适配低显存硬件环境,精度损失控制在1%以内 1.2 核心架构与技术亮点 1.2.1

By Ne0inhk