在这篇文章里京东通过关键的三步巧妙的规避掉分布式事务同步预占下单时订单服务同步调用库存服务的接口先预占库存非扣减就是说消费者拍下商品订单后库存先为该订单短暂预留。如果预扣失败立即告知用户下单失败异步确认支付成功后通过MQ异步触发预占→实扣的流程兜底保障超时自动释放预占 每日对账修复异常真的是务实又能保证性能的做法佩服。另外你可以去google一下会发现在国际顶级的架构圈里对分布式事务的使用都是持谨慎的态度的。Pat Helland 2007 年发表论文指出分布式事务有性能开销大、实现复杂、有故障风险等挑战建议探索其替代方案处理数据一致性。微服务权威 Chris Richardson 认为 2PC 作为强一致性协议有同步阻塞等缺陷在高并发微服务场景更倾向用事件驱动、最终一致性模式。亚马逊 CTO Werner Vogels 基于实践经验表示高并发分布式系统架构中最终一致性通常是更合适策略。我自己是没有干过金融银行的业务可能他们需要分布式事务但按我自己的理解当瞬时并发流量非常大的时候采用分布式事务很容易出大故障的。就以常见的下单扣减库存为例子如果为了保证强一致性订单、库存2个系统在下单那一刻全部被锁住那只要网络稍微抖一下或者其中一个系统响应慢一些整个系统的吞吐量就会断崖式下跌甚至雪崩。在我们以前的C端订单服务里下单接口要支撑的TPS都是好几千的如果按照刚才强一致性玩法那不知道得出多少故障。下面用伪代码的方式简单说一下下单扣减库存的关键流程。下单预占库存// 订单服务 public Order createOrder(OrderRequest req) { // 1. 同步预占库存失败立即返回用户 boolean stockLocked inventoryService.preOccupy( req.getSkuId(), req.getQuantity(), req.getOrderId(), 30 // 30分钟超时 ); if (!stockLocked) { // 用户实时感知 throw new RuntimeException(库存不足请重试); } // 2. 本地事务创建订单仅操作本库无跨服务事务 Order order new Order(); order.setId(req.getOrderId()); // 状态待支付 order.setStatus(unpay); // 仅操作订单库 orderDao.insert(order); // 返回下单成功请30分钟内支付 return order; }库存中心预占接口注意库存中心的预占接口流量非常大既要保证性能和原子性也要保证幂等。public boolean preOccupy(String skuId, int qty, String orderId, int minutes) { String lockKey PREOCCUPY: skuId : orderId; // 检查是否已预占防重复请求 if (redis.exists(lockKey)) { // 已存在直接返回成功 return true; } // LUA脚本原子操作检查库存预占避免超卖 boolean success redis.eval(luaScript, skuId, qty, orderId); if (success) redis.expire(lockKey, minutes * 60); return success; }订单支付后真实的扣减库存异步// MQ消费者处理支付成功事件 public void onPaymentSuccess(String orderId) { // 预占→实扣操作DB更新Redis inventoryService.confirmDeduct(orderId); // 更新订单状态 orderDao.updateStatus(orderId, PAID); }定时任务超时释放库存占用public void releaseExpiredOrders() { // 超时30分钟 ListOrder expiredOrders orderDao.findOrdersLockExpired(); for (Order order : expiredOrders) { // 释放库存预占 inventoryService.release(order.getId()); orderDao.updateStatus(order.getId(), EXPIRED); } }就算你用了MQ的延迟消息定时任务也是一定要有的兜底用的。那一定要用库存预占的做法吗?不一定的如果你的并发写的流量一般其实也可以采用如下的方式下单接口直接调用库存中心扣减库存的接口当扣减库存成功后万一下单失败了则调用库存中心一个库存回滚的接口伪代码如下// 订单服务 public Order createOrder(OrderRequest req) { // 1. 直接扣减库存 boolean stockLocked inventoryService.reduce( req.getSkuId(), req.getQuantity(), req.getOrderId(), 30 // 30分钟超时 ); if (!stockLocked) { // 用户实时感知 throw new RuntimeException(库存不足请重试); } // 2. 本地事务创建订单仅操作本库无跨服务事务 try{ Order order new Order(); order.setId(req.getOrderId()); // 状态待支付 order.setStatus(unpay); // 仅操作订单库 orderDao.insert(order); } catch (Exception e) { //下单失败回滚库存 inventoryService.rollback( req.getSkuId(), req.getQuantity(), req.getOrderId()); throw e; } // 返回下单成功请30分钟内支付 return order; }这里可能有人会问万一调用库存中心回滚库存的接口失败了怎么办?比如网络问题或者对方接口突然出问题了。 我当时是这么做的发MQ。就是说为了尽快让库存回滚回去我采用的是直接同步调用库存回滚接口万一失败了才异步发MQ。小结我没有去研究阿里和美团是怎么做的但是我估计思路应该差不多的如果有读者知道的欢迎到评论区留言共同进步。然后我这里抛一个问题我刚才提供的第二种办法中当orderDao.insert(order);刚执行完就宕机了catch那里就无法执行了也即是库存无法回滚了那应该如何处理?