1. 问题现象:支付成功但订单消失的诡异现象
上周五晚上10点23分,我正在悠闲地喝着咖啡检查生产环境日志,突然收到一连串用户投诉:"明明显示支付成功了,为什么订单列表里找不到?"。作为一个有五年电商系统开发经验的老手,我立刻意识到这不是普通的偶发问题。
通过查询支付网关回调记录,确认这些订单确实完成了第三方支付,金额也已经扣除。但诡异的是,订单表里就是找不到对应的记录。更令人不安的是,这种现象只出现在高峰期(晚8点-11点),白天完全正常。
2. 问题排查:从表象到根源的侦探之旅
2.1 初步排查方向
首先我排除了最明显的可能性:
- 支付回调处理逻辑:日志显示回调处理正常完成
- 事务回滚:没有异常日志,且支付成功不应该回滚
- 缓存不一致:直接查库也不存在记录
2.2 关键线索发现
在仔细比对日志时,我发现一个奇怪的时间差:支付回调处理完成时间与订单创建时间存在约8秒间隔。而我们的支付处理逻辑应该是同步完成的。
进一步检查发现,这8秒期间有数据库连接获取超时的警告日志。这让我把怀疑目光投向了数据库连接池。
3. MyBatis连接池深度解析
3.1 连接池配置现状
我们使用的是MyBatis默认的HikariCP连接池,配置如下:
spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.connection-timeout=3000 spring.datasource.hikari.idle-timeout=600003.2 连接池工作原理
HikariCP的工作流程可以类比为图书馆借书:
- 应用线程像读者一样请求连接(借书)
- 连接池像图书馆管理员,有空闲连接就直接分配
- 没有空闲时,要么等待(不超过connection-timeout),要么新建连接(不超过maximum-pool-size)
3.3 问题根因分析
在高峰期,我们的服务出现了以下情况:
- 并发请求突增导致连接池耗尽
- 新连接创建需要时间(平均2秒)
- 支付回调线程等待3秒后超时放弃
- 但支付网关已经扣款,导致数据不一致
4. 解决方案设计与实施
4.1 短期应急措施
立即采取的临时方案:
// 在支付回调处理中增加重试机制 @Transactional public void handlePaymentCallback(PaymentInfo info) { int retry = 0; while (retry < 3) { try { // 业务处理逻辑 return; } catch (CannotGetJdbcConnectionException e) { retry++; Thread.sleep(1000); } } // 记录补偿任务 }4.2 长期优化方案
经过压力测试后,我们调整了连接池配置:
# 根据实际并发量调整 spring.datasource.hikari.maximum-pool-size=50 spring.datasource.hikari.connection-timeout=5000 # 新增最小空闲连接 spring.datasource.hikari.minimum-idle=10 # 添加连接健康检查 spring.datasource.hikari.connection-test-query=SELECT 14.3 架构层面改进
- 引入熔断机制:当连接池使用率超过90%时,快速失败并降级
- 实现异步订单补偿:通过消息队列确保最终一致性
- 增加监控告警:对连接池使用率设置阈值告警
5. 生产环境验证与效果
5.1 压测对比数据
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 最大TPS | 120 | 350 |
| 平均响应时间 | 1.2s | 280ms |
| 错误率 | 8.7% | 0.02% |
5.2 实际业务影响
优化上线后:
- 订单丢失投诉降为零
- 高峰期系统稳定性显著提升
- 数据库连接数更加平稳,避免了剧烈波动
6. 经验总结与避坑指南
6.1 连接池配置黄金法则
maximum-pool-size不是越大越好,建议公式:
合理最大值 = (核心数 * 2) + 有效磁盘数我们16核服务器最终设置为50
connection-timeout应该大于平均查询耗时,但不超过用户可忍受时间
一定要设置minimum-idle,避免突发流量冲击
6.2 监控指标 checklist
必须监控的关键指标:
- 活跃连接数
- 空闲连接数
- 等待获取连接的线程数
- 连接获取平均耗时
6.3 其他常见陷阱
不同框架的默认配置差异:
- HikariCP默认maxPoolSize是10
- Tomcat JDBC默认maxActive是100
事务超时与连接超时的区别:
- 事务超时是整个事务的时限
- 连接超时是获取连接的等待时间
连接泄漏检测:
spring.datasource.hikari.leak-detection-threshold=60000
7. 进阶思考:分布式事务的考量
对于支付这类敏感操作,我们后来引入了Saga模式:
- 支付成功即创建订单(状态为处理中)
- 异步执行库存扣减等操作
- 任何步骤失败触发补偿流程
这种模式虽然复杂,但彻底解决了这类一致性问题。不过实施成本较高,需要根据业务重要性权衡。