双十一当天,某发卡网系统在流量洪峰下面临崩溃边缘,随着订单激增,数据库连接池迅速耗尽,响应时间飙升,用户开始出现付款失败与页面超时,运维团队紧急启动限流与降级策略,临时扩容服务器并切换至缓存集群,优先保障核心交易链路,在数次险情中,系统通过动态调整熔断阈值和异步队列削峰,终于稳住负载,避免全站宕机,这场持续数小时的“生死时速”最终以支撑住峰值订单量告终,但也暴露出架构设计中的单点瓶颈与预案不足,为后续的系统重构埋下伏笔。
凌晨1:47,我的手机疯了
屏幕像爬满了萤火虫,整个运维群炸了锅,第一条消息是监控告警:订单队列积压超过50000条,第二条消息更可怕:数据库连接池耗尽,新的支付请求开始超时,第三条消息,所有人都慌了——老板在群里@了我三个问号。
这不是演习,这是我们发卡网系统“链动小铺”上线以来,第一次面对真实的高并发洪峰,那一夜,我亲眼看着一个日活不过千的小平台,在双11流量的洪水中挣扎求生。
场景:比技术更残酷的是现实
发卡网,说白了就是个卖虚拟商品的自动发货平台——游戏点卡、直播充值码、视频会员卡密,链动小铺的用户不多,但精准:主要是群接龙团长和微商代理,靠大量小额订单赚差价的“链动”玩家,平时每秒几百个请求,系统相安无事。
但双11这天,某知名游戏推出限定皮肤礼包,而我们是全网最低价渠道商,下午3点,运营在社区发了个帖子,晚上8点,流量涌入,晚上10点,系统开始“慢性死亡”。
用户那边的画面是这样的:选商品 → 提交订单 → 一直转圈 → 超时弹窗,但钱已经扣了,订单却没生成,客服群炸了:“吞钱了!” “退钱!” “再发一次货!”
那是真正的崩溃,技术上的,心理上的,业务上的,系统崩溃30分钟,直接影响是:74单重复扣款、21单超时丢单、2个小时的修复期,订单丢失率约12%,老板开会的语气,像是要处理叛徒。
复盘:高并发面前,我们犯了三个致命错误
当服务器日志被导出,当请求链路被拆解,真相浮出水面,活生生的教训,比任何技术文章都值得记下。
把数据库当成“万能收银台”
我们的架构非常“朴素”:用户下单 → 写入订单表 → 减库存 → 调用支付接口 → 回调修改状态 → 触发发货,所有步骤都压在MySQL一张大表上。
高并发下发生了什么?行锁,多个用户同时购买同一件商品,数据库的行锁机制让它们排队,一台服务器每秒能处理的写事务大概只有500左右,但流量峰值是每秒2000+次下单,排队结果就是:数据库连接池被慢查询堵死,新请求排队超时,崩溃连锁。
数据说话:监控显示,MySQL在高峰时段QPS一度冲到1800,但平均事务响应时间从正常的12ms飙升到5.2秒,数据库CPU利用率从30%拉到95%,慢查询日志一夜之间写了2GB。
库存扣减用“先查后减”
在扣库存时,我们写的伪代码是:
$stock = SELECT stock FROM goods WHERE id = 1;
if ($stock > 0) {
UPDATE goods SET stock = stock - 1 WHERE id = 1;
}
在高并发下,这个“先查后减”遇到了经典的超卖问题——同时有10个请求读到库存还有“1”,同时进入if块,最终10单成功出单,但库存实际变成了负数。
这直接导致:某些用户付款了,但系统提示库存不足无法发货,用户闹,客服急,运营骂。
单机部署+同步式外部调用
我们的架构是:单台应用服务器(8核16G) + 单台数据库(4核8G),支付回调是同步的——必须等银行返回“成功”才给用户发货。
但支付接口的延迟波动很大,正常200ms,高峰时可能超过3秒,3秒内,应用服务器线程被“卡住”,占满了Tomcat线程池,新请求来了,只能排队或超时。
那晚,Tomcat线程池最大300个,高峰期瞬间被支付回调线程占满200个,剩下100个线程要处理查询、下单、页面渲染——分秒之间,全部阻塞。
方案:我们是怎么用“技术断舍离”把系统拉回来的
事后,我们用了一周时间做了全面重构,下面这些方案,不一定是最前沿的,但每一条都是踩过坑后的真金白银。
订单拆分:独立订单系统
我们把订单服务从主应用中“拆”出来,独立成一个微服务,并且读写分离。
- 写库只存最近的1小时订单数据(用分表,按订单ID取模),保证写入性能。
- 读库通过binlog同步到ES或Redis,用户查订单时不走主库。
效果:写库的TPS从500提升到接近3000(取决于服务器配置),读请求几乎不触达主库。
库存扣减:Redis原子操作
用Redis的DECR命令替代数据库UPDATE:
if(redis->decr('goods_stock_1') >= 0) {
// 库存充足,异步写入订单
}
注意,DECR是原子操作,不会出现超卖,而且Redis单机就能扛几十万QPS的读写,瓶颈立刻消失。
我们还将Redis库存做了分层预热:热销商品预先加载到Redis,普通商品走数据库降级。
用户订单、发货完全异步化
支付回调不再直接处理订单逻辑,而是将消息写入RabbitMQ延迟队列,消费端(专门处理订单的Worker)从队列拉取消息,处理订单状态更新、库存确认、发货。
用户付款后,前端直接展示“支付成功,正在处理”,此时支付回调已经在队列里了,发货Worker在处理中,用户看到的是1-2秒的短暂等待,而不是长达半分钟的卡死。
Worker是按规则预先设置好数量的,比如高峰时启动20个Worker消费,平时2个,这解耦了入口压力和业务处理压力。
添加熔断、降级、限流
这不是空话,我们用Sentinel做了三件事:
- 熔断:支付回调接口连续失败率超过50%,自动熔断10秒,避免雪崩。
- 降级:当系统负载超过80%时,自动关闭“订单追踪”“推荐商品”等功能,优先保障下单和发货的核心链路。
- 限流:用滑动窗口算法控制接口QPS,下单接口”限流1000QPS,超出的请求直接返回“系统繁忙,请稍后重试”,不要怕丢单,丢单比吞单好处理一万倍。
重构后的数据对比(真实压力测试结果)
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 最高并发下单(QPS) | 350(崩溃线) | 3200(正常) |
| 数据库CPU利用率(峰值) | 95% | 35% |
| 支付回调成功率 | 78%(部分超时不处理) | 7% |
| 订单超时率(>5秒) | 12% | <0.1% |
| 服务器成本 | 2台(一主一从) | 3台(Nginx+App+Redis+MQ+Worker) |
| 用户投诉量 | 2600+/小时 | 减少到可忽略 |
对了,加服务器其实没多少成本,原先2台机器月租2000元,重构后3台加Redis实例总共约3200元,但那次双11的单日流水,因为系统稳定了,直接从20万跃升到80万。稳定性最终变成了真金白银。
写在最后:高并发不是技术秀,是业务底线
有人问我:“你们一个小发卡网,搞这么复杂干嘛?直接上云不就好了?”
我的答案是:技术方案从来不是越复杂越好,而是越匹配你的业务特点越好。 发卡网的特点是“小额、高频、虚拟商品、价格敏感”,核心痛点是“下单决策链短、冲动消费多、竞品多”,一次崩溃,用户下一秒就去隔壁平台了。
对于发卡网这类系统,我总结了一个“生存法则”:
- 库存是生命线:千万不能超卖,秒级扣减是最低要求。
- 支付是流量口:不能阻塞在同步调用上,异步是唯一的活路。
- 用户反馈要有反馈:哪怕只是“正在处理,请稍候”的轮询,也比“请求超时”强一万倍,人不怕等,怕的是不知道发生了什么。
- 测试不要心软:压测时打满CPU、打满线程池,看看哪里先死,修哪里。
说句很感性的话:一个系统的崩溃,本质上是对用户信任的背叛。 那个双11晚上,我看着群里的“吞钱”声讨,第一次理解——技术不仅是代码,更是公司与用户之间的契约,我们必须守护它。
希望这篇文章,能给那些正在或者即将面对高并发冲击的同行们,一点真实的参考,也欢迎在评论区聊聊你们的系统,在哪个瞬间“差点没挺过去”。
本文链接:https://www.ncwmj.com/news/10476.html
