深入解析:订单支付后库存不扣减,如何用RabbitMQ来优化?

news/2026/1/23 20:26:03/文章来源:https://www.cnblogs.com/yangykaifa/p/19523949

上周在Review学员代码的时候,我们发现了一个很基础但很重要的问题:支付回调流程中缺少了库存扣减环节。这类问题虽然基础,但如果直接进入生产环境,可能导致库存的数据和实际销售的情况不一致,出现超卖的情况。能够及时发现这种问题,这就是Review代码的重要性。

先看这段有问题的代码:

// 原来的支付回调逻辑(问题代码)
func PaymentCallback(ctx context.Context, orderID uint32) error {
// 只更新订单状态为已支付
_, err := dao.OrderInfo.Ctx(ctx).Where("id=?", orderID).
Data(g.Map{"status": consts.OrderStatusPaid}).Update()
if err != nil {
return err
}
// 缺少库存扣减逻辑!商品库存还是原样
return nil
}

这个问题的核心在于流程设计的不完整,用户支付成功后只是更新了订单状态,却没有同步调整商品库存,可能导致其他用户购买时看到的库存数据不正确。

想要解决这个问题,需要补充缺失的逻辑,更要考虑分布式系统下的流程合理性,这里我们选择引入RabbitMQ实现事件驱动架构,既能解决当前问题,也能方便后续的业务扩展。

问题分析

业务逻辑理解不正确

原逻辑对订单流程的理解是"创建订单→支付成功→完成交易",但正确的流程应该要包含库存相关的环节:

创建订单→预扣库存→支付成功→确认交易→后续处理

不同服务之间的协作

在微服务架构中:

  • 订单服务负责订单状态流转
  • 商品服务负责库存数据维护

两个服务需要通过规范的协作机制保证数据一致性,而不是简单的同步调用。

解决方案

我们重新设计了包含库存管理的订单流程,通过RabbitMQ实现服务间的解耦通信:

创建订单时预扣库存

将库存扣减提前到订单创建的阶段,通过数据库事务保证操作的原子性:

// app/goods/internal/logic/goods_info/goods_info.go
func ReduceStock(ctx context.Context, req *rabbitmq.OrderCreatedEvent) error {
// 使用数据库事务确保原子性
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
for _, goods := range req.GoodsInfo {
// 1. 查询当前库存
var goodsInfo entity.GoodsInfo
if err := dao.GoodsInfo.Ctx(ctx).TX(tx).
Where("id = ?", goods.GoodsId).
Fields("stock").
Scan(&goodsInfo); err != nil {
return gerror.Wrapf(err, "查询商品{%d}库存失败", goods.GoodsId)
}
// 2. 判断库存是否足够
if goodsInfo.Stock < goods.Count {
return gerror.Newf("商品{%d}库存不足(当前:%d, 需要:%d)",
goods.GoodsId, goodsInfo.Stock, goods.Count)
}
// 3. 扣减库存
newStock := goodsInfo.Stock - goods.Count
g.Log().Infof(ctx, "商品{%d}新库存:%d", goods.GoodsId, newStock)
if _, err := dao.GoodsInfo.Ctx(ctx).TX(tx).
Where("id = ?", goods.GoodsId).
Data(g.Map{"stock": newStock}).
Update(); err != nil {
return gerror.Wrapf(err, "更新商品{%d}库存失败", goods.GoodsId)
}
}
return nil
})
return err
}

设计思路

  • 提前锁定库存,避免支付过程中商品被重复购买
  • 事务保证库存检查与扣减的原子性,防止并发问题
  • 库存不足时直接阻断订单创建,提升用户体验

支付成功后的确认处理

支付完成后,通过事件通知触发后续清理工作:

// 支付回调逻辑
func PaymentCallback(ctx context.Context, orderID uint32) error {
// 1. 更新订单状态
_, err := dao.OrderInfo.Ctx(ctx).Where("id=?", orderID).
Data(g.Map{"status": consts.OrderStatusPaid}).Update()
if err != nil {
return err
}
// 2. 获取订单详情(包含商品信息)
orderDetail, err := GetOrderDetail(ctx, orderID)
if err != nil {
return err
}
// 3. 发布库存确认事件(这里库存已在创建订单时预扣)
// 主要是清理缓存等后续操作
go func() {
// 异步清理商品缓存
if err := goodsRedis.DeleteKeys(context.Background(), orderDetail.GoodsIDs); err != nil {
g.Log().Errorf(ctx, "清理商品缓存失败: %v", err)
}
}()
return nil
}

订单超时的库存返还机制

为避免用户下单后未支付导致库存长时间锁定,设计超时返还逻辑:

// app/order/utility/consumer/order_timeout_consumer.go
func (c *OrderTimeoutConsumer) HandleMessage(ctx context.Context, msg amqp.Delivery) error {
// 解析订单超时事件
var event rabbitmq.OrderTimeoutEvent
err := rabbitmq.UnmarshalEvent(msg.Body, &event)
if err != nil {
return err
}
// 判断是否真正超时(30分钟未支付)
eventTime, _ := time.Parse(time.RFC3339, event.TimeStamp)
if time.Now().After(eventTime.Add(30 * time.Minute)) {
// 处理订单超时
err = order_info.HandleOrderTimeoutResult(ctx, event.OrderId)
if err != nil {
return err
}
// 发布库存返还事件
eventReq, err := order_info.GetOrderDetail(ctx, event.OrderId)
if err == nil {
go rabbitmq.PublishReturnStockEvent(event.OrderId, eventReq)
}
}
return nil
}

库存返还的具体实现

通过并发处理提升库存返还效率:

// app/goods/internal/logic/goods_info/goods_info.go
func ReturnStock(ctx context.Context, req *rabbitmq.OrderStockReturnEvent) ([]*rabbitmq.OrderGoodsInfo, error) {
// 使用goroutine并发处理每个商品
resultChan := make(chan *rabbitmq.OrderGoodsInfo, len(req.GoodsInfo))
var wg sync.WaitGroup
wg.Add(len(req.GoodsInfo))
for _, stockInfo := range req.GoodsInfo {
go func(stockInfo *rabbitmq.OrderGoodsInfo) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
g.Log().Errorf(ctx, "库存返还panic: %v", r)
}
}()
// 查询当前库存
var goodsInfo entity.GoodsInfo
err := dao.GoodsInfo.Ctx(ctx).Where("id=?", stockInfo.GoodsId).
Fields("stock").Scan(&goodsInfo)
if err != nil {
resultChan <- &rabbitmq.OrderGoodsInfo{
GoodsId: stockInfo.GoodsId,
Count:   stockInfo.Count,
}
return
}
// 返还库存
newStock := goodsInfo.Stock + stockInfo.Count
_, err = dao.GoodsInfo.Ctx(ctx).Where("id=?", stockInfo.GoodsId).
Data(g.Map{"stock": newStock}).Update()
if err != nil {
resultChan <- &rabbitmq.OrderGoodsInfo{
GoodsId: stockInfo.GoodsId,
Count:   stockInfo.Count,
}
return
}
g.Log().Infof(ctx, "商品{%d}库存返还成功,新库存:%d", stockInfo.GoodsId, newStock)
}(stockInfo)
}
wg.Wait()
close(resultChan)
// 收集处理结果
var resultArr []*rabbitmq.OrderGoodsInfo
for res := range resultChan {
resultArr = append(resultArr, res)
}
return resultArr, nil
}

消息队列的事件驱动架构

定义核心事件实现服务解耦:

// 事件定义
type OrderCreatedEvent struct {
OrderId   uint32             `json:"order_id"`
GoodsInfo []*OrderGoodsInfo `json:"goods_info"`
}
type OrderStockReturnEvent struct {
OrderId   uint32             `json:"order_id"`
GoodsInfo []*OrderGoodsInfo `json:"goods_info"`
}

事件流设计

用户下单→OrderCreated事件→商品服务扣减库存↓
支付超时→OrderTimeout事件→商品服务返还库存↓
支付成功→订单状态更新+缓存清理

技术难点与解决方案

难点1:分布式系统的数据一致性

问题:订单与库存数据分属不同服务,如何保证操作协同?

解决方案

难点2:高并发下的库存准确性

问题:多用户同时购买时如何防止库存数据混乱?

解决方案

// 数据库事务+行级锁保证并发安全
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 事务内查询自动加行锁,阻止并发修改
var goodsInfo entity.GoodsInfo
dao.GoodsInfo.Ctx(ctx).TX(tx).Where("id=?", goodsId).Scan(&goodsInfo)
// 检查并更新库存
if goodsInfo.Stock >= count {
dao.GoodsInfo.Ctx(ctx).TX(tx).Data(g.Map{"stock": goodsInfo.Stock - count}).Update()
}
return nil
})

难点3:系统性能与用户体验平衡

问题:库存操作频繁,如何避免影响响应速度?

解决方案

  • 核心流程同步处理,确保用户体验
  • 非核心操作(如缓存清理)异步化,不阻塞主流程
  • 批量操作使用并发处理提升效率

结语

很多时候一些严重的错误往往出现在一些小细节上面。通过这次库存相关的优化案例可以发现:看似简单的业务流程,在分布式架构下需要考虑服务协作、并发控制、异常处理等等多个方面的因素

通过引入RabbitMQ,不仅解决了已经存在的库存同步问题,更让整个系统具备了更好的扩展性,比如未来要新增物流通知、积分等功能的时候,只需新增事件的消费者就ok了,不需要再去修改现有的核心代码。

本文基于真实的GoFrame微服务电商项目,所有代码都经过生产环境验证,这里是项目的介绍:(https://mp.weixin.qq.com/s/ACzEHtvGh2YsU_4fxo83fQ)。如果你也遇到类似问题,欢迎交流讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1206421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

postman应用实战

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Postman应用实战 下面以微信公众平台举例&#xff1a; 第一步、先创建文件夹 第二步、打开postman&#xff0c;创建collections 第三步、设置环境变量&#…

软件测试需求分析

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1.1需求的重要性 1.1.1 软件缺陷的8020原则 1&#xff09;在软件测试过程中&#xff0c;从需求分析开始到集成测试阶段引入测试手段&#xff0c;能发现所有缺…

在字节和滴滴划水四年,过于真实了...

先简单交代一下吧&#xff0c;胡哥是某不知名211的本硕&#xff0c;21年毕业加入滴滴&#xff0c;之后跳槽到了头条&#xff0c;一直从事测试开发相关的工作。之前没有实习经历&#xff0c;算是四年的工作经验吧。 这四年之间他完成了一次晋升&#xff0c;换了一家公司&#x…

腾讯云TSearch存算分离,破解日志分析算力瓶颈

腾讯云TSearch存算分离&#xff0c;破解日志分析算力瓶颈随着企业数字化深入&#xff0c;日志分析、运维监控等场景的数据量呈爆炸式增长&#xff0c;传统存算一体架构逐渐暴露致命短板&#xff1a;写入压力集中导致带宽瓶颈&#xff0c;冷热数据混布拖累查询性能&#xff0c;多…

Pytest之收集用例规则与运行指定用例详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 小伙伴们大家好呀&#xff0c;今天笔者会给大家讲解一下pytest是如何收集我们写好的用例&#xff1f;我们又有哪些方式来运行单个用例或者批量运行用例呢&…

火山引擎DPU潮汐复用,重构算力成本优化逻辑

火山引擎DPU潮汐复用&#xff0c;重构算力成本优化逻辑AI大模型训练、短视频渲染等场景的算力需求存在显著潮汐特性&#xff0c;高峰时段资源紧张&#xff0c;低谷时段大量算力闲置&#xff0c;企业面临“算力不足”与“成本浪费”的双重困境。火山引擎基于DPU架构创新推出潮汐…

基于Java+SSM的种子商店网站的设计与实现(源码+lw+部署文档+讲解等)

课题介绍 本课题旨在设计并开发基于 JavaSSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架的种子商店网站&#xff0c;针对传统种子经营门店线下渠道单一、品类展示受限、订单管理低效、农户购种选种不便等痛点&#xff0c;打造集种子展示、在线选购、订单管理、农资资讯…

​中国工业软件出海新标杆:浩辰CAD看图王荣获国际大奖,亮相纽约时代广场

近日&#xff0c;中国工业软件出海领域迎来里程碑事件。在七麦数据发起的“NextWorld 2025年度风采奖”评选中&#xff0c;浩辰软件旗下产品——CAD看图王海外版&#xff08;DWG FastView&#xff09;凭借其全球化市场表现&#xff0c;成功斩获“年度出海实力应用”奖项&#x…

CAD学习资源大全:从入门到精通,这一份就够了

CAD学习资源可按官方渠道、在线课程、图文教材、社区论坛、实战工具五类系统获取&#xff0c;覆盖从零基础入门到高阶精通的全阶段需求&#xff0c;以下是2026年最新精选资源与使用建议。 一、官方权威资源&#xff08;基础入门首选&#xff09; | 资源类型 | 推荐内容 | 核心…

基于Android的安卓云笔记系统(源码+lw+部署文档+讲解等)

课题介绍 本课题旨在设计并实现基于 Android 的安卓云笔记系统&#xff0c;针对传统本地笔记数据易丢失、多端同步不便、编辑功能单一、内容管理杂乱等痛点&#xff0c;打造适配移动场景的轻量化云笔记应用&#xff0c;实现笔记内容云端存储、多端同步、编辑便捷化、管理智能化…

【开题答辩全过程】以 基于微信小程序的考公论坛的设计与实现为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

《从选型到应用:霍尔传感器(单极 / 全极 / 锁存)核心参数对比与实战技巧》

霍尔传感器&#xff08;Hall Effect Sensor&#xff09;是一种利用霍尔效应将磁信号转换为电信号的传感器。它在现代电子设备中应用极广&#xff0c;从手机翻盖检测到电动汽车的电机控制都有它的身影。 以下是关于霍尔传感器的主要功能及选型参数的详细整理&#xff1a; 一、 …

温度传感器选型完全指南:功能、参数与应用场景详解

一、 温度传感器的主要功能温度传感器的核心任务是 **“感知”和“转换”**&#xff0c;具体功能通常分为以下几类&#xff1a;测量与监控 (Measurement & Monitoring)功能描述&#xff1a; 实时采集环境或物体的温度数据。应用场景&#xff1a; 手机电池温度检测、机房环境…

《MOS 管 PD 参数深度解析:热阻、封装与散热设计的底层逻辑》

在 MOS 管&#xff08;MOSFET&#xff09;领域&#xff0c;PD 指最大耗散功率&#xff08;Maximum Power Dissipation&#xff09;&#xff0c;是 MOS 管的核心参数之一&#xff0c;代表其安全工作时允许的最大功耗上限&#xff0c;超出该数值会因过热损坏器件&#xff0c;以下…

Flyback 变换器中 MOS 管耐压值怎么选?深度解析输入电压与击穿风险的博弈

Flayback的DCDC中的输入电压和内部集成的MOS耐压之间有什么关系嘛你想问的大概率是 Flyback&#xff08;反激式&#xff09;DC-DC 变换器&#xff0c;其输入电压与内部集成 MOS 管的耐压并非简单的正比关系&#xff0c;而是 MOS 管耐压需适配输入电压&#xff0c;并结合变压器匝…

什么是离线开关?

离线开关是什么意思&#xff1f;在电源电子领域&#xff0c;“离线开关”&#xff08;Off-line Switcher&#xff09;是一个非常常见的术语&#xff0c;通常指的是AC/DC 变换器&#xff0c;或者更具体地说&#xff0c;是指能够直接连接到市电&#xff08;电网&#xff09;上进行…

什么是桥驱芯片?

桥驱芯片&#xff08;通常指半桥或全桥栅极驱动器&#xff0c;用于驱动 MOSFET 或 IGBT&#xff09;时&#xff0c;选型参数的优先级通常是由应用场景&#xff08;电压 / 功率等级&#xff09; -> 性能要求&#xff08;频率 / 效率&#xff09; -> 可靠性与保护&#xff…

java后端工程师+AI大模型进修ing(研一版‖day56) - 教程

java后端工程师+AI大模型进修ing(研一版‖day56) - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consola…

python+pytest接口自动化测试:接口测试基础详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 接口定义 一般我们所说的接口即API&#xff0c;那什么又是API呢&#xff0c;百度给的定义如下&#xff1a; API&#xff08;Application Programming Interf…

如何在 Linux 中使用 dd 命令 ? - 实践

如何在 Linux 中使用 dd 命令 ? - 实践2026-01-23 20:14 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block …