1. 项目概述:一场抢标风暴背后的系统真相
“Keep pure, keep smile”——这行字最初出现在我团队内部一份灰蓝色背景的故障复盘PPT封面上,不是口号,是压力峰值过去后,我们坐在会议室里喝着冷掉的咖啡,互相确认彼此没崩溃时的真实状态。它后来成了我们技术组的暗号:当某次上线后凌晨三点告警狂响,有人在群里发个笑脸表情,底下立刻跟一串“keep pure, keep smile”,没人多问,但都懂那底下压着多少没睡的夜、改了七版的SQL、被用户骂到关机又开机的客服电话录音。
这件事的起点特别朴素:一次常规的加息券活动。用户领了券,兴冲冲点开APP准备抢标,结果页面转圈、白屏、404,或者更魔幻的——页面终于加载出来,标的进度条已经100%,倒计时归零。客服工单像雪片一样飞进来:“你们是不是后台锁标了?”“券是假的吧?”“我刷新了37次,每次都是最后一秒被抢光,系统是不是在针对我?”起初我们真信了“抢小米手机就该这样”的自我安慰逻辑,直到某天单日投诉量突破日常均值的12倍,法务同事默默把《消费者权益保护法》第26条截图发到了技术群——那一刻,玩笑收住,键盘声变重。
核心问题从来不是“用户手速不够”,而是 三端流量在毫秒级时间窗口内,对同一套老旧架构发起饱和式冲击 。APP、官网、H5三套前端,共用两台Apache Web服务器做LVS负载;这两台Web服务器,又全部直连MySQL主库;而MySQL只有一主一从,从库几乎闲置。整个链路像一条细麻绳,两端系着千军万马,中间却只靠一根线吊着。当抢标指令发出,不是用户在抢标,是1500个并发请求在争抢同一张数据库连接、同一个Apache工作进程、同一个CPU时间片。页面打不开?不是网络断了,是你的HTTP请求在Apache队列里排到了第2800位,而它的最大承载量只有1024。抢到又回退?不是代码写错了,是事务提交时主库IO被打满,commit超时自动回滚,钱进了用户账户,标的进度却没更新,系统判定“不一致”,强制退款。
这个项目没有高大上的新技术名词,全是老老实实的容量水位线测算、连接数压测、SQL执行计划分析、配置文件逐行比对。它解决的不是“如何用AI重构金融系统”,而是“如何让1000个人同时点下‘立即投资’按钮时,系统别当场跪下”。如果你正负责一个用户量稳步增长、但技术债越堆越厚的中型业务平台,尤其是涉及交易、抢购、限时活动这类强并发场景,这篇复盘就是为你写的。它不教你画架构图,只告诉你:当监控告警第一次响起时,该先敲哪条命令;当用户开始质疑公平性时,你该拿什么数据去回应;当老板问“要几台服务器”时,你怎么算出那个“8”不是拍脑袋,而是基于每秒2376次UPDATE、平均响应延迟482ms、连接池耗尽率91.3%的真实数字。
2. 系统瓶颈深度拆解:从表象到根因的四层穿透
2.1 第一层:现象层——用户反馈与监控告警的交叉验证
所有技术决策必须始于对现象的诚实记录。我们没有跳过这一步直接写方案,而是花了整整两天,把客服系统里所有带“抢标”“打不开”“回退”关键词的工单导出,按时间戳、设备类型(iOS/Android/PC/H5)、用户等级(新/老/高净值)、券类型(加息/抵现)做了交叉统计。关键发现有三个:
- 时间强相关性 :92.7%的故障工单集中在抢标开始后前15秒,其中68%集中在第3-7秒。这说明问题不是随机抖动,而是确定性的瞬时洪峰。
- 端侧差异巨大 :APP端投诉量占总量的63%,官网占29%,H5仅8%。但H5在活动预热期的PV占比高达41%——这意味着H5用户大量涌入,却在抢标临门一脚时被APP和官网分流或挤出。
- 失败模式分层 :
- “打不开”类(41%):HTTP 503/504、TCP连接超时、SSL握手失败;
- “打开慢”类(33%):首屏加载>8s、API响应>5s;
- “操作失败”类(26%):支付成功但标的未锁定、标的锁定后数分钟内回退。
同步调取Zabbix监控数据,将工单时间戳与服务器指标曲线叠加。我们发现:在抢标开始瞬间,APP服务器的 load average 从1.2飙升至11.8,持续12秒后回落;官网服务器从0.8升至7.3,持续8秒。而网络带宽使用率峰值仅70%,丢包率为0—— 网络层被首先排除 。这个交叉验证过程至关重要:它把模糊的“用户说不行”转化成了可测量的“服务器在XX秒内无法处理XX类请求”。
2.2 第二层:Web层——Apache连接池的生死线
当确认不是网络问题,矛头直指Web服务器。我们登录两台APP服务器,执行了三组关键命令:
# 实时观察活跃连接数(每秒刷新)
watch -n 1 'netstat -an | grep :80 | grep ESTABLISHED | wc -l'
# 查看Apache工作进程状态
sudo apachectl status
# 统计当前httpd进程数(注意:ps -ef|grep httpd|wc -l会包含grep自身,实际应为ps -ef|grep httpd|grep -v grep|wc -l)
ps -ef | grep httpd | grep -v grep | wc -l
抢标瞬间, netstat 显示ESTABLISHED连接数稳定在2600-2800; apachectl status 输出显示 BusyWorkers: 1024 (已满), IdleWorkers: 0 ;而 ps 命令返回的httpd进程数恰好是1024。这印证了我们的怀疑: Apache的MaxRequestWorkers(旧版叫MaxClients)被设为1024,已成绝对瓶颈 。
为什么是1024?翻看 /etc/httpd/conf/httpd.conf ,发现注释写着:“# Default: 256, increased for high traffic”。这是三年前某次小规模活动后临时调的,从未回归基线。而Apache的并发处理能力,并非简单等于MaxRequestWorkers。它还受制于:
-
ServerLimit:必须≥MaxRequestWorkers,否则重启后实际生效值会被截断; -
StartServers/MinSpareServers/MaxSpareServers:影响进程预热速度,抢标前若空闲进程不足,新请求需等待fork新进程,增加延迟; -
KeepAliveTimeout:若设为5秒,一个用户连续刷新3次,就可能占用3个worker进程达15秒,远超实际处理时间。
我们做了压测对比:将MaxRequestWorkers从1024提升至3072(3×1024),在同等并发下, BusyWorkers 峰值降至2980, IdleWorkers 维持在100+,首屏加载P95从5.2s降至1.8s。但仍有约15%的请求超时——因为Web层只是第一道闸门,后面还堵着更窄的河道。
2.3 第三层:数据库层——主库的单点雪崩
Web层缓解后,用户反馈从“打不开”转向“抢到又回退”。日志里不再有 Connection refused ,但频繁出现 Lock wait timeout exceeded 和 Deadlock found when trying to get lock 。我们登录MySQL主库,执行:
-- 查看当前连接及状态
SHOW PROCESSLIST;
-- 查看锁等待情况
SELECT * FROM information_schema.INNODB_TRX\G
SELECT * FROM information_schema.INNODB_LOCK_WAITS\G
SELECT * FROM information_schema.INNODB_LOCKS\G
-- 查看QPS和TPS


被折叠的 条评论
为什么被折叠?



