去年双十一,我们一个中等规模的扫码购活动,差点把整个后端打趴下。 成功率只有 90% 。 说实话,当时老板的脸都绿了。 不就是扫个码吗? 我一开始也这么想。 直到我盯着火焰图里那条占了 40% 时间的条码定位函数,才意识到问题的严重性。
二维码解码的工程陷阱:从 200ms 到 10ms 的搏斗
大多数人以为扫码就是调用个库,比如 ZXing。 天真了。 真实场景的图片,那是啥样的? 反光、模糊、倾斜、遮挡,还有大爷大妈手抖拍出的抽象画。 通用库的处理方式,通常是一条流水线:灰度化 → 二值化 → 轮廓提取 → 透视变换 → 解码。 这里每一步都是计算陷阱。 比如二值化,全局阈值在大面积反光下会直接丢失特征。 我们压测的数据: 用 ZXing 默认参数,单帧处理耗时 180~230ms,在 iPhone 8 上。 更糟的是,识别率在弱光下只有 82%。
我们最后做了什么? 不是自己重写整个算法,而是重新编排了流水线,并加入了一个轻量级的质量判定。 原理不复杂: 对输入帧先做快速的高斯梯度评估,如果发现模糊,直接跳过,不进入后续的重型操作——这就像你眯着眼看不清东西时会先眨眨眼,而不是硬瞪。 然后,在二值化时,改用自适应局部阈值,但这会带来噪声,于是又后面接了一个基于连通域的面积滤波器。 这整个流程,用 OpenCV 和 NEON 指令重写后,单帧耗时降到了 18ms 以内。 再配合帧间状态机,连续成功两次才确认,误读率反而降到了 0.02%。 下面这张图粗略描述了改造后的流水线结构:

压测对比数据更是暴力: 10 万张现场采集的困难样本,旧方案成功率 92.7%,平均耗时 210ms;新方案成功率 99.8%,平均耗时 12ms。 这种数量级的提升,靠的不是什么黑魔法,就是对图像处理顺序的重构。 工程的美感就在这里。
分布式事务是怎么被打趴的——别再提 2PC 了
扫码购这个业务,天然是个跨服务的状态机: 扫码 → 加购物车 → 结算 → 扣库存 → 生成订单 → 支付。 早年我们用过一个“优雅”的TCC 方案,结果大促第一天,库存服务那家伙频频超时,整个链路的 try 阶段开始堆积,最终引发了雪崩。 当天晚上的复盘会,人人都不说话,只听见键盘的敲击声——都在改代码。
问题出在哪? TCC 需要预留资源,这在库存服务里意味着要先冻结库存。 并发一大,冻结操作就变成了热点行锁竞争。 我们的 MySQL 在 2000 TPS 时就开始明显抖动。 强一致的复杂性,直接扼杀了吞吐。

后来的方案? 彻底转向异步驱动的最终一致。 核心是把“扣库存”这个动作,做成一个幂等的异步操作,放在消息队列里。 架构上引入一个轻量级的事件表,放在订单服务的同一个本地数据库里,用事务外发消息。 消费者端通过版本号 + 去重表做幂等。 这套组合,让库存扣减的并发能力直接从 2000 涨到了 15000。 延迟? 从扫码成功到扣减完成的 P99 延迟是 1.2 秒,但用户根本无感知——因为前端展示是乐观更新。 唯一需要小心的,是那个最终一致的“最终”可能造成的超卖。 我们为此加了动态库存阈值,实时剩余小于 5% 时,自动切换为一段阻塞式的快速扣减,相当于一个保险丝。 压测时,我们故意把库存打到 1 件,系统硬是没超卖一件。 那种惊心动魄后的平静,才是架构师追求的瞬间。
客户端与后端的协议之痛:长连接不是银弹

扫码购的体验,有一半在“闪扫码”的爽快感上。 起初我们用 HTTP 短连接,扫码一次一个往返,延迟基本在 80ms 以上(RTT + 建连)。 这对那些在货架间快速扫码的用户来说,简直是灾难。 网络抖动一下,甚至出现重复扣款。 我们收到了一个投诉,吓出一身冷汗: 一个用户被扣了两次钱,因为他的 APP 没收到第一次响应,自动重试了。 这可是真金白银。
解决重复扣款,其实不是靠长连接,而是靠请求级幂等。 我们给每一次扫码生成一个全局唯一的 nonce,放在用户会话中,服务端用 Redis 做 5 分钟的重复判断。 这能挡住绝大部分问题。 但延迟还是高。 于是我们开始改造为基于 TCP 的长连接,用 Protobuf 做序列化,在同一个连接上做多路复用。 然而,坑又来了: 移动网络环境,长连接太容易假死了。 我们设了心跳,可频繁的心跳又加剧了耗电。 最后是折衷: 静默时 3 分钟一次心跳,活跃扫码时间隔缩短到 10 秒,并加入失败快速重连的指数退避。 连接复用后,平均扫码延迟降到 38ms,P99 也从 200ms 降到 90ms。 电力消耗增加了 5%,但转化率却提升了近 4 个点。 值得。
说回工程美学。 很多时候,优化不是去发明一个崭新的算法,而是重新组织数据的流动路径,让每一毫秒都不白白浪费。 扫码购这个看似简单的场景,把图像处理、分布式事务、网络协议这些领域搅在一起,酿成一杯五味杂陈的酒。 但你喝下去,只要能爬出那三个坑——解码流水线、分布式库存、客户端的协议泥潭——后面的路就开阔了。 下次再有人问“不就是扫个码吗”,我会笑着说: 是啊,可这码后面,藏着一整个需要精心维护的微型世界。