背景
现在直播带货浪潮兴起,各大互联网企业都加快入局互联网直播和电商。淘宝、天猫、京东等老牌互联网电商市场份额,不断被抖音、小红书、快手的直播带货、“种草”等新型电商形式所蚕食。订单管理系统可以看做是互联网电商的核心,串联着整个电商交易的全流程。如何设计并保障系统高可用有着极其重要的意义。下面我们就简单聊聊一个分布式订单管理系统的设计及其容灾架构。
系统设计
数据模型设计
订单管理系统,最基础的功能就是生成并管理订单,生成订单首先需要规划好系统的数据模型,也就是一张订单需要包含哪些数据。需要考虑的关键数据实体通常包括订单、订单项、用户、产品和支付详情。这些实体之间的关系需精心设计以确保系统的可扩展性和性能。
数据模型实体
- 用户(User): 存储用户信息。
- 产品(Product): 存储产品信息。
- 订单(Order): 存储关于订单的信息,每个订单归属于一个用户。
- 订单项(OrderItem): 存储订单中各个产品的具体信息,与订单和产品关联。
- 支付(Payment): 存储下单过程中的支付信息,每个支付对应一个订单。
在实际的业务场景中,这些模型通常会更加复杂,并且会包含更多的字段和关联,如用户地址、订单历史、产品图片、库存日志、用户评价、退款记录等等。
订单单号
前面规划设计好了订单的数据模型,接着看下比较核心的数据订单单号
的生成逻辑。订单单号生成是电商系统设计中的一个重要环节,特别是在高并发和分布式系统环境中,系统生成的订单单号首先不能重复,需要保证全局唯一,这是最基本的要求。同时需要保证单号生成的性能。
单号的生成有以下几种方案以及其优缺点
- 顺序递增 id,从一个起始值开始,每次新增订单时单号递增,可以控制生成的步长,其优点是实现简单,可以使用 MySQL 数据库主键 id 实现,便于理解,生成速度快,低延迟;但其缺点也很明显,在分布式系统中,要确保单号的唯一性和顺序性会非常困难。同时预测性强,如果从安全性考虑不是很适用。
- 基于时间戳生成,可以使用当前时间的时间戳来生成单号,并且在其后面加上一些随机数或机器ID来保证唯一性。这种方案的优点是生成的单号中包含时间信息,有助于订单追踪和管理。同时,在分布式系统中,只要保证机器时间同步,就能生成唯一单号。其缺点在于,如果多个订单在同一时间戳内创建,需要额外逻辑来保证唯一性。需要确保系统时钟的一致性,如果系统时钟同步出现问题,可能会导致单号重复。
- UUID/GUID, 直接生成一个全局唯一标识符(UUID/GUID)作为订单号。这种方案生成简单,不依赖于数据库的顺序和时间,且在任何环境下都是唯一的。但是UUID很长,不易记忆和业务沟通。同时,生成的单号中不含时间信息,对于某些业务分析不方便。
- 分布式ID生成器,如Twitter的Snowflake算法。Snowflake算法的基本思想是:
1. 使用41位的时间戳(精确到毫秒,可以用69年)。
2. 使用10位的机器标识(可以部署在1024个节点)。
3. 使用12位的计数顺序号,每个节点每毫秒可以生成4096个ID。
这种方案能够在不依赖中央数据库的情况下,可以快速生成大量的唯一ID,并且这些ID还是趋势递增的,这对于订单排序和查询是非常有用,适用于高并发的分布式环境。然而,它也需要仔细的时间同步机制,需要维护数据中心ID和机器ID的配置,同时对系统时间的依赖性较高,一旦时钟回拨,可能会生成重复ID,所以在系统设计时需要对数据中心和机器ID有一个合理的规划。目前大都采用这种方案生成订单单号。
接口幂等
接口幂等是指一次或者多次请求同一接口的相同资源,对于接口本身应该要具有相同的结果。而订单管理系统的接口幂等,最主要是为了保证上游重复调用情况下,系统不错误地重复生成相同订单。这是分布式系统设计中的一个重要概念,确保了系统的可靠性和一致性。几个比较典型的业务场景:
- 用户付完款后,此时网络通信故障或者大促期间瞬时流量负载较高,支付系统调用订单系统生成用户下单信息异常,此时上游支付系统并不知道订单信息有没有生成成功,不停重试。
- 在未收到系统明确响应情况下,用户主动在页面不停点击重试,多次提交。
这时候我们就要保证系统不会因为异常重试而不停地生成新订单。接口幂等性在网络不稳定或者客户端重试逻辑存在的场景下尤为重要。实现接口幂等也有多种方案:
- 悲观锁,在处理请求前,先获取对应资源的锁,确保同一时间只有一个请求在操作资源。这种方案的优点是实现简单,可以直接依赖数据库的锁机制,可以直接使用数据库的
select for update
. 其缺点也很明显,可能会导致资源锁定时间过长,影响系统性能和扩展性。 - 乐观锁,与悲观锁相对应的就有乐观锁,可以在数据库记录中添加一个版本号,每次业务操作时对比并更新这个版本号。 其优点相对悲观锁而言并发控制能力强,避免了悲观锁的长时间等待。但是如果是高并发场景下,也可能会因为频繁的冲突检查和回滚导致性能问题。
- 数据库唯一索引,利用数据库的唯一索引特性,确保关键业务操作不会因为重复执行而导致数据冲突。这种方案直接利用现有数据库特性,无需额外开发。但是只适用于数据库层面的幂等操作,对于更复杂的业务逻辑可能不足够。
- 唯一事务编号(Token),这种方案由服务端生成一个唯一标识(
Token
),客户端在发起请求时携带这个Token
,服务端通过Token
来识别和控制重复的请求。这种方案实现相对简单,不依赖其他系统,只是服务端需要维护Token状态,可能会相对增加系统的存储和复杂性。 - 状态机,在订单管理系统中,状态机用于定义订单从创建到完成的整个生命周期。状态机设计及维护的好坏决定一个系统的可靠性。通常的订单管理系统状态机及其扭转大致如下:
1. 创建待付款(Created/Pending Payment). 订单已创建,等待用户付款。前提是用户完成购物车和结算页面的操作。其触发条件:用户点击“提交订单”。
2. 延迟待付款(Delayed Pending Payment). 系统允许订单在一定时间内等待付款,超出这个时间订单会自动取消。前提是用户在规定时间内没有完成付款。触发条件:设定的等待付款时间阈值已过。
3. 已付款(Paid). 用户完成付款操作,等待系统确认支付成功。前提是用户在付款页面完成付款操作。触发条件:支付网关发送支付成功通知。
4. 处理中(Processing). 订单已经支付成功,正在处理中,包括库存检查、包装等。触发条件:系统自动或人工确认付款成功。
5. 已发货(Shipped). 订单中的商品已经提交物流配送且已发出。前提是订单处理完成,商品准备好发货。触发条件:物流系统确认商品发出。
6. 已完成(Completed). 商品已经送达客户且客户已签收,该笔订单完成。前提是用户收到商品,或者物流系统确认送达。触发条件:用户确认收货或系统自动确认收货。
7. 已取消(Cancelled). 订单被取消,可能是因为用户主动取消,支付超时或库存不足。触发条件:用户点击取消订单,或者系统在延迟待付款状态超时后自动取消订单。
8. 退款/退货(Refunded/Returned). 订单中的商品被退回,且用户收到退款。触发条件:退货物流确认商品已返回,系统处理退款操作。通常退款/退货我们又称为电商系统的逆向单。
在实现这个状态机时,可以通过数据库中的订单状态字段来跟踪当前状态,同时使用各种触发器(如支付系统回调、定时任务检查等)来处理状态转换的逻辑。
对于延迟待付款状态,通常会有一个定时任务或者延时队列来检查订单是否已经超时未支付,并据此更新订单状态。
对于每个状态转换,还需要确保相应的业务操作(如库存扣减、退款处理等)的原子性,以维护系统的一致性和数据的准确性。
通常,这些状态会以一定的流程图或者状态转换图的形式呈现,其中包括各状态之间的转换条件和可能的动作。其状态图大致如图所示
选择哪种方案通常取决于具体的业务需求、系统复杂度以及预期的并发量。在设计时,需要综合考虑系统的可靠性、响应时间和开发成本等因素。目前大部分采用状态机
+数据库唯一键
来实现接口幂等。
数据一致性
数据一致性通常是指在数据库中,数据的状态是准确、相互矛盾的信息被消除的情况。对于互联网电商订单管理系统,数据一致性确保所有用户看到的订单信息是当前的和准确的,不管它们访问的是哪一个服务器。
而分布式数据一致性是在分布式系统中,多个副本之间能够保持数据一致的特性。这在互联网电商中尤为重要,为了提高系统可用性以及负载均衡,系统数据(如订单信息、库存状态、用户信息等)通常会在多个数据中心或服务器之间复制。
在互联网电商订单管理系统中,通常需要在以下一些业务场景保持数据一致性:
- 订单处理:保证订单在创建、修改、或取消时,所有的状态更新都能及时反映在整个系统中。
- 库存管理:确保库存数量在商品被购买时能够及时更新,避免超卖或库存不足。
- 价格更新:商品价格变动需要即时反映在所有服务器及呈现给用户。
- 用户账户:用户的账户信息,如余额、优惠券、积分等,需要实时同步,以防止数据不一致带来的用户体验问题,严重的甚至可能会导致资损。
在选择解决方案时,通常需要在一致性、可用性和分区容错性(CAP理论)之间做出权衡。不同的电商平台可能会根据自己的业务特点和技术架构,选择最适合自己的一致性模型。