
本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
订单的自动拆单Order Splitting,并注意保证并行处理时的数据一致性处理。
初期设计时觉得拆单嘛,不就是把一个大订单改成几个小订单存数据库里吗?结果一上线,遇到大促高并发,库存扣减错乱、运费计算对不上、甚至出现“幽灵订单”,才发现:拆单,其实是分布式一致性和并发处理的修罗场。
本文教你如何在保证数据强一致性的前提下,实现高性能的并行拆单。
用户在你的App里买一堆,购物车有:
用户只点了一次“结算”,生成了一个父订单(Parent Order)。但后台须变成三个子订单(Child Orders):
核心痛点: 用户付了一笔钱,我们要把它拆成三笔,并保证库存、金额、优惠券分摊(Proration)一分钱都不差。
微服务架构下,拆单面临:
要抛弃传统的“串行拆单”思维,采用 “预计算 + 原子落库” 的策略。
建议采用 Pipeline(流水线)模式 结合 规则引擎。
提交订单 -> 拆单中心(规则引擎) -> 并行预计算(运费/优惠) -> 分布式锁 -> 预占库存 -> 原子化生成子单 -> 更新父单状态。
Rule Engine,不要在代码里写死 if (isColdChain) ...。使用策略模式或轻量级规则引擎。
这一步在内存中进行,生成一个 “虚拟拆单树” (Virtual Split Tree),此时还没操作数据库。
解决“慢”的问题,性能优化关键。拆出的3个虚拟子单,分别计算运费、分摊优惠券金额。这通常涉及RPC调用(物流服务、营销服务)。用CompletableFuture进行并行IO。
// 伪代码示例:并行处理虚拟子单的费用计算
List<VirtualSubOrder> subOrders = splitEngine.previewSplit(parentOrder);
List<CompletableFuture<Void>> futures = subOrders.stream()
.map(subOrder -> CompletableFuture.runAsync(() -> {
// 1. 并行调用运费服务
subOrder.setShippingFee(logisticsService.calcFee(subOrder));
// 2. 并行计算优惠分摊
subOrder.setDiscount(promotionService.calcShare(subOrder));
}, executorPool))
.collect(Collectors.toList());
// 等待所有子单计算完毕,如果有异常则整体抛出,不再进行下一步
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();这里只是在计算数据,完全没有修改数据库,所以不需加锁,非常快。
TCC 还是 本地事务?
错误做法:循环遍历子单,一个一个插入数据库,一个一个扣库存。
后果:第3个子单失败了,还得去回滚前2个,复杂且易脏数据。
最佳实践:基于单库的本地事务 + 分布式锁。
尽管是微服务,但在“下单”核心环节,拆单落库和父单状态更新建议在同一个 订单数据库 分片内完成,利用数据库的 ACID。
具体落地方案:
使用父订单号(ParentOrderID)作为Key加锁。
LOCK_KEY = "split_order_" + parentOrderId
防止用户疯狂点击或消息队列重试导致的“重复拆单”。
两阶段,拆单前,先锁定库存。
lockStock(List<Item>)。注意,这里要批量锁。如果有一个SKU锁失败,整个订单报错,拆单终止。将计算好的所有子单数据、父单状态变更,封装在一个数据库事务。
START TRANSACTION;
-- 1. 插入子订单 A
INSERT INTO orders (id, parent_id, items, status) VALUES (sub_a, parent_1, ...);
-- 2. 插入子订单 B
INSERT INTO orders (id, parent_id, items, status) VALUES (sub_b, parent_1, ...);
-- 3. 更新父订单状态为 "已拆分"
UPDATE orders SET status = 'SPLITTED' WHERE id = parent_1;
COMMIT;只有所有步骤都成功,拆单才算完成。
电商拆单最怕“除不尽”。如:优惠券减10元,拆成3个子单。10 / 3 = 3.3333...
如果每个子单减3.33,最后总共减9.99。财务会对不上账。
最大余数法(The Largest Remainder Method) 或 兜底减法。
规则:最后一个子单的金额 = 总金额 - (前 N-1 个子单金额之和)。
落地:在第二步“并行计算”时,虽然是并行,但在汇总数据时,须有一个Coordinator(协调者)步骤,重新校验金额之和是否等于父单。
微服务下订单拆单原则:
检查现有订单系统,若把拆单逻辑写在一个巨大 Service 方法里,且包含多次数据库提交,立即重构为“内存预计算 -> 事务一次性提交”的模式。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。