订单系统

订单系统 #

系统拆分 #

  • 下单系统:用户选择商品进行下单操作。
  • 订单系统:保存订单信息,提供订单查询功能。
  • 履约系统:订单创建完成后的后续处理。

数据建模 #

表结构

  • 订单主表:订单基本信息。
  • 订单商品表:订单中的商品信息。商品信息需要关联完整的商品快照。
  • 订单支付表:订单的支付和退款信息。
  • 订单优惠表:订单使用的优惠信息。需要为每件商品单独保存使用到的优惠,否则用户支付后发起部分退款时按原价退款会造成资损。

字段设计

  • 将订单号、卖家昵称、更新时间等需要被当做查询条件的字段抽取出独立字段存储,将其余数据结构当成json串存入一个大字段中。
  • 将订单数据结构存储为hashcode字段,和其它系统进行增量数据同步时就只需对比hashcode即可。

订单号设计

  • 淘宝订单编号(退款单编号):13位递增序号 + 买家编号最后6位 = 19位
  • 支付宝流水号:yyyyMMdd + 12位支付宝序号 + 8位递增序号 = 28位
  • 大众点评唯一ID:时间戳 + 用户ID后4位 + 随机数
  • 异构分区键:订单号最后4位由买家ID后缀(2位)+卖家ID后缀组成(2位)

订单存储 #

分区方案

  • 订单ID携带分区信息。买家库和商家库分别以买家ID和卖家ID为维度进行分区。
  • 哈希分区,物理节点和逻辑节点分离。一开始就设计比实际服务器节点数量更多的分区,新增服务器节点时将部分分区移动到新节点上。

扩容步骤

  1. 数据库双写,查询走旧库;日终对账,补齐差异;
  2. 历史数据导入完毕并对账无误后,数据库依然双写,查询走新库;日终对账;
  3. 旧库不再同步写入,只有未完成的原订单需要终态时才异步写入;

订单查询 #

为保证读一致性,查询自己的商品走主库,浏览其它商品走从库。

下单流程 #

下单时需要确保下单时的价格为用户看到的价格,下单完成后需要清空购物车。

  1. 商家每次修改商品时,商品系统都需要生成商品快照,记录版本号。
  2. 用户下单时携带SKU_ID和版本号。订单服务检查请求中的商品版本号。
    • 如果版本号不是最新的商品版本号,则提醒用户“商品信息已改变,请刷新页面重试”。
    • 不使用版本号时直接判断商品信息最近30s内是否有改变,有改变则提示“请刷新页面重试”。
  3. 订单创建成功后,通过消息队列通知购物车系统清空购物车。

下单的幂等性保护 #

重新下单场景

  • 用户以为没有操作成功再次单击按钮。
    • 用户点击下单按钮后立即置灰,禁止再次点击。
  • 黑客直接调用提单接口进行提交。
    • 采用令牌机制。用户每次进入结算页,提单系统颁发一个令牌ID。当用户提交订单后,提单系统会检查令牌ID存在&令牌ID访问次数=1的话才会处理后续逻辑,否则直接返回。
  • 网络超时导致的系统重试。
    • 后端系统支持幂等性。

订单创建的幂等性

  1. 用户进入创建订单页面后,从服务端获取订单号,服务端将订单号保存到Redis中。
  2. 下单时发送订单号,服务端从Redis中删除订单号。
  3. 数据库用订单号作为唯一键进行兜底保护。
  4. 因重复订单导致的插入失败直接返回创建成功而不是创建失败。

订单更新的幂等性

ABA问题

对于同个资源的两个更新操作依次完成了更新操作,但第一个请求的响应在传输过程中丢了,导致调用方自动重试,结果最新的第二个请求的更新结果被重试的第一个请求覆盖了。

解决方法

  1. 增加一列 version。
  2. 每次查询订单的时候,version 需要随着订单数据返回给页面。
  3. 页面在更新数据的请求中,需要把这个version作为更新请求的参数,再带回给订单更新服务。

订单同步 #

下单时订单创建在买家库后异步同步到卖家库。

日千万订单系统 #

性能问题:单一下单服务时单库压力大。

解决方案:将服务拆分为接单服务、订单引擎、订单管道,对数据库进行分片。使用双写和数据补偿的方式处理缓存,使用缓存过期的方式控制数据量。将整个订单处理流程分解成一个一个的任务,逐个单独处理,来应对日千万级的订单处理压力。

具体步骤

  1. 用户在结算页面点击提交订单。
  2. 接单服务将订单插入订单表,并将首任务插入任务表,通知订单引擎。接单库采用多分区架构,每个分区一主多从,随机写入(扩展性强,新增数据库对写入逻辑是无感知的)。
  3. 订单引擎生成任务队列后,采用多线程调度任务,根据配置决定任务队列里的认为是串行还是并行执行,首任务完成后插入第二个任务。
  4. 订单引擎通过订单管道调用远程服务,订单管道通过线程池执行任务。
  5. 订单管道请求后将结果返回给任务引擎。
  6. 任务状态机定期检查任务状态,补偿重试失败任务。
  7. 接单服务在插入订单和任务后,调用订单中心。
  8. 订单中心将订单落库,插入缓存,在后续订单中心接收到台账的消息后,也会同时更新数据库和缓存,将订单状态更新为“订单完成”。
  9. 用户通过订单中心的缓存查询我的订单。