百万订单的架构演化
2026-09-23 16:34 掸尘 阅读(176) 评论(0) 收藏 举报前言
入公司好几年了,做 PHP 开发以来,第一次遇到日均过百万的订单,QPS 也达到了 1500/s。这几年踩过不少坑,与大家分享一下。
初期技术栈
公司做的是花店 SaaS,核心业务包括聚合订单和聚合配送,当初为了快速验证业务。技术早期选型也是根据公司的资源情况定的。
- 语言:PHP 7.3
- 框架:Yii2
- Web 服务:Nginx + PHP-FPM
- 数据库:阿里云 RDS 一主多从
- 日志:阿里云 SLS
- 服务器:阿里云 ECS,最初是一台 8 核 16G
第一次崩盘
刚进公司不久,运营做了一波推广活动,母亲节当天系统直接崩了。当天单量也就 2~3 万,稍微有点并发就扛不住了。
第一次重构
经过分析,主要瓶颈在外卖订单推送这个环节。做了三件事:
- 引入队列异步处理:及时响应外卖平台的回调接口,避免阻塞主流程
- 接入阿里云负载均衡:流量分发到多台后端服务器
- PHP-FPM 调优 + 开启 OPcache:减少进程创建开销,提升执行效率
经过这一轮优化,系统顺利扛住了日均 20 万左右的订单。
第二次崩盘
随着订单表越来越大,在一次节日期间,用户反馈页面卡死,完全无法操作。
排查后发现数据库存在大量慢查询——根因是 order_id 字段类型是 VARCHAR,但外卖平台推送过来的是 INT 类型,MySQL 隐式类型转换导致索引失效,全表扫描把数据库拖垮了。
发现情况后当场改代码,把传入的 order_id 强制转换为字符串类型,同时紧急给 RDS 增加从库分摊读压力。前后花了大约 1~2 小时才恢复,代价是一部分用户流失。
教训深刻:VARCHAR 字段与 INT 值比较时的隐式类型转换,是 MySQL 索引失效的经典陷阱。
第二次优化
RDS 在高并发下表现不理想,经与阿里云沟通后决定迁移到 PolarDB。PolarDB 的核心优势是存储计算分离架构带来的——更快的弹性、更低的延迟、更高的一致性。
| 指标 | RDS | PolarDB |
|---|---|---|
| 主从延迟 | 毫秒 ~ 秒级 | < 毫秒级 |
| 弹性扩缩容 | 分钟级 | 秒级 |
| 高并发性能 | 1x(基准) | 6x(官方数据) |
同时做了以下配套工作:
- 慢查询治理:逐一分析 TOP N 慢 SQL,补索引、改写法
- 订单归档:订单表太大,改为每日自动归档历史数据,保持主表轻量
这次优化为百万订单奠定了核心基础。即使到了百万级,数据库各项指标依然健康。
第三次优化
手动扩容服务器太麻烦,转向阿里云的弹性伸缩(ESS),原理如下:
监控告警(CPU / 内存 / QPS)
↓
触发创建 ECS 实例
↓
自动部署 + 健康检查
↓
加入负载均衡,承接流量
弹性伸缩为节日大促的流量峰值提供了稳定性保障,平时不浪费资源,高峰自动扩容。
消息系统
系统里有页面通知和 APP 推送两块业务,量级不小——百万订单对应的消息通知量大概在 1000 万级。我们把消息系统单独拆成了一个独立服务:
- 框架:Hyperf 2.0(PHP 协程框架,性能好)
- 队列:RabbitMQ
- 服务器:8 核 16G,单台部署
所有消息都经过 RabbitMQ,再由 PHP 消费。RabbitMQ 性能本身还行,但我部署的是单机,没有做集群,量一上来就出现连接失败的情况。由于我对 RabbitMQ 集群运维不熟悉,也没太多精力折腾,后来直接切换到了阿里云托管的 RabbitMQ,问题才稳定下来。
回头看,技术选型上也有值得反思的地方。当初选 RabbitMQ 主要是因为我熟悉,但实际用下来,感觉 Kafka 更适合我们的场景——消息消费了就完了,不需要确认是否消费成功,属于典型的"发后即忘"模式。后来我优化了 RabbitMQ 的消费端,关闭了 ACK 确认机制,算是弥补了当初选型上的不足。
教训:技术选型不能只看"我熟不熟悉",更要看"业务场景适不适合"。
总结
- 语言选择:PHP 不是高并发的最优解,如果项目从零开始,高并发场景可以直接上 Go,能省下不少服务器成本,支撑百万订单需要 8核16G 20台
- 框架价值:Yii2 的日志体系真的很详细,为早期快速定位问题提供了极大方便
- 善用云服务:如果团队运维能力有限,尽量用云托管组件。比如我自己搭建的 RabbitMQ,随着流量增长,出现了不少运维问题,后来切换到云消息队列才稳定下来
欢迎关注公众号,第一时间更新

浙公网安备 33010602011771号