当前位置: 首页 > news >正文

冷启动第一秒放进来 200 个连接,数据库直接被打挂:RateLimiter 的 WarmUp 和漏桶的死队列


title: "冷启动第一秒放进来 200 个连接,数据库直接被打挂:RateLimiter 的 WarmUp 和漏桶的死队列"
tags: [限流, 令牌桶, 漏桶, 算法, Java, 后端]
category: 后端


限流加了,数据库还是被打挂

我们有一个给 B 端客户用的批量导入接口,平时 QPS 不高,但每天早高峰会有一波集中调用。为了护住后面的数据库,我在接口前加了一道 GuavaRateLimiter,限到 50 QPS。代码很简单:

// 限到每秒 50 个许可 private final RateLimiter limiter = RateLimiter.create(50.0); public ImportResult importBatch(BatchReq req) { limiter.acquire(); // 拿不到许可就阻塞等待 return importService.doImport(req); }

上线后头几天风平浪静。直到某天早上应用因为 OOM 重启,重启完成后第一秒就被灌进来 200 多个积压请求,数据库连接池瞬间被打满,后续所有请求全部超时,数据库那台机器 CPU 直接 100%。

当时的困惑是:不是限了 50 QPS 吗,怎么还会被冲垮?后来读了一遍RateLimiter的源码才明白两件事:第一,RateLimiter.create(50)用的是SmoothBursty,它允许把过去没用掉的令牌攒起来,冷启动时桶里可能已经有几百个令牌,所以重启后第一秒能一口气放行一大批;第二,真正的「冷启动保护」要用SmoothWarmingUp,而我们对它的「预热」语义理解反了。另一个隐患在我们同时自研的「漏桶」实现里:一个无界队列把请求全接住,结果下游恢复不了时队列无限增长,反倒成了 OOM 的第二个来源。

令牌桶与漏桶的本质差异

先说清楚两个算法的模型,它们护系统的姿势完全不同:

  • 令牌桶:以固定速率往桶里丢令牌,请求来了先取令牌,取不到就等或拒。桶有容量上限,能容忍短时突发(攒下的令牌一次性放出去)。
  • 漏桶:请求像水一样进桶,桶底下以固定速率漏水(处理)。桶满就溢出(拒绝)。它强制平滑输出,不关心你前面来多猛,下游速率恒定,突发会被排队或丢弃。

一句话对比:令牌桶「允许你偶尔冲一下」,漏桶「无论如何都匀速」,两者一个在入口宽容、一个在出口整形。选错模型,限流反而帮倒忙——我们这次就是「想要护数据库(该用漏桶的匀速)却用了令牌桶的突发」。

RateLimiter 的两种实现:Bursty 与 WarmingUp

RateLimiter.create有两个工厂方法,内部对应两套子类:

// 令牌桶(SmoothBursty):允许攒令牌,支持突发 public static RateLimiter create(double permitsPerSecond) { return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer()); } // 预热型(SmoothWarmingUp):有预热期,冷启动阶段速率从低到高爬升 public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) { RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit); rateLimiter.setRate(permitsPerSecond); return rateLimiter; }

逐行拆解:

  • 第 2 行create(double)走的是SmoothBursty,它维护maxBurstSeconds(默认 1 秒),意味着桶里最多攒permitsPerSecond * 1个令牌。
  • 第 6 行create(double, warmupPeriod, unit)SmoothWarmingUp,多了一个预热时长参数,比如 10 秒。

SmoothBursty的突发能力来自它的reserve逻辑:只要距离上次取令牌的时间足够长,它就把这段时间累积的令牌都算给你。acquire()在冷启动瞬间可能一次性放行的数量约等于「空闲时长 × 速率」。我们的应用重启后空闲了几十秒,桶里攒了几百令牌,所以第一秒才会「放行 200+」。

SmoothWarmingUp的「预热」恰恰是为了反突发:它让限流器在刚启动时只放很低的速率,随预热期推进逐步爬到目标速率。我们当时误以为「WarmUp 是启动快、后面慢」,其实是反的——它是「启动慢、后面快」,专门用来给连接池、缓存等冷资源留出热身时间。把它用对,重启后第一秒就不会再被冲爆。

正确用法:给数据库类接口加预热

// 预热 10 秒,期间速率从低逐步升到 50 QPS private final RateLimiter limiter = RateLimiter.create(50.0, 10, TimeUnit.SECONDS); public ImportResult importBatch(BatchReq req) { if (!limiter.tryAcquire()) { // 拿不到立即拒,不阻塞积压 throw new TooManyRequestsException("导入过于频繁,请稍后重试"); } return importService.doImport(req); }

逐行拆解:

  • 第 3 行create(50.0, 10, TimeUnit.SECONDS)让限流器用SmoothWarmingUp,重启后的前 10 秒速率是逐步爬升的,第一秒可能只放 5~10 个,给数据库连接池留出建立连接、预热缓存的时间。
  • 第 6 行tryAcquire()改成「拿不到就拒」,而不是acquire()的「拿不到就死等」。这点很关键:如果请求在限流器外排队等待,表面 QPS 是被限住了,但线程却被占着,连接池和线程池照样会被压垮。
  • 第 8 行直接抛业务异常,把压力交给客户端重试,比让服务端线程干等长。

漏桶的死队列:另一种 OOM

我们另一条链路曾自研过漏桶,用一个阻塞队列当「桶」:

public class LeakyBucket { private final int capacity; private final BlockingQueue<Runnable> bucket; private final ScheduledExecutorService leak = Executors.newSingleThreadScheduledExecutor(); // 固定速率「漏水」 public LeakyBucket(int capacity, int leakRatePerSec) { this.capacity = capacity; this.bucket = new LinkedBlockingQueue<>(); // 无界队列! leak.scheduleAtFixedRate(this::leak, 0, 1000 / leakRatePerSec, TimeUnit.MILLISECONDS); } public boolean offer(Runnable task) { if (bucket.size() >= capacity) return false; // 满了才拒 return bucket.offer(task); // 没满就进队列等着 } }

逐行拆解:

  • 第 6 行new LinkedBlockingQueue<>()用了无界队列,这是埋雷:offer几乎永远不会因为「队列满」而拒绝,所有请求都被接住堆在内存里。
  • 第 13 行bucket.size() >= capacity的判满逻辑其实对无界队列形同虚设——只要offer不抛异常,请求就一直进。
  • 真正的问题在「漏水」速率:当下游(数据库)长时间不可用时,漏水线程处理不动,bucket里的任务无限堆积,最终把堆内存吃光,触发 OOM。我们那次漏桶实现的 OOM,根因就是这个无界队列,而不是限流本身。

修法是把队列改成有界,并且让offer在队满时真正拒绝,而不是默默接住:

this.bucket = new ArrayBlockingQueue<>(capacity); // 有界,满了 offer 返回 false // offer 时已满 -> 直接拒绝,而不是堆积 public boolean offer(Runnable task) { return bucket.offer(task); // 队满自动返回 false,调用方据此拒流 }

两种限流该怎么选

维度令牌桶(SmoothBursty)令牌桶(WarmingUp)漏桶
对突发流量的态度允许攒令牌,容忍突发预热期抑制突发强制匀速,突发排队/丢弃
冷启动保护无,反而易冲爆有,速率渐升有,恒定输出
队列风险无队列无队列无界队列会 OOM
适合场景秒杀入口、可突发的业务护 DB、护缓存、冷资源下游脆弱、必须匀速的链路

我的经验:护数据库、护第三方这种「慢热且脆弱」的依赖,优先用漏桶式的匀速整形,或者用带预热的令牌桶;纯入口限流、允许短时突发的,才用普通SmoothBursty。而无论哪种,「拿不到就拒」都该优于「拿不到就等」。

补充一个我们踩过的细节:tryAcquire()不传等待时间时和acquire()一样会阻塞,区别在于它返回布尔值让你自己决定拒流策略。如果下游恢复极慢,即便用了tryAcquire拒流,被拒的请求在客户端重试时仍会再次打进来,形成「拒绝—重试—再拒绝」的脉冲。所以限流之外最好再配一层熔断(例如 Sentinel 的慢调用比例规则),让下游真正不可用时整条链路快速失败,而不是在限流器边缘反复横跳,把压力转化成无意义的重试风暴。

复盘数字

  • 这次事故持续约 18 分钟,期间导入接口成功率跌到 0,连带把同库的订单查询也拖慢(连接池被占满)。
  • 加预热 + 改tryAcquire后,同样早高峰 200+ 积压请求,第一秒实际放行从 200+ 降到 12 个,数据库连接池使用率峰值从 100% 降到 63%。
  • 那条自研漏桶的无界队列,在改造前高峰期曾把单实例堆内存从 1.2 GB 推到 3.1 GB,是有名的「慢泄漏」,这次一并改成有界。

我的取舍

我不建议无脑用RateLimiter.create(permits)然后acquire()了事——那是给「能突发、能等待」的场景准备的,拿来护数据库恰恰是反面。我的判断是:凡是身后站着连接池、缓存、第三方慢依赖的接口,一律用WarmingUp+tryAcquire组合,宁可让客户端看到几个「稍后重试」,也别让服务端线程在限流器外排长队。至于漏桶,自研时一定要把「桶」做成有界,无界队列不是漏桶,是一个定时炸弹。

思考题

RateLimiter.create(50)RateLimiter.create(50, 10, SECONDS)分别放在一个「先 sleep 30 秒再瞬间打 300 个请求」的用例里,打印前 10 次acquire()各自的等待时间,你会清楚看到 Bursty 把积压令牌一次性放出、WarmingUp 把速率压在前几秒的差别。

http://www.jsqmd.com/news/1379723/

相关文章:

  • 学工系统-学工一体化平台-学校学工系统 解决方案
  • 做镁合金加工需要采购切削液,哪家正规加工厂更值得信赖? - GrowthUME
  • 情感陪伴AI:从大语言模型到数字基础设施的技术演进与应用
  • 寻找老牌生产企业?皮箱模拟行走路况试验机哪家好与诚信厂家联系方式 - 品牌推荐大师
  • python hot 100——4 动态规划
  • 湖州热门烘焙面包培训机构|港焙学校真实测评 - 港焙西点-知美人美学
  • 3天让你的安卓手机脱胎换骨:Universal Android Debloater 终极优化指南
  • 猫抓浏览器扩展:三步轻松抓取网页视频资源的终极指南
  • 湖南省选武校看这篇就够了|洪江市、冷水江市、涟源市、吉首市文武学校口碑实力汇总 - 圣龙武术朱老师
  • 2026台式RFID和条码打印机行业分析:双模识别技术如何驱动工业标识升级?
  • PyQt5安装失败全解析:从VC++编译到.whl轮子解决方案
  • Sticky:Linux桌面便签的终极解决方案,让数字灵感永不丢失
  • AI音乐生成与音频分析技术:从重型金属吉他到自动化音乐处理实践
  • 2026年阜新新媒体运营推广合规服务商中网创信怎么选?服务体系、内容协同与避坑指南 - 中国远见品牌企业资讯
  • 商业综合体地下车库反向寻车,千万别盲目上蓝牙AOA!
  • Nemotron 3.5 Lightning + NeMo Switchyard深度解析:30B MoE仅3B活跃参数,从蒸馏到路由的AI Agent执行层新范式
  • 2026广东成人高考|财大全新专业调整,上班族升本优选 - 湖北找学校
  • 小白也能学会!如何远程访问公司内网的 FastAPI 服务
  • 从Function Calling到MCP:构建安全可控的生产级AI Agent系统
  • 9款必备MCP Server配置指南:解锁Cursor AI私有数据访问与效率革命
  • 如何系统评估与淘到高价值周边模型:从信息搜集到真伪鉴别全流程
  • 2026年涿州钻石回收怎么选靠谱商家?(185-3117-2838)火炬路204赵掌柜二奢店实体门店规范鉴定指南 - 赵掌柜二奢
  • Pygame性能优化:脏矩形技术原理、实战与避坑指南
  • Spring IoCDI
  • OFD文件预览方式详解(在线预览),采用异步加载OFD文件实现在线预览解析ofd文件 - usdoc
  • Windows GDI文本绘制:TextOut与DrawText的核心原理与实战选择
  • 远程联机软件推荐 远程联机用哪个软件好
  • G-Helper风扇曲线精准调校:华硕笔记本散热性能深度优化指南
  • 2026年湛江安防监控与弱电机房,办公网络怎么建? - LYL仔仔
  • 企业媒体发稿怎么做好AI收录沉淀?传播易智能投放解决方案