撮合系统 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。
(本系列所有内容均为个人经历的技术复盘,公司及业务细节已做脱敏处理。)