订单系统设计
问题
如何设计一个高可用的电商订单系统?
答案
整体架构
下单核心流程
订单状态机
订单号设计
订单号 = 时间 + 机器 + 序列号
// 格式: 20260327 + 机器编号(2位) + 序列号(6位)
// 示例: 2026032701000001
public String generateOrderNo() {
String date = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String machine = String.format("%02d", machineId);
long seq = redisTemplate.opsForValue().increment("order:seq:" + date);
return date + machine + String.format("%06d", seq);
}
超时取消(延迟队列)
RocketMQ 延迟消息实现订单超时取消
// 下单时发送延迟消息(30分钟后投递)
public void createOrder(Order order) {
orderMapper.insert(order);
// 发送延迟消息,30分钟后检查支付状态
Message msg = new Message("ORDER_TIMEOUT", order.getOrderNo().getBytes());
msg.setDelayTimeLevel(16); // RocketMQ 延迟级别16 = 30min
rocketMQTemplate.send(msg);
}
// 消费延迟消息
@RocketMQMessageListener(topic = "ORDER_TIMEOUT")
public void onTimeout(String orderNo) {
Order order = orderMapper.findByOrderNo(orderNo);
if (order != null && order.getStatus() == OrderStatus.UNPAID) {
order.setStatus(OrderStatus.CANCELLED);
orderMapper.updateStatus(order);
inventoryService.rollback(order); // 回补库存
}
}
常见面试问题
Q1: 如何防止重复下单?
答案:
- 前端:按钮点击后禁用,防止重复提交
- 后端:幂等 Token。下单前获取 Token 存 Redis,下单时验证并删除(Lua 原子操作)
- 数据库:唯一索引兜底
Q2: 如何保证扣库存和创建订单的一致性?
答案:
- 本地消息表:订单和消息在同一个事务中写入,异步通知库存服务
- TCC:Try 预扣 → Confirm 确认扣 → Cancel 回补
- 详见 分布式事务
Q3: 订单表数据量太大怎么办?
答案:
- 分库分表:按用户 ID 分片(同一用户的订单在同一库)
- 冷热分离:3 个月前的订单归档到历史库
- 详见 分库分表
Q4: 支付回调失败怎么办?
答案:
- 支付平台会重试回调(一般重试 5~8 次,间隔递增)
- 订单服务主动查询 :定时任务扫描超时未回调的订单,主动调支付平台查询接口
- 回调接口保证幂等(同一订单号多次回调不重复处理)