Arch Day 71: 购物车与结算链路
Arch Day 71: 购物车与结算链路
日期: 2026-06-09 (Day 71) 阶段: 第三阶段 - 零售域深度 标签: #电商 #购物车 #结算 #高并发 #库存一致性
核心概念
一句话定义
购物车是用户"购买意图"的容器,结算链路是将意图转化为订单的关键路径——这条路径的每一步都涉及价格一致性、库存可靠性和支付安全性的多方博弈。
为什么关注
购物车到结算是电商转化的"最后一公里"。行业数据显示,全球电商购物车放弃率高达70%(2025年Baymard Institute)。这意味着10个加入购物车的用户中只有3个最终完成支付。结算链路的每一毫秒延迟、每一个异常报错、每一次库存不足提示,都可能导致用户流失。从架构角度,结算链路是电商系统中并发最高、一致性要求最严、出错代价最大的链路之一。
误区与反模式
| 误区 | 现实 |
|---|---|
| "购物车就是个列表" | 购物车承载促销计算、库存校验、价格实时更新等复杂逻辑 |
| "下单就是插一条记录" | 下单涉及库存预扣、优惠锁定、风控校验、幂等保障等编排 |
| "库存扣减在创建订单时做" | 应该在支付前预扣、支付后真扣,否则超卖或占库存 |
| "价格以结算页显示为准" | 需要三级校验:购物车价→下单价→支付价 |
| "同步处理所有逻辑" | 非核心路径(积分、通知、日志)应异步化 |
知识点详解
一、购物车设计
1.1 购物车类型
| 类型 | 存储位置 | 生命周期 | 特点 |
|---|---|---|---|
| 游客车(Guest Cart) | Cookie/LocalStorage | 浏览器生命周期 | 无需登录,容量有限 |
| 会员车(User Cart) | 服务端(Redis+DB) | 长期持久化 | 多端同步,可恢复 |
| 临时车(Session Cart) | 服务端Session | 会话期间 | 登录前缓存 |
| 小程序车 | 微信Storage | 小程序生命周期 | 受平台限制 |
1.2 游客车→会员车合并策略
class CartMergeStrategy:
"""购物车合并策略"""
def merge(self, guest_cart, user_cart):
"""
登录时合并游客车和会员车
原则:用户体验优先,不丢失任何商品
"""
merged = copy.deepcopy(user_cart)
for guest_item in guest_cart.items:
existing = merged.find_item(guest_item.sku_id)
if existing:
# 策略1: 取较大数量
existing.quantity = max(existing.quantity, guest_item.quantity)
# 策略2: 数量相加(需限制上限)
# existing.quantity = min(existing.quantity + guest_item.quantity, MAX_QTY)
else:
# 游客车中的新商品加入会员车
merged.add_item(guest_item)
# 清空游客车
guest_cart.clear()
# 重新计算促销
merged.recalculate_promotions()
return merged
1.3 购物车数据模型
┌─────────────────────────────────────────┐
│ Cart (购物车) │
│ ├── cartId: String │
│ ├── userId: String (nullable for guest) │
│ ├── cartType: GUEST | USER │
│ ├── items: List<CartItem> │
│ │ ├── skuId: String │
│ │ ├── skuName: String │
│ │ ├── quantity: Integer │
│ │ ├── originalPrice: Decimal │
│ │ ├── currentPrice: Decimal │
│ │ ├── selected: Boolean │
│ │ ├── stockStatus: IN_STOCK|LOW|OUT │
│ │ ├── shopId: String │
│ │ └── promotionTags: List<String> │
│ ├── promotions: List<CartPromotion> │
│ │ ├── promotionId, discount, type │
│ │ └── appliedItems: List<skuId> │
│ ├── totalOriginalPrice: Decimal │
│ ├── totalDiscount: Decimal │
│ ├── totalPayable: Decimal │
│ ├── updatedAt: Timestamp │
│ └── expireAt: Timestamp │
└─────────────────────────────────────────┘
1.4 购物车存储架构
写入路径:
用户操作(加购/改量/删除)
│
▼
┌──────────┐ 异步持久化 ┌──────────┐
│ Redis │ ─────────────→ │ MySQL │
│ (主存储) │ │ (备份) │
└──────────┘ └──────────┘
读取路径:
打开购物车
│
▼
┌──────────┐ Cache Miss ┌──────────┐
│ Redis │ ←───────────── │ MySQL │
│ (热数据) │ │ (冷数据) │
└──────────┘ └──────────┘
│
▼
实时校验(库存+价格+促销)
│
▼
返回完整购物车数据
1.5 购物车读写优化
场景:双11前,用户疯狂加购(写场景优化)
├── Redis Hash存储:HSET cart:{userId} {skuId} {json}
├── 加购只更新单个field,不需要读写整个购物车
├── 异步落DB:MQ → Consumer → MySQL
└── 限流:单用户每秒最多5次加购操作
场景:双11开始,用户打开购物车结算(读场景优化)
├── 批量查询:Pipeline批量获取SKU信息
├── 并行计算:库存/价格/促销并行查询
├── 预计算:大促前预计算促销价缓存
└── 降级:促销计算超时则返回原价+异步刷新
二、结算页设计
2.1 结算页要素
┌──────────────────────────────────────────┐
│ 结算页(Checkout) │
│ │
│ ① 商品确认 │
│ ┌────────────────────────────────┐ │
│ │ [✓] 商品A × 2 ¥99.00/件 │ │
│ │ [✓] 商品B × 1 ¥199.00/件 │ │
│ └────────────────────────────────┘ │
│ │
│ ② 收货地址 │
│ ┌────────────────────────────────┐ │
│ │ 张三 138****1234 │ │
│ │ 北京市朝阳区xxx路xxx号 │ [>] │
│ └────────────────────────────────┘ │
│ │
│ ③ 配送方式 │
│ ┌────────────────────────────────┐ │
│ │ ○ 普通快递 (预计3-5天) 免运费 │ │
│ │ ● 次日达 +¥8.00 │ │
│ └────────────────────────────────┘ │
│ │
│ ④ 优惠信息 │
│ ┌────────────────────────────────┐ │
│ │ 满减优惠:满300减50 -¥50.00 │ │
│ │ 优惠券:新人8折券 -¥39.60 │ │
│ └────────────────────────────────┘ │
│ │
│ ⑤ 支付方式 │
│ ┌────────────────────────────────┐ │
│ │ ● 支付宝 ○ 微信 ○ 银行卡 │ │
│ └────────────────────────────────┘ │
│ │
│ ⑥ 金额汇总 │
│ ┌────────────────────────────────┐ │
│ │ 商品总价 ¥397.00 │ │
│ │ 运费 +¥8.00 │ │
│ │ 优惠 -¥89.60 │ │
│ │ ──────────────────────── │ │
│ │ 应付金额 ¥315.40 │ │
│ └────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ ¥315.40 [ 提交订单 ] │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────────┘
2.2 结算页数据聚合
结算页一次展示需要聚合多个域的数据:
| 数据来源 | 查询内容 | 延迟要求 |
|---|---|---|
| 商品服务 | SKU信息、图片、名称 | <50ms |
| 库存服务 | 实时可用库存 | <30ms |
| 价格服务 | 当前售价 + 促销价 | <50ms |
| 地址服务 | 用户地址列表 | <30ms |
| 物流服务 | 可用配送方式 + 运费 | <100ms |
| 促销服务 | 可用优惠 + 计算结果 | <100ms |
| 风控服务 | 用户风险评估 | <50ms |
优化策略:并行调用 + 超时降级
public CheckoutPageDTO buildCheckoutPage(CheckoutRequest request) {
// 并行调用各服务(CompletableFuture)
CompletableFuture<SkuInfoList> skuFuture =
CompletableFuture.supplyAsync(() -> skuService.batchQuery(request.getSkuIds()));
CompletableFuture<StockResult> stockFuture =
CompletableFuture.supplyAsync(() -> stockService.batchCheck(request.getSkuIds()));
CompletableFuture<PriceResult> priceFuture =
CompletableFuture.supplyAsync(() -> priceService.calculate(request));
CompletableFuture<AddressList> addressFuture =
CompletableFuture.supplyAsync(() -> addressService.listByUser(request.getUserId()));
CompletableFuture<DeliveryOptions> deliveryFuture =
CompletableFuture.supplyAsync(() -> deliveryService.getOptions(request));
CompletableFuture<PromotionResult> promoFuture =
CompletableFuture.supplyAsync(() -> promotionService.calculate(request));
// 等待所有结果(总超时500ms)
CompletableFuture.allOf(skuFuture, stockFuture, priceFuture,
addressFuture, deliveryFuture, promoFuture)
.get(500, TimeUnit.MILLISECONDS);
// 聚合结果
return assembleCheckoutPage(...);
}
三、下单链路设计
3.1 下单全流程
用户点击"提交订单"
│
▼
① 前端防重(按钮禁用+requestId)
│
▼
② 网关层限流(令牌桶)
│
▼
③ 幂等校验(requestId去重)
│
▼
④ 参数校验(地址/商品/数量)
│
▼
⑤ 风控校验(黑名单/异常行为)
│
▼
⑥ 库存预扣(Redis原子减)
│
▼
⑦ 优惠锁定(锁定优惠券+计算)
│
▼
⑧ 价格校验(三级价格对比)
│
▼
⑨ 创建订单(写DB)
│
▼
⑩ 发送下单成功消息(MQ)
│
├── 异步:通知仓库备货
├── 异步:发送短信/推送
├── 异步:积分/成长值
└── 异步:数据统计
│
▼
返回订单号 + 拉起支付
3.2 库存预扣机制
时间线:
T0: 用户点击下单
T1: Redis库存预扣(原子操作) ← 关键步骤
T2: 创建订单(状态=待支付)
T3: 用户支付(15分钟超时)
T4a: 支付成功 → DB库存真扣 → 订单状态→已支付
T4b: 支付超时 → Redis库存回补 → 订单状态→已取消
class InventoryService:
"""库存预扣服务"""
# Redis Lua脚本:原子预扣
DEDUCT_SCRIPT = """
local stock = tonumber(redis.call('GET', KEYS[1]))
local quantity = tonumber(ARGV[1])
if stock >= quantity then
redis.call('DECRBY', KEYS[1], quantity)
return 1 -- 扣减成功
else
return 0 -- 库存不足
end
"""
def pre_deduct(self, sku_id, quantity, order_id):
"""
库存预扣(Redis原子操作)
"""
key = f"stock:available:{sku_id}"
result = self.redis.eval(self.DEDUCT_SCRIPT, 1, key, quantity)
if result == 1:
# 记录预扣流水(用于超时回补)
self.redis.setex(
f"stock:hold:{order_id}:{sku_id}",
900, # 15分钟TTL
quantity
)
return True
return False
def confirm_deduct(self, order_id, sku_id, quantity):
"""
支付成功后,确认扣减(DB持久化)
"""
# DB真扣
self.db.execute(
"UPDATE inventory SET stock = stock - %s "
"WHERE sku_id = %s AND stock >= %s",
(quantity, sku_id, quantity)
)
# 删除预扣记录
self.redis.delete(f"stock:hold:{order_id}:{sku_id}")
def release_hold(self, order_id, sku_id, quantity):
"""
超时/取消时,释放预扣库存
"""
key = f"stock:available:{sku_id}"
self.redis.incrby(key, quantity)
self.redis.delete(f"stock:hold:{order_id}:{sku_id}")
3.3 价格三级校验
class PriceConsistencyChecker:
"""
价格一致性三级校验
防止:前端篡改/缓存过期/促销变更导致的价格不一致
"""
def check(self, checkout_request, order):
# Level 1: 购物车价格 vs 下单价格
cart_total = self.get_cart_total(checkout_request.cart_id)
order_total = order.total_payable
if abs(cart_total - order_total) > Decimal('0.01'):
raise PriceInconsistencyError("购物车价格与下单价格不一致")
# Level 2: 下单价格 vs 实时计算价格
realtime_total = self.price_service.calculate_realtime(order.items)
if abs(order_total - realtime_total) > Decimal('0.01'):
# 价格变动容忍策略
if realtime_total < order_total:
# 实时更便宜→用实时价(用户受益)
order.update_price(realtime_total)
else:
# 实时更贵→提示用户价格变动
raise PriceChangedError(
f"价格已变动: {order_total} → {realtime_total}"
)
# Level 3: 下单价格 vs 支付价格(支付回调时校验)
# 在支付回调handler中执行
pass
四、高并发下单优化
4.1 性能瓶颈分析
| 瓶颈点 | 原因 | 影响 |
|---|---|---|
| 库存热点 | 爆款商品并发写 | Redis热点key |
| 数据库写入 | 订单+订单项+流水 | MySQL写TPS上限 |
| 分布式锁 | 优惠券核销/库存扣减 | 锁竞争 |
| 外部调用 | 支付/物流/风控 | 网络延迟 |
| 促销计算 | 复杂叠加规则 | CPU密集 |
4.2 优化方案矩阵
| 层级 | 优化手段 | 效果 |
|---|---|---|
| 前端 | 按钮防抖、请求去重、排队等待动画 | 减少无效请求 |
| 网关 | 令牌桶限流、用户级QPS控制 | 保护后端 |
| 应用 | 异步化非核心逻辑、预计算 | 降低RT |
| 缓存 | Redis预扣、本地缓存热点数据 | 减少DB压力 |
| 队列 | 订单创建异步化(MQ削峰) | 平滑写入 |
| 数据库 | 分库分表、批量写入、延迟落库 | 提升写TPS |
4.3 异步下单架构
同步路径(用户感知):
请求 → 限流 → 幂等 → 风控 → 库存预扣(Redis) → 返回"下单成功,排队中"
耗时: <200ms
异步路径(后台处理):
MQ → 创建订单(DB) → 锁定优惠券 → 发通知 → 更新状态为"待支付"
耗时: 1-5s
用户体验:
"提交订单" → 立即反馈"排队中" → 轮询/推送 → "下单成功,请支付"
// 同步路径:快速响应
@PostMapping("/submit-order")
public OrderSubmitResult submitOrder(OrderRequest request) {
// 1. 幂等校验
if (idempotentService.isDuplicate(request.getRequestId())) {
return OrderSubmitResult.duplicate();
}
// 2. 库存预扣(Redis)
boolean stockOk = inventoryService.preDeduct(request.getItems());
if (!stockOk) {
return OrderSubmitResult.stockInsufficient();
}
// 3. 生成订单号
String orderNo = orderNoGenerator.generate();
// 4. 发送到MQ(异步创建订单)
orderMQ.send(new CreateOrderMessage(orderNo, request));
// 5. 立即返回(用户不需要等DB写入完成)
return OrderSubmitResult.queued(orderNo);
}
五、幂等设计
public class IdempotentService {
/**
* 下单幂等:同一个requestId只创建一次订单
* 实现:Redis SETNX + 订单表唯一索引双重保障
*/
public boolean tryAcquire(String requestId) {
// Redis: SETNX,设置30分钟过期
Boolean acquired = redis.opsForValue()
.setIfAbsent("idempotent:" + requestId, "1", 30, TimeUnit.MINUTES);
if (Boolean.TRUE.equals(acquired)) {
return true; // 首次请求
}
// 已存在→检查订单是否已创建
Order existing = orderRepository.findByRequestId(requestId);
if (existing != null) {
// 已有订单→直接返回(不重复创建)
return false;
}
// Redis有记录但订单未创建(上次失败了)→允许重试
return true;
}
}
对比分析
下单架构方案对比
| 维度 | 同步下单 | 异步下单 | 预扣+异步 | 排队下单 |
|---|---|---|---|---|
| 用户体验 | 即时反馈 | 延迟反馈 | 快速预反馈 | 排队等待 |
| 吞吐量 | 低(受DB限制) | 高 | 高 | 极高 |
| 一致性 | 强一致 | 最终一致 | 最终一致 | 最终一致 |
| 复杂度 | 低 | 中 | 中高 | 高 |
| 回滚难度 | 简单 | 需补偿 | 需补偿 | 需补偿 |
| 适用场景 | 低并发/B2B | 常规电商 | 大促 | 秒杀 |
| 典型RT | 200-500ms | 50ms+异步 | 100ms+异步 | 50ms+排队 |
购物车存储方案对比
| 方案 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| 纯Cookie | 无服务端存储 | 容量小、不安全 | 简单H5 |
| LocalStorage | 容量大 | 不跨端 | 单端应用 |
| Redis | 高性能、跨端 | 需持久化备份 | 主流方案 |
| MySQL | 持久可靠 | 性能低 | 低频场景 |
| Redis+MySQL | 兼顾性能和可靠 | 一致性管理 | 大型电商 |
架构设计实操
设计目标
设计高并发下单链路,满足:
- 大促峰值10万QPS
- 下单响应时间<200ms
- 库存准确(不超卖不少卖)
- 价格一致性保障
方案设计
┌──────────────────────────────────────────────────────────────┐
│ 客户端 │
│ 按钮防抖 + requestId + 排队等待UI │
└───────────────────────────┬──────────────────────────────────┘
│
┌───────────────────────────▼──────────────────────────────────┐
│ API Gateway │
│ 限流(10万QPS) → 用户级限流(5QPS/人) → 黑名单 → 路由 │
└───────────────────────────┬──────────────────────────────────┘
│
┌───────────────────────────▼──────────────────────────────────┐
│ 下单编排服务(BFF) │
│ ① 幂等校验(Redis SETNX) │
│ ② 风控校验(异步,超时放行) │
│ ③ 库存预扣(Redis Lua原子操作) │
│ ④ 价格校验(本地缓存+实时计算) │
│ ⑤ 生成订单号(Snowflake) │
│ ⑥ 发MQ(异步创建订单) │
│ ⑦ 返回: {orderNo, status:"queued"} │
│ 耗时: <150ms │
└───────────────────────────┬──────────────────────────────────┘
│ MQ
┌───────────────────────────▼──────────────────────────────────┐
│ 订单创建服务(异步) │
│ ① 消费MQ消息 │
│ ② 锁定优惠券 │
│ ③ 创建订单+订单项(DB) │
│ ④ 发送下单成功事件 │
│ ⑤ 通知用户(推送/轮询) │
│ 耗时: 1-3s │
└──────────────────────────────────────────────────────────────┘
ADR: 库存扣减采用Redis预扣+DB异步确认
背景: 高并发场景下数据库直接扣库存会成为瓶颈
决策: 采用"Redis预扣→MQ异步→DB确认"三段式库存扣减
理由:
- Redis单线程原子操作避免超卖
- 单实例Redis可支持10万+QPS
- MQ削峰平滑DB写入压力
- 预扣超时自动回补保证不少卖
权衡:
- 优势:高吞吐、不超卖
- 劣势:Redis和DB不一致窗口(秒级)、Redis宕机需要恢复
- 兜底:定时对账任务,每小时比较Redis和DB库存
AI增强实践
1. AI智能购物车推荐
class AICartRecommender:
"""AI驱动的购物车推荐"""
def recommend_additions(self, cart):
"""购物车中追加推荐"""
# 基于购物车商品进行关联推荐
cart_embeddings = [
self.item_encoder.encode(item.sku_id)
for item in cart.items
]
# 向量搜索相似/互补商品
candidates = self.vector_db.search(
query=mean(cart_embeddings),
filter={"not_in": [i.sku_id for i in cart.items]},
top_k=20
)
# 加入促销信号重排
for c in candidates:
c.score *= self.get_promo_boost(c, cart) # 有凑单优惠的加权
c.score *= self.get_margin_boost(c) # 高毛利商品加权
return sorted(candidates, key=lambda x: x.score, reverse=True)[:5]
def smart_coupon_suggest(self, cart):
"""智能凑单提示"""
available_promos = self.promo_service.get_available(cart)
for promo in available_promos:
gap = promo.threshold - cart.total_amount
if 0 < gap <= 50: # 差50元以内可以凑单
suggest_items = self.find_items_near_price(gap)
return f"再买¥{gap}即可享受{promo.description},推荐加购:{suggest_items}"
2. AI预测性缓存预热
| AI能力 | 说明 | 效果 |
|---|---|---|
| 热点预测 | 预测大促哪些SKU将成为热点 | 提前预热缓存 |
| 库存预警 | 预测库存消耗速度 | 提前补货/限购 |
| 流量预测 | 预测各时间段QPS | 动态扩容 |
| 转化预测 | 预测购物车转化概率 | 智能催付策略 |
与Web3/DeFi的关联
结算链路 × Web3
| Web2结算 | Web3结算 |
|---|---|
| 支付宝/微信/银行卡 | 稳定币(USDC/USDT)支付 |
| 15分钟支付超时 | 区块确认时间(秒级~分钟级) |
| 平台担保 | 智能合约Escrow |
| 中心化对账 | 链上自动清结算 |
| 积分奖励 | Token奖励(可交易) |
DeFi DEX交易 vs 电商下单
DEX Swap流程(本质也是"下单链路"):
① 用户发起Swap请求
② 前端获取报价(类似价格计算)
③ 用户确认(Slippage tolerance = 价格容忍度)
④ 提交交易(类似"提交订单")
⑤ 链上执行(类似"库存扣减")
⑥ 确认(类似"支付成功")
共通的设计挑战:
- 价格一致性(报价→成交的价格滑移)
- 库存/流动性争抢(多人竞争同一资源)
- 失败回滚(交易revert = 订单取消)
- 前端防重(nonce机制 = requestId幂等)
今日思考
1. 购物车的"实时性"和"性能"如何平衡?
每次打开购物车都要校验库存和价格,数据实时但慢;缓存后快但可能过期。最佳实践是"分层实时":商品基础信息缓存(5分钟更新)、价格信息近实时(1分钟更新)、库存信息实时查询但有降级(超时返回上次结果)。关键商品(即将售罄)实时校验,普通商品缓存校验。
2. 分布式事务在下单链路中如何处理?
下单涉及库存、优惠、订单三个服务的数据一致。强一致方案(2PC/XA)性能太差。实践中采用"Saga+补偿"模式:库存预扣→订单创建→优惠锁定,任一步失败触发补偿(回滚已执行的步骤)。最终一致性通过定时对账保障。
3. "先扣库存再创建订单" vs "先创建订单再扣库存"?
先扣库存:不会超卖,但可能占库存(下单后不支付)。先创建订单:体验好,但可能超卖。行业主流方案是"先扣库存"——因为超卖的损失(赔偿/信誉)远大于占库存的损失(超时自动释放)。阿里的方案是Redis预扣(快)+ DB乐观锁确认(准确)。
面试题准备
题目:下单链路如何保证库存和价格一致性?高并发下如何优化?
30秒回答: 库存用"Redis预扣+DB异步确认"保证不超卖;价格用"三级校验"(购物车→下单→支付)保证一致性。高并发优化核心是"同步链路极简化"——只做幂等+风控+库存预扣,其余全部异步。
2分钟回答:
库存一致性方案:
- Redis Lua脚本原子预扣(10万QPS级)
- 预扣设15分钟TTL,超时自动回补
- 支付成功后通过MQ触发DB真扣(乐观锁防并发)
- 定时对账任务兜底(Redis vs DB差异修复)
价格一致性方案:
- Level1: 结算页展示前实时计算价格
- Level2: 提交订单时二次计算,比对差异
- Level3: 支付回调时校验支付金额与订单金额
- 订单快照:下单时刻的价格、优惠信息完整记录
高并发优化:
- 前端:防抖+去重+排队UI
- 网关:全局限流(10万QPS) + 用户限流(5QPS/人)
- 同步链路:只做预扣+生成订单号+发MQ → <150ms
- 异步链路:MQ消费→创建订单→通知用户
- 热点隔离:爆款SKU独立Redis实例
追问准备:
- Q: Redis挂了怎么办?→ Redis Cluster + 降级到DB直接扣(性能降低但保证可用)
- Q: MQ消息丢了怎么办?→ 定时扫描"预扣但未创建订单"的记录,补偿处理
- Q: 高并发下DB写不过来?→ MQ削峰 + 批量写入(100条/批) + 分库分表
学习资源
明日预告
Day 72: 搜索与推荐系统 —— "搜索"是用户带着明确意图找商品,"推荐"是系统猜测用户可能喜欢什么。两者架构看似不同,底层正在融合。AI时代的搜索推荐正在经历从关键词匹配到语义理解、从协同过滤到LLM生成式推荐的范式转变。