“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

电商系统之订单设计篇

  • 2020-05-29
  • 本文字数:5418 字

    阅读完需:约 18 分钟

电商系统之订单设计篇

01 概述

订单系统作为电商系统的“纽带”贯穿了整个电商系统的关键流程。其他模块都是围绕订单系统进行构建的。订单系统的演变也是随着电商平台的业务变化而逐渐演变进化着,接下来就和大家一起来解析电商平台的“生命纽带”。


上帝视角订单系统



订单系统的作用是:管理订单类型、订单状态,收集关于商品、优惠、用户、收货信息、支付信息等一系列的订单实时数据,进行库存更新、订单下发等一系列动作。订单系统业务的基本模型涉及用户、商品(库存)、订单、付款,订单基本流程是下订单——>减库存,这两步必须同时完成,不能下了订单不减库存(超卖),或者减了库存没有生成订单(少卖)。超卖商家库存不足,消费者下了单买不到东西,体验不好;少卖商家库存积压或者需要反复修改商品信息,反复麻烦,体验也不好。


02 订单基本概念

设计订单系统时包含几个大的方向需要考虑,这些内容决定了订单系统的稳定性和可持续性。


订单的多样性特点


主要由来源和操作的多样导致了订单多样性点。


订单字段


订单字段包含了订单中需要记录的信息,他的作用主要用于沟通其他系统,为下游系统提供信息依据。



订单信息


订单号作为订单识别的标识,一般按照某种特定规则生成,根据订单的增加进行自增,同时在设计订单号的时候考虑订单无序设置(防止竞争者或者第三方来估算订单量)。订单号后续用作订单唯一标示用于对接 WMS(仓存管理系统)和 TMS(运输管理系统)时的订单识别。


订单状态


订单状态在下面章节会详细描述


用户信息


指买家的相关信息,包括名称、地址、手机号。O2O 还会多一种情况就是自提点,这样地址则会变为自提点的地址。地址信息在后续会作用在 WMS 和 TMS 上用于区分区域和配送安排。


商品信息


商品的基本信息和库存,金额由于比较特殊所以我把金额独立在商品信息以外说,不过逻辑上其实都属于商品信息范畴。商品信息主要影响库存更新和 WMS 产生。


金额信息


订单产生的商品信息,这里面除了要记录最终的金额,过程金额也需要记录。比如商品分摊的优惠金额、支付金额,应付金额等。在后续的订单结算、退换货、财务等环节都需要使用。


时间信息


记录订单每个状态节点的触发时间。

03 订单流程

订单流程是指整个订单从产生到完成整个流转过程,包括了正向和逆向流程的过程。

正向流程

这里面主要是涉及主流电商系统中的通用订单流程,部分细节可以根据自己平台的特殊性进行调整。



需要注意的地方


  1. 订单生成环节存在超时未支付自动取消的过程,库存的占用会在订单取消后释放。

  2. 如果选择 COD(货到付款)则支付环节相应转移到订单配送之后,而过程中所有与款项相关的逻辑变为只操作金额数字,不对结算和账户进行打退款操作。

  3. 金额分摊需要到商品

  4. 订单系统审核主要对恶意用户或者刷单情况进行处理。系统可根据白名单、黑名单、消费频次、促销品购买量方面做风控规则。如果后续会进入到人工审核,则规则上可以适当从宽。当触发规则需要进行订单退订的行为。此处设计时要小心对用户体验的损害,往往前台文案上说明当前节点是审核状态或者是等待接单。

  5. 传统电商则是通过关联第三方物流的物流信息进行跟踪。

  6. 预售等货和移仓需要做成 SOA 服务,以便在交易页面计算预计时间和预计到货时间。移仓处理依赖仓库的情况,也会涉及到后续拆分和合并包裹的逻辑。

  7. 订单产生时先要判断报缺情况,如果出现报缺问题则要考虑整单报缺、部分报缺、换货或者换转退的情况(库存,仓促调拨和退款)。报缺情况分为系统报缺和实物报缺,这是承接但相对独立的两个环节。

  8. 电商系统要考虑 7 天无理由退货的情景,即订单状态完成后申请退货。此时主要涉及的是金额上的计算以及一些财务程序(如发票等)问题的处理。

逆向流程

逆向流程指订单发生取消、退货等情况时引发的订单流程过程。


触发逆向流程的触发主要有几种情况:


  • 用户自主取消订单(整单)

  • 风控系统触发取消订单(整单)

  • 客服接到客诉仲裁后触发取消订单(整单)

  • 超时未支付取消订单(整单)

  • 换货报缺转为退单(整单、部分报缺)



关注点


  1. 订单状态(某一节点后如订单产生后不允许取消订单)

  2. 当退单被商家拒绝后需要转入客服仲裁的环节

  3. 部分退的订单促销一般保持享用状态,但金额按照分摊的金额进行退款

订单状态

从订单状态设计目的和存在价值去分析和理解它背后设计机制:维度及维度颗粒度大小。


  1. 正向和逆向流程维度

  2. 正向订单:已锁定、已确认、已付款、已发货、已结算、已完成、已取消等

  3. 正向预售订单:预付款已付未确认、已确认未付尾款(变更)

  4. 正向问题单:未确认、未锁定、未发货、部分付款、未付款等

  5. 逆向退单:待结算、未收到货、未入库、质检不通过、部分收货、已取消、客户已收货等

  6. 逆向换单:完成、已结算、客服已收货等

  7. 服务对象维度

  8. 顾客/用户:待付款、待发货、待收货、待评价、买家已付款、交易成功/失败、卖家已发货、退款成功、交易关闭、

  9. ERP 等其他交互系统:已锁定、已确认、已分仓、已分配、已出库、已收货、已完成等

  10. 等待买家付款、待付款和待发货订单、退款中的订单、定金已付、买家已付款、

  11. 卖家已发货、交易成功、交易失败、异常订单

订单推送

当状态发生变化时,需要将对应的变化情况告知给相关人员以便了解当前订单的情况,这就是订单推送的作用。


订单推送的触发依赖于状态机的变化,涉及到的信息包括


  • 推送对象(用户、物流人员、商家)

  • 推送方式(push、短信、微信)

  • 推送节点(状态改变)

04 订单系统设计的挑战和实践

订单系统需求演变

第一步:实现购买流程


  1. 实现订单的创建、发货、确认等信息闭环

  2. 支持订单审核(初期可支持人工审核即可)

  3. 支持用户端显示订单相关信息

  4. 支持促销金额的计算


第二步:提供服务


  1. 提供订单分布式服务

  2. 支持跨平台交易单生成(即同一个大交易单内既有商家商品又有自营商品或者是多个商家的商品)

  3. 支持拆单、合并逻辑(配送单、支付单等)

  4. 提供更丰富的订单推送服务,完善订单状态


第三步:支持不同营销手段下的订单类型


平台发展到足够大的规模,提效、稳定变成一个重要的话题。可以提供不同营销场景下的订单,如:团购、预购等。

订单系统架构的演变

第一代:简单粗暴

第一代的问题


第一代系统由于,订单状态是在特定的服务器进行处理,如果服务一旦出现问题就会造成订单的丢失,导致订单流程无法进行下去。


总结:


1、服务单点


2、数据库单点

第二代:无状态异步驱动

第二代系统对于第一代有了很好的提升,应用服务器不再保留订单状态,但是这样的系统设计同时也给数据库服务器造成了高频查询带来的压力,导致数据库相对比较脆弱。


总结:


状态扫描带来的负载

第三代:队列模式

第三代是对于第二代的升级,订单的状态流转不再依靠高频查询数据库来获得,通过队列模式,很好减轻了数据库的压力,但是第三代依然有问题,就是该系统中 server2 成了核心,该模块的维护就会变得很复杂,这也是架构设计的关键,没有完全的完美架构,只能得到一个平衡架构。

三代系统演变中的最佳实践

实践 1: 重试和补偿

  • 多个机器重试不能同步, 需要随机跳跃(Jitter)和指数回退 (exponential back-off)

  • 正在重试的服务也可能宕机,需要保存状态 (State)

实践 2: 幂等性

  • 你没收到响应不见得失败了

  • 你响应了不见得别人以为你成功了

  • 重试必需带上唯一的有意义的 ID

  • 每一个服务的调用都必须是幂等的

  • 非只读的服务必须保存状态

实践 3: 一致性实践

  • 订单系统有强一致性需求

  • 无单点故障的分布式系统的一致性是非常困难的问题

  • 已有算法:Paxos,现有开源系统(e.g. Zookeeper)

  • 有时候单点故障并不可怕,常用的,成熟的关系数据库方案也是一个不错的选择

  • 云端分布式无单点故障的系统

实践 4: 工作流 (Workflow)

  • 可扩展性:

  • 无状态的 Worker,分布式部署,分布式存储 工作流状态

  • 可靠性:

  • 定时器、重试、幂等性、强一致性的状态

  • 可维护性:

  • 工作流的描述和执行 Activity 描述相分离, 支持异步触发

  • 支持版本和升级

系统优化

数据库读写分离


基本的原理是让主数据库处理事务性查询,而从数据库处理 SELECT 查询。数据库复制被用来把事务性查询导致的变更同步到集群中的从数据库。 当然,主服务器也可以提供查询服务。使用读写分离最大的作用无非是环境服务器压力。

好处

  1. 增加冗余

  2. 增加了机器的处理能力

  3. 对于读操作为主的应用,使用读写分离是最好的场景,因为可以确保写的服务器压力更小,而读又可以接受点时间上的延迟。

读写分离提高性能之原因

  1. 物理服务器增加,负荷增加

  2. 主从只负责各自的写和读,极大程度的缓解 X 锁和 S 锁争用

  3. 从库可配置 myisam 引擎,提升查询性能以及节约系统开销

  4. 从库同步主库的数据和主库直接写还是有区别的,通过主库发送来的 binlog 恢复数据,但是,最重要区别在于主库向从库发送 binlog 是异步的,从库恢复数据也是异步的

  5. 读写分离适用与读远大于写的场景,如果只有一台服务器,当 select 很多时,update 和 delete 会被这些 select 访问中的数据堵塞,等待 select 结束,并发性能不高。 对于写和读比例相近的应用,应该部署双主相互复制

  6. 可以在从库启动是增加一些参数来提高其读的性能,例如–skip-innodb、–skip-bdb、–low-priority-updates 以及–delay-key-write=ALL。当然这些设置也是需要根据具体业务需求来定得,不一定能用上

  7. 分摊读取。假如我们有 1 主 3 从,不考虑上述 1 中提到的从库单方面设置,假设现在 1 分钟内有 10 条写入,150 条读取。那么,1 主 3 从相当于共计 40 条写入,而读取总数没变,因此平均下来每台服务器承担了 10 条写入和 50 条读取(主库不承担读取操作)。因此,虽然写入没变,但是读取大大分摊了,提高了系统性能。另外,当读取被分摊后,又间接提高了写入的性能。所以,总体性能提高了,说白了就是拿机器和带宽换性能。

  8. MySQL 复制另外一大功能是增加冗余,提高可用性,当一台数据库服务器宕机后能通过调整另外一台从库来以最快的速度恢复服务,因此不能光看性能,也就是说 1 主 1 从也是可以的。


实现方案


数据库分库分表

不管是采用何种分库分表框架或者平台,其核心的思路都是将原本保存在单表中太大的数据进行拆分,将这些数据分散保存到多个数据库的多个表中,避免因为单表数据量太大给数据的访问带来读写性能的问题。所以在分库分表场景下,最重要的一个原则就是被拆分的数据尽可能的平均拆分到后端的数据库中,如果拆分的不均匀,还会产生数据访问热点,同样存在热点数据因为增长过快而又面临数据单表数据量过大的问题。


而对于数据以什么样的纬度进行拆分,大家看到很多场景中都是对业务数据的 ID(大部分场景此 ID 是以自增长的方式)进行 HASH 取模的方式将数据进行平均拆分,这个简单的方式确实在很多场景下都是非常合适的拆分方法,但并不是在所有的场景中这样拆分的方式都是最优的选择。也就是说数据如何拆分并没有所谓的金科玉律,更多的是需要结合业务数据的结构和业务场景来决定。


下面以大家最熟悉的电商订单数据拆分为例,订单是任何一个电商平台都有的业务数据,每个平台用户提交订单都会在平台后端生成订单相关的数据,一般记录一条订单数据的数据库表结构如下:



订单数据主要由三张数据库表组成,主订单表对应的就是用户的一个订单,每提交一次都会生成一条主订单表的数据。在有些情况下,用户可能在一个订单中选择不同卖家的商品,而每个卖家又会按照该订单中自己提供的商品计算相关的商品优惠(如满 100 元减 10 元)以及按照不同的收货地址设置不同的物流配送,所以会出现子订单的相关概念,即一个主订单会由多个子订单组成,而真正对应到具体每个商品订单信息,则保存在订单详情表中。


如果一个电商平台的业务发展健康的话,订单数据是比较容易出现因为单个数据库表中的数据量过大而造成性能的瓶颈,所以需要对他进行数据库的拆分。此时从理论上对订单拆分是可以由两个纬度进行的,一个纬度是通过订单 ID(一般为自增长 ID)取模的方式,即以订单 ID 为分库分表键;一个是通过买家用户 ID 的纬度进行哈希取模,即以买家用户 ID 为分库分表键。


两种方案做一下对比:


1、如果是按照订单 ID 取模的方式,比如按 1024 取模,则可以保证主订单以及相关子订单,订单详情数据平均落入到后端 1024 个数据库表中,原则上很好地满足了数据尽可能平均拆分的原则。


2、通过采用买家 ID 取模的方式,比如也是按照 1024 取模,技术上则也能保证订单数据拆分到后端的 1024 个数据库表中,但这里就会出现一个业务场景中带来的问题,就是如果有些卖家是交易量非常大的,那这些卖家的订单数据量(特别是订单详情表的数据量)会比其他卖家要多处不少,也就是会出现数据不平均的现象,最终导致这些卖家的订单数据所在的数据库会相对其他数据库提前进入数据归档(为避免在线交易数据库的数据的增大带来数据库性能的问题,一般将 3 个月内的订单数据保存在线交易数据库中,超过 3 个月的订单会归档后端专门的归档数据库)。


所以从对『数据尽可能平均拆分』这条原则来看,按照订单 ID 取模的方式看起来更能保证订单数据的平均拆分,但我们暂时不要这么快下结论,也要根据不同的业务场景和最佳实践角度多思考不同纬度带来的优缺点。

总结

电商平台的需求一直在变化,随之订单系统的架构也会随之变化,架构设计就是一个持续改进的过程,这篇文章还有好多细节未提及,如果你想把订单系统做的更好,需要更加深入系统的每一个环节,比如:容灾、灾备、分流、流控都需要慢慢雕琢,在架构中没有完美的架构只有平衡的架构,不要追求单点的完美,而是要追求多点的平衡。


作者介绍:李伟山,江湖人称山哥,85 年出品。现任米么金服高级研发总监,曾经在华为、阿里巴巴任职,座右铭:Fake it until you make it。“技术琐话”、"中生代技术"专家团成员,个人经营公众号:技术方舟。


2020-05-29 15:2816275

评论 3 条评论

发布
用户头像
很棒,自己在学习写一个完整的商城,借鉴一下订单的设计。
2022-03-09 12:11
回复
用户头像
很棒
2021-10-12 20:57
回复
用户头像
好家伙,那就准备设计数据库吧
2020-10-06 10:22
回复
没有更多了
发现更多内容

即时通讯安全篇(九):为什么要用HTTPS?深入浅出,探密短连接的安全性

JackJiang

https 即时通讯 ssl im开发

干货| 四种渲染到底是啥?终于有人讲明白了!(上)

Orillusion

开源 WebGL 元宇宙 Metaverse webgpu

Maven 项目

Emperor_LawD

maven 5月月更

覆盖 70% 核心业务,ShardingSphere 如何成为喜马拉雅架构演进的催化剂

SphereEx

Apache 数据库 开源 ShardingSphere SphereEx

druid 源码阅读 4——走一个单测看sql运行

张大彪

如何设计产品帮助中心?从这几点出发

小炮

不用PyScript,网页端运行的Python编辑器

Alexads

Python 代码编辑器

使用 Amazon Cloud WAN 构建您的全球网络

云物互联

云计算 AWS Cloud WAN

FLV封装格式

Loken

音视频 5月月更

Redis「2」缓存一致性与异常处理

Samson

Redis 核心技术与实战 学习成长 5月月更

看 AWS 如何通过 Nitro System 构建竞争优势

云物互联

云计算 AWS DPU 硬件虚拟化技术

一份让面试官满意的简历究竟要做到什么

宇宙之一粟

简历模板 简历 5月月更

反向代理 413 Request Entity Too Large 问题

HoneyMoose

前缀和算法

工程师日月

算法 5月月更

福昕软件与神州数码达成中国区分销合作

联营汇聚

druid 源码阅读(四)返回一个连接

爱晒太阳的大白

5月月更

周日直播,龙蜥社区等8位专家在线报告 | 2022大学生操作系统大赛培训会

OpenAnolis小助手

操作系统 人才培养 大学生 龙蜥社区

Collections和Objects的使用注意

zarmnosaj

5月月更

从相亲来看Flutter 的 StatefulWidget 和 StatelessWidget

岛上码农

flutter ios开发 安卓开发 跨平台应用 5月月更

【LeetCode】一次编辑Java题解

Albert

LeetCode 5月月更

druid源码学习四-多线程之锁探究

Nick

Apache Druid 锁机制 多线程安全

[数据分析实践]-音频分析-BirdCLE-2

浩波的笔记

人工智能 机器学习 数据分析

Cocos Creator学习の有限状态机

空城机

Cocos 5月月更

数据库连接池 -Druid 源码学习(四)

wjchenge

Druid 数据库连接池

面向CV编程:COPY了别人文章中的代码,想让代码能像作者一样跑通,应该注意什么呢?怎样才能让代码愉快地跑起来呢

迷彩

读书笔记 高效工作 程序员 个人思考 5月月更

【愚公系列】2022年05月 二十三种设计模式(十三)-职责链模式(Chain of Responsibility Pattern)

愚公搬代码

5月月更

学生管理系统(2)

5月月更

Druid连接池源码阅读04

石小天

最「难搞」的英伟达也开源了,苹果会是下一个吗?

腾源会

开源 英伟达 苹果 腾源会

使用声网 SDK 为Android APP添加视频直播

声网

android 教程 视频直播

设计模式之原型模式

乌龟哥哥

5月月更

电商系统之订单设计篇_文化 & 方法_技术琐话_InfoQ精选文章