第 4 篇 · Part 4

撮合系统 11 万 TPS 压测是怎么做的

When the Bug Was in My Own Tool, Not the System Under Test

撮合系统 11 万 TPS 压测是怎么做的

那场压测里,我最先发现的问题不在被测系统里,在我自己的工具里。

这个故事,从一个"系统 bug"讲起。

一、10 个用户,压出 11 万 TPS

业务要求:10 个用户同时下单,下单接口的 TPS 达到 11 万。

10 个用户听起来不多,但每个用户都在高频下单、频繁撤单,意味着系统每秒要处理超过十万笔订单。

方案上我们用的是阶梯压测:同样的 10 用户并发,持续 1 小时、2 小时、4 小时、8 小时、24 小时,逐步加长时间。

因为要验证的不是瞬时峰值,而是系统能不能长期稳定运行。交易所的撮合 7×24 小时不关,跑 1 小时没问题不算数,跑一整天不出错才算数。

二、为什么放弃 JMeter:手动触发是假的

压测最大的难点不是"发请求",是"模拟真实用户"。

普通场景(下单、撤单)用 JMeter 就够。但有一个场景特殊:爆仓。

爆仓不是想触发就触发的——价格跌到强平线,用户才会被强平。真实世界里行情实时变化,可能突然有一大批用户同时被触发。要测好这个场景,压测脚本必须"听着行情走":价格一到阈值,立刻高并发地发起爆仓请求。

JMeter 做不了这个。它的参数化机制很难表达"根据实时行情动态触发"这种逻辑,硬写要写很复杂的预处理器,最后还是假的。

手动触发更不行:既模拟不了真实的高并发冲击,也覆盖不了行情波动带来的突发流量。

三、换 Go 重写压测脚本

后来我们调研到 Locust——Python 写的压测工具,脚本就是代码,可以让每个虚拟用户独立监听行情、独立触发爆仓,接近真实用户。

但它有个硬伤:Python 的 GIL,单机并发有瓶颈,压不满。

深挖后发现 Locust 有个方案:用 Go 重写 Worker(boomer),既保留"脚本就是代码"的灵活性,又突破 GIL 的并发瓶颈。

我用业余时间写了个 Go 版"行情驱动的爆仓压测客户端"做技术验证:每个虚拟用户独立监听模拟行情,价格到阈值自动触发爆仓,自己实现了行情通信和本地缓存,保证高并发下的稳定。跑通后,单机 TPS 比预期提升了 10 倍以上。

第一轮交付我们用 JMeter 版本按时完成(团队决策,保证进度),调研结论和技术验证整理成文档分享给团队,后续轮次换成了 Go 方案。

四、压测压出的"bug",根因在工具

工具换好,正式开压。压着压着,发现一个异常:条件单成交率明显偏低。

第一反应是系统有 bug。条件单是核心功能,成交率异常意味着用户的钱可能被错误处理——这是大事。

顺着链路查了很久,最后定位到:不是被测系统的问题,是压测脚本自己的问题。

条件单靠行情价格触发,而脚本里取的行情源失真——脚本"以为"的价格和市场实际价格对不上,条件单自然触发不了。

那一刻我意识到:压测工具自己要是不可信,压出来的所有数字都是假的。 你以为在测系统,其实在测工具的幻觉。

解决方式:自建了一个行情缓存服务,让压测脚本订阅真实行情流,行情断了回退到默认价,脚本不崩、不写死。

修好之后,成交率恢复正常。

五、真正的瓶颈:单集群撑不起

工具可信之后,系统的真实问题才浮出水面:单服务集群最大并发只有一万出头,离 11 万差得远。

这不是脚本的问题,是架构的问题——单集群就是撑不起这个量级。

我把数据整理成报告反馈上去,技术负责人拍板横向扩展:委托、结算、权益这些服务扩到几十个实例,撮合和风控翻倍扩容,入口网关加分流。

扩展完再压:平均响应时间 1-2 秒,24 小时稳定运行,实测 TPS 超过 11 万,达标。

最后

这场压测教会我两件事:

第一,模拟真实比压出数字更重要。手动触发、写死价格,看起来"在压测",其实在自欺。

第二,压一个真系统之前,先把自己的工具压到可信。 工具骗你,数字全错,后面所有结论都建在沙子上。

这两件事,后来贯穿了我整个测试生涯。

下一篇,写我自己写脚本,挖出"多空同时爆仓"的系统级 bug。再往后,还有一个差点让两个开发吵起来的 bug、一个两三百人交易所的测试团队到底长什么样。

我是旅人,一个在加密货币交易所当过测试工程师的人。关注我,看一个测试怎么在真金白银的系统里找 bug。

(本系列所有内容均为个人经历的技术复盘,公司及业务细节已做脱敏处理。)