晚上六点零七分,十九家门店的拣货端几乎同时弹出超时提示。无锡这家本地商超连锁刚把线上订单切到统一调度,市区十二家、江阴宜兴七家,日均线上单量八千六百单上下,事故发生在一次全城满减开始后的头一分钟。接手这套系统的小程序开发公司在复盘会上翻出四个做法,没有一个算离奇,都是单量不大时最稳妥、单量一起来就集体失效的那一类。
把日均订单摊到八万六千四百秒,算出来的并发没有参考价值
常见算法是拿日均单量除以一天的秒数:八千六百除以八万六千四百,得零点一,结论是四核八G 绰绰有余。这一步错在把订单当成一天里均匀滴下来的水。商超线上订单的形状是两个尖峰,工作日十七点半到十八点半出掉全天的三成四,周末上午九点到十一点再出两成一。太湖新城那几家社区店更极端,晚高峰一小时的单量顶得上白天六小时。
第二处漏算是请求放大。用户点开小程序首页,实测触发十一个接口:定位选店、门店营业状态、首页楼层、优惠券、购物车角标、会员积分、配送时段、批量库存查询各一次,商品瀑布流首屏两次,埋点一次。真下单时再叠加验价、锁库存、创建订单、支付预下单四个。一单背后从来不是一次请求,是十几到二十次。
后果在那次满减里看得很清楚:零秒到六十秒之间涌进一万两千个会话,网关侧峰值冲到每秒一千二百多个请求,而应用只有一台,四百多个请求排队到超时,前端表现是转圈十五秒后弹「网络开小差」,那一分钟的下单成功率只有六成一。
整改动作是换掉估算口径:取近三十天里最高的那一分钟单量,乘以实测的请求放大倍数,再乘三倍安全系数,得出压测目标值,这家连锁算下来是每秒九百到一千二百个请求。这个数字要写进和小程序开发公司签的验收条款,写成「峰值一千二百 QPS 下 P95 不超过八百毫秒」,而不是含糊的「系统运行流畅」。
一台大机器扛全城,扛不住就往上升配
做法是把门店接口、后台管理、调度服务和数据库全塞进一台八核十六G 的云主机,压力大了就纵向升到十六核三十二G。单量小的时候这样确实省心,隐患有三处。
数据库和应用抢同一份内存,MySQL 的 innodb_buffer_pool 吃掉八G,应用 JVM 再要四G,剩下的留给系统和缓存,晚高峰有人在后台拉一张销售报表,前台接口响应就集体抖动。纵向升配必须停机,控制台上点一下,实际重启窗口三到八分钟,只能约在凌晨两点,白天出状况只能硬扛。最伤的是单点:那次故障的直接原因是这台机器磁盘 IO 打满,十九家店的下单和拣货同时停摆四十分钟,门店退回纸质拣货单,晚班多留了两个人手工补录。
整改后的结构并不复杂:两台四核八G 的应用节点挂在负载均衡后面,会话和验证码挪进 Redis,数据库单独换成四核八G 的高可用版本,主备切换实测三十秒内完成;商品图交给对象存储加 CDN,首页六十多张图不再占应用带宽。费用反而降了,原来一台十六核三十二G 加固定十兆带宽月付两千八百多,改完是应用两台约七百、数据库约九百、缓存二G 约一百八、CDN 按流量约两百,合计不到两千。可用区选在华东二,无锡出口过去实测五到八毫秒,比放在华北低二十五毫秒左右,这段延迟在拣货端每三秒一次的交互里是能被手感直接察觉的。不少小程序开发公司在方案阶段跳过这一段,只写「部署于阿里云」,验收时就没有任何东西可对照。
十九家店共用一张库存表,靠行锁保证不超卖
做法是所有门店库存放在一张 stock 表,字段是门店号加商品号加可售数量,下单时 select for update 锁住那一行再扣减。逻辑上挑不出毛病,正确性也确实有保证,代价是把并发压力全压到了几十个热点行上。
商超的热点集中得吓人:促销当天,鸡蛋、鲜奶、抽纸这三类占了全部库存行更新的四成七。十九家门店同时卖同一个 SKU,跨店调拨单又要更新同一行,行锁排队瞬间形成。MySQL 的 innodb_lock_wait_timeout 默认五十秒,排在后面的事务要么等到超时报错,要么把连接池占满——连接池 maxActive 用的还是框架默认值八,晚高峰八个连接全卡在等锁上,整个应用从外面看就像死了。那次事故超卖三十七单,全部来自超时后用户的重复提交。
整改分三步走。库存表按门店号分片,无锡市区十二家店和江阴宜兴七家店落到不同的库,热点行争抢范围先缩小一个量级。促销 SKU 走缓存预扣减,扣减用 Lua 脚本把判断和扣减压进一次原子操作,成功后发消息队列异步落库,每十分钟与数据库对一次账,差异超过两件立即告警。跨店调拨不再和销售单抢锁,单独走一条串行队列,幂等键用调拨单号,重复提交直接返回首次结果。连接池 maxActive 调到五十,最大等待时间设成三秒——宁可让顾客三秒后看到明确的失败提示,也不要让他对着转圈等五十秒。这几个参数要落到配置文件里并写进交付文档,小程序开发公司只给一句「已做防超卖处理」是没法验收的。
压测只打下单接口,验收只看平均响应时间
做法是拿一份现成脚本对着下单接口开两千线程,看到平均响应时间一百八十毫秒,报告上写「性能达标」。这份报告在两个地方失真。
并发的大头根本不在下单。拣货端才是:十九家店平均每店六台 PDA,每三秒轮询一次新任务,什么都不干的常态就是每秒三十八次请求,晚高峰翻倍,还要叠加拣货状态回传、缺货登记、称重回传和小票打印回调。这些接口在压测脚本里一次都没出现过。平均值同样会骗人,实测平均一百八十毫秒的那个接口,P99 是四千二百毫秒,被拖慢的百分之一恰好落在拣货员身上,他们等不动就重复点,重复任务又反过来加压,崇安寺那家店当晚重复任务占到全部任务的一成三。
整改动作停在这一条:把拣货端每三秒的定时轮询改掉,具备条件就换成长连接,服务端有新任务才推送;一时改不动的,把轮询做成退避式,连续三次没有新任务就从三秒退到十秒再退到三十秒,一有任务立刻回到三秒,仅这一处改完常态请求量降了六成七。验收口径同步换成 P95 不超过八百毫秒、P99 不超过一千五百毫秒、错误率低于千分之五,平均值只作参考。压测脚本按真实时段的接口配比混合编排,下单、拣货轮询、调拨、报表按五比三比一比一,跑满三十分钟稳定态而不是打三分钟看峰值,脚本和参数要求小程序开发公司随代码一起交付进仓库,此后每次版本发布前跑一遍,跑不过就不许发版。




