1. 摘要

订单系统是电商平台的核心心脏,它承载着交易数据的写入与状态流转,直接影响资金准确性和用户体验。本文将从零开始,深入剖析Java订单系统的架构设计,涵盖数据模型设计、状态机管理、高并发下单、分布式事务、分库分表、订单超时处理以及最终的可观测性建设。

与通用电商架构不同,本文聚焦订单域这一核心子系统,提供可直接落地的代码级方案。


2. 订单系统的核心挑战

挑战维度 具体问题 业务后果
数据一致性 扣库存失败但订单已创 用户下单后无法履约
高并发写入 秒杀时订单表成为瓶颈 数据库连接池爆炸,系统雪崩
状态流转 支付、发货、退款状态错乱 资损,客诉严重
海量数据 订单表年增数亿行 查询慢,维护困难
分布式事务 跨库存、支付、积分服务 数据不一致

3. 订单系统的业务边界

在微服务架构中,订单系统需要明确职责:

text

┌─────────────────────────────────────────────────┐
│                    订单系统边界                    │
├─────────────────────────────────────────────────┤
│ 核心职责:                                        │
│ 1. 订单创建(接收购物车数据)                      │
│ 2. 订单状态流转(待支付→待发货→已完成)            │
│ 3. 订单金额计算(商品金额 + 运费 - 优惠)          │
│ 4. 订单取消与删除(逻辑删除)                      │
├─────────────────────────────────────────────────┤
│ 不负责(依赖其他服务):                           │
│ • 库存扣减 → 库存服务/库存中心                     │
│ • 优惠券使用 → 促销服务                           │
│ • 支付网关对接 → 支付服务                         │
│ • 积分变动 → 用户服务                            │
└─────────────────────────────────────────────────┘

4. 订单数据模型设计(关键表结构)

4.1 订单主表 t_order

sql

CREATE TABLE `t_order` (
  `id` bigint(20) NOT NULL COMMENT '订单ID(全局唯一,不暴露业务含义)',
  `order_no` varchar(32) NOT NULL COMMENT '订单号(展示给用户)',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID(分片键)',
  `total_amount` decimal(18,2) NOT NULL COMMENT '订单总金额',
  `pay_amount` decimal(18,2) NOT NULL COMMENT '实付金额',
  `freight_amount` decimal(18,2) DEFAULT '0.00' COMMENT '运费',
  `discount_amount` decimal(18,2) DEFAULT '0.00' COMMENT '优惠金额',
  `order_status` tinyint(4) NOT NULL COMMENT '订单状态:0待支付,1待发货,2已发货,3已完成,4已取消,5售后中',
  `pay_status` tinyint(4) DEFAULT '0' COMMENT '支付状态:0未支付,1支付中,2已支付,3支付失败',
  `delivery_status` tinyint(4) DEFAULT '0' COMMENT '发货状态:0未发货,1部分发货,2已发货',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  `delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
  `receive_time` datetime DEFAULT NULL COMMENT '确认收货时间',
  `cancel_time` datetime DEFAULT NULL COMMENT '取消时间',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `version` int(11) DEFAULT '0' COMMENT '乐观锁版本号',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_user_id_create_time` (`user_id`,`create_time`),
  KEY `idx_order_status` (`order_status`,`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';

设计要点:

  • id 使用雪花算法生成,避免自增主键暴露订单量

  • order_no 使用时间戳+机器ID+序列号,可读性高

  • 用 user_id 作为分片键,确保同一用户订单落在同一库

  • 状态字段拆分为 order_status + pay_status + delivery_status,各维度独立

4.2 订单商品明细表 t_order_item

sql

CREATE TABLE `t_order_item` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `order_id` bigint(20) NOT NULL COMMENT '订单主表ID',
  `user_id` bigint(20) NOT NULL COMMENT '冗余用户ID(方便分片)',
  `sku_id` bigint(20) NOT NULL COMMENT '商品SKU ID',
  `sku_name` varchar(200) NOT NULL COMMENT '商品快照(防止商品改名影响订单)',
  `sku_image` varchar(500) DEFAULT NULL COMMENT '商品图片快照',
  `price` decimal(18,2) NOT NULL COMMENT '下单时单价',
  `quantity` int(11) NOT NULL COMMENT '购买数量',
  `total_amount` decimal(18,2) NOT NULL COMMENT '小计金额',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_order_id` (`order_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品明细';

冗余策略:

  • 冗余 sku_namesku_imageprice,避免关联商品服务(商品信息可能变更)


5. 订单状态机设计(核心)

状态机是订单系统的大脑,必须严格定义允许的状态流转。

5.1 状态流转图

5.2 状态机实现(Java枚举 + 状态模式)

java

public enum OrderStatus {
    WAIT_PAY(0, "待支付") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == PAID || target == CANCELLED;
        }
    },
    PAID(1, "已支付") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == WAIT_SHIP || target == CANCELLED;
        }
    },
    WAIT_SHIP(2, "待发货") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == SHIPPED;
        }
    },
    SHIPPED(3, "已发货") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == COMPLETED;
        }
    },
    COMPLETED(4, "已完成") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == AFTER_SALE;
        }
    },
    CANCELLED(5, "已取消") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return false; // 终态
        }
    },
    AFTER_SALE(6, "售后中") {
        @Override
        public boolean canTransitionTo(OrderStatus target) {
            return target == COMPLETED;
        }
    };

    private final int code;
    private final String desc;

    OrderStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public abstract boolean canTransitionTo(OrderStatus target);

    // 安全的状态变更方法
    public OrderStatus transitionTo(OrderStatus target) {
        if (!this.canTransitionTo(target)) {
            throw new IllegalStateException(
                String.format("订单状态不能从 %s 变更为 %s", this.desc, target.desc)
            );
        }
        return target;
    }
}

5.3 状态变更的并发控制

java

@Service
public class OrderStatusService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    /**
     * 使用乐观锁更新状态
     */
    public boolean changeOrderStatus(Long orderId, OrderStatus newStatus, OrderStatus expectStatus) {
        Order order = new Order();
        order.setId(orderId);
        order.setOrderStatus(newStatus.getCode());
        
        // WHERE id = #{id} AND order_status = #{expectStatus} AND version = #{version}
        int rows = orderMapper.updateStatusWithVersion(order, expectStatus.getCode());
        
        if (rows <= 0) {
            throw new ConcurrentModificationException("订单状态已被修改,请重试");
        }
        return true;
    }
}

6. 核心流程:下单(高并发版本)

6.1 下单时序图

6.2 核心下单代码(服务层)

java

@Service
@Transactional(rollbackFor = Exception.class)
public class OrderCreateService {
    
    @Autowired
    private IdGenerator idGenerator;        // 雪花算法
    @Autowired
    private RedisStockService redisStockService;
    @Autowired
    private RocketMQTemplate mqTemplate;
    
    public CreateOrderResp createOrder(CreateOrderReq req) {
        // 1. 防重令牌校验(防止重复下单)
        String token = req.getToken();
        if (!redisTemplate.delete("order:token:" + token)) {
            throw new BusinessException("订单正在处理,请勿重复提交");
        }
        
        // 2. 计算订单金额(调用促销服务,带缓存)
        OrderAmount amount = promoService.calculateAmount(req.getSkuList(), req.getCouponId());
        
        // 3. 扣减Redis库存(原子操作)
        for (SkuItem item : req.getSkuList()) {
            Boolean success = redisStockService.deductStock(item.getSkuId(), item.getQuantity());
            if (!success) {
                throw new StockInsufficientException("商品库存不足:" + item.getSkuId());
            }
        }
        
        // 4. 生成订单ID和订单号
        Long orderId = idGenerator.nextId();
        String orderNo = generateOrderNo();
        
        // 5. 插入订单主表
        Order order = buildOrder(orderId, orderNo, req, amount);
        orderMapper.insert(order);
        
        // 6. 插入订单明细
        List<OrderItem> items = buildOrderItems(orderId, req);
        orderItemMapper.batchInsert(items);
        
        // 7. 发送延时消息(15分钟后检查支付状态)
        OrderTimeoutMessage timeoutMsg = new OrderTimeoutMessage(orderId, orderNo);
        mqTemplate.syncSend("order-timeout-topic", timeoutMsg, 
            1000, 15 * 60 * 1000);  // 延时15分钟
        
        // 8. 发送异步消息:扣减MySQL真实库存
        mqTemplate.send("stock-deduct-topic", 
            new StockDeductMessage(req.getSkuList()));
        
        return CreateOrderResp.success(orderId, orderNo, amount.getPayAmount());
    }
    
    private String generateOrderNo() {
        // 格式:年月日 + 时间戳后6位 + 机器ID + 随机数
        return "ORD" + System.currentTimeMillis() + 
               IdUtil.getWorkerId() + 
               RandomUtil.randomNumbers(4);
    }
}

6.3 延时消费(订单超时关闭)

java

@Component
@RocketMQMessageListener(topic = "order-timeout-topic", consumerGroup = "order-timeout-group")
public class OrderTimeoutConsumer implements RocketMQListener<OrderTimeoutMessage> {
    
    @Override
    public void onMessage(OrderTimeoutMessage message) {
        // 查询订单状态
        Order order = orderMapper.selectById(message.getOrderId());
        
        // 只有待支付才需要取消
        if (order.getOrderStatus() == OrderStatus.WAIT_PAY.getCode()) {
            // 更新订单状态为已取消
            order.setOrderStatus(OrderStatus.CANCELLED.getCode());
            order.setCancelTime(new Date());
            orderMapper.updateById(order);
            
            // 发送MQ消息,通知库存服务归还库存
            mqTemplate.send("stock-return-topic", 
                new StockReturnMessage(order.getId(), order.getOrderNo()));
        }
    }
}

7. 分布式事务:下单与库存的一致性

7.1 方案选型

方案 适用场景 订单系统选择
Seata AT模式 强一致性、低并发 ❌ 性能损耗大,秒杀场景不合适
Seata TCC模式 核心资金链路 ✅ 用于支付回调场景
RocketMQ事务消息 最终一致性、高并发 ✅ 用于下单主流程
本地消息表+轮询 可靠性要求高 ✅ 用于退款场景

7.2 最终一致性实现(下单→扣库存)

java

@Service
public class OrderCreateWithTxService {
    
    @Autowired
    private RocketMQTemplate mqTemplate;
    @Autowired
    private OrderMapper orderMapper;
    
    public void createOrderWithTx(CreateOrderReq req) {
        // 发送半消息(事务消息)
        TransactionSendResult result = mqTemplate.sendMessageInTransaction(
            "order-create-tx-topic",
            MessageBuilder.withPayload(req).build(),
            req
        );
        
        if (result.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {
            throw new BusinessException("订单创建失败");
        }
    }
    
    @RocketMQTransactionListener
    class OrderCreateTransactionListener implements RocketMQLocalTransactionListener {
        
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            CreateOrderReq req = (CreateOrderReq) arg;
            try {
                // 执行本地事务:插入订单、订单明细
                orderMapper.insert(buildOrder(req));
                orderItemMapper.batchInsert(buildOrderItems(req));
                
                // 提交消息,让消费者扣减库存
                return RocketMQLocalTransactionState.COMMIT;
            } catch (Exception e) {
                log.error("订单本地事务执行失败", e);
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        }
        
        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            // 回查:检查订单是否存在
            String orderId = (String) msg.getHeaders().get("orderId");
            Order order = orderMapper.selectById(orderId);
            return order != null ? 
                RocketMQLocalTransactionState.COMMIT : 
                RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

8. 海量数据:分库分表方案

8.1 分片策略

维度 策略 说明
分片键 user_id 同一用户的订单落在同一库表
分库数 16库 根据未来3年订单量预估(日均100万单,3年≈10亿)
分表数 每库16表 总计256张表
扩缩容 一致性哈希 减少数据迁移量

8.2 ShardingSphere-JDBC配置

yaml

spring:
  shardingsphere:
    datasource:
      names: ds0,ds1,ds2,ds3
      ds0:
        url: jdbc:mysql://host1:3306/order_db_0
      # ... ds1-ds3 类似配置
    sharding:
      tables:
        t_order:
          actualDataNodes: ds$->{0..3}.t_order_$->{0..15}
          tableStrategy:
            inline:
              shardingColumn: user_id
              algorithmExpression: t_order_$->{user_id % 16}
          databaseStrategy:
            inline:
              shardingColumn: user_id
              algorithmExpression: ds$->{user_id % 4}
          keyGenerator:
            column: id
            type: SNOWFLAKE

8.3 跨分片查询处理

java

// 订单列表查询:必须带user_id
public PageResult<OrderVO> listUserOrders(Long userId, Integer page, Integer size) {
    // ShardingSphere会自动路由到正确的分片
    PageHelper.startPage(page, size);
    List<Order> orders = orderMapper.selectByUserId(userId);
    return PageResult.success(orders);
}

// 后台管理查询:按订单号查(不带分片键)
// 解决方案:建立订单号 -> 用户ID的映射缓存
@Component
public class OrderNoCacheService {
    // 订单号生成时,同时写入Redis: order_no -> user_id
    public Long getUserIdByOrderNo(String orderNo) {
        String key = "order:no_map:" + orderNo;
        String userId = redisTemplate.opsForValue().get(key);
        if (userId == null) {
            // 兜底:广播查询所有分片(性能差,仅用于补偿)
            userId = broadcastQuery(orderNo);
        }
        return Long.parseLong(userId);
    }
}

9. 查询性能优化:读写的CQRS分离

9.1 架构方案

text

写库(MySQL分库分表)  →  Canal监听binlog  →  Elasticsearch(订单搜索)
                                              ↓
读库(MySQL只读从库)   ←  定时同步       ←  报表查询

9.2 ES索引设计

json

{
  "order_index": {
    "mappings": {
      "properties": {
        "order_no": { "type": "keyword" },
        "user_id": { "type": "long" },
        "total_amount": { "type": "double" },
        "order_status": { "type": "byte" },
        "create_time": { "type": "date" },
        "sku_names": { "type": "text", "analyzer": "ik_max_word" },
        "receiver_phone": { "type": "keyword" }
      }
    }
  }
}

9.3 数据同步(Canal + MQ)

java

@Component
public class OrderBinlogHandler implements CanalEventListener {
    
    @Autowired
    private ElasticsearchRestTemplate esTemplate;
    
    @Override
    public void onEvent(CanalEntry.Entry entry) {
        if ("t_order".equals(entry.getHeader().getTableName())) {
            List<Order> orders = parseBinlog(entry);
            // 同步到ES(异步)
            esTemplate.save("order_index", orders);
        }
    }
}

10. 订单系统的可观测性

10.1 关键指标(Grafana监控)

指标 阈值 告警动作
下单QPS > 5000 自动扩容
订单创建成功率 < 99.9% 钉钉告警
P99下单耗时 > 500ms 慢查询分析
延时消息积压 > 10000 增加消费者

10.2 全链路追踪(SkyWalking)

在关键方法添加追踪:

java

@Trace
public CreateOrderResp createOrder(CreateOrderReq req) {
    ActiveSpan.tag("user_id", req.getUserId().toString());
    ActiveSpan.tag("order_amount", req.getTotalAmount().toString());
    // 业务逻辑
}

11. 总结与最佳实践

关注点 最佳实践
防止重复下单 前端防重令牌 + 后端Redis分布式锁(key=userId+sku组合)
库存一致性 先扣Redis,异步扣MySQL;超时关单要回滚
订单号生成 雪花算法(不连续、不暴露业务量) + 业务前缀
状态机 显式定义流转规则,运行时校验
分库分表 user_id分片,查询必须带分片键
超时处理 延迟消息 + 定时任务双保险
读优化 CQRS:ES支持复杂搜索,MySQL作为写主库
监控 下单成功率、P99耗时、异常堆栈实时告警

通过以上设计,Java订单系统可以支撑日均百万级订单秒级万级并发下单,同时保证数据的最终一致性高可用性(99.99%可用性)。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐