作为一个后端程序员,在网络下单后,后台的逻辑是怎么处理的。订单系统是怎么保持系统低延迟,高可用,还有不丢单情况的。
我们先来看一个简单的订单系统。
这是一个比较简单的系统,前台有结算页提供用户去结算,当后台收到前台用户点击结算操作的时候,就开始处理下单服务。
订单写入到后台的数据库,异构数据到缓存,用户在”我的订单”中进行订单查询,当用户支付完成后,收银台发送消息给下单服务。如下图所示:
我们看下这个系统哪些环节会出现丢单情况,我觉得关键点应该聚焦在写数据库、接受消息、发送订单消息这些环节上。
这边总结几个可能出现丢单的情况:
一.关键逻辑不要使用读写分离的查询方式,比如创建订单后要创建支付单。但是在反查订单的时候由于主从延迟,没查询到订单信息,就可能造成创建支付单失败。
另外,关键逻辑也不要使用缓存来进行订单查询,同样是为了避免因为缓存延迟造成订单反查的失败。
还有一点是,订单补偿不要粗暴地使用消息队列,可能会造成丢单。比如在进行订单状态的修改时,如果处理失败,则将这个订单信息插入到消息队列中,重新消费,以此来完成订单的补偿,这样会造成消息的丢失。
最后一点,接收消息失败时要让消息进行重试,避免丢失。比如一次消费多条订单记录,一条一条的处理,如果成功,就继续处理,如果修改失败,可能会因为return或者continue关键字将其余消息都丢失掉了。
那么如何设计一个支持日万级别订单系统呢?
其实前面的设计已经很容易能支撑日万级的订单系统了,但是出于从稳定性和可用性和不丢单的方面去考虑,我们需要进行重构优化。
一个是:写数据库时,数据库事务粒度不要太大,避免死锁,关注慢sql。比如,不要再数据库事务里同时去更新其他数据源,或者发送MQ消息等,这不仅不能保证数据的一致性,还会把数据库的连接耗尽。
另外需要注意的是,关注异构数据库的稳定性和性能,在网络抖动的情况下,要关注订单系统的幂等性,避免出现计费等错误,影响后续操作流程。
做好这几个点,前面的架构方案,基本就可以满足并支撑一个日万级的订单量的订单系统了。
那么我们如何设计一个支撑日千万级别的订单系统。万级和千万级主要的区别在一个量,由于量的增大,造成系统负载,导致服务宕机。
首先前面的设计过分依赖数据库,而且这个数据库还是订单库,比如修改订单的状态就要反查数据库,这些高并发的写数据库下,会造成数据资源的抢占,从而影响系统的稳定性。
其次,为了避免数据的不一致性,请求访问主要集中在主库,这样主库压力就很大。因此就要设计分库分表的部署结构,下单服务因此就要设计成支持分库分表的架构,但由于热点数据的存在,可能导致数据库数据倾斜的问题,会提早进行扩容。
还有,下单服务耦合业务过重,使得即使是多集群的部署架构,也很难实现快速的处理响应,更何况不同业务的订单处理流程还不一样,使得系统维护性也会越来愈差。比如,创建订单时由于业务不同,数码、3C、图书等订单包含的信息是不一样的,这就需要特殊处理。这种特殊处理逻辑与创建订单耦合在一起,系统自然就会变得越来越慢。
总体来说,上面的架构有如下几个问题:下单服务处理接单慢,数据库压力大,数据异构速度慢,缓存数据质量差等,那我们怎么解决呢?
为了应对日千万级的订单量,我们将下单服务进行了服务拆分,使用单独的接单服务处理接单,使用订单引擎和订单管道处理订单业务逻辑,改用双写和数据补偿的方式处理缓存,使用缓存过期的方式控制数据量。具体的实现方案如下:
当用户在结算页点击提交订单之后,接单服务会在同一个事务里,将订单插入接单库,将首任任务插入任务库,再由订单引擎进行任务调度。什么是任务呢,就是订单的操作步骤,比如说订单缓存,减库存,发送订单通知等,我们通过将整个订单处理流程分解成一个个的任务,逐个单独处理,来应对日千万级的订单处理压力。
当然,接单库为集群服务器,通过随机的方式写入,原因是可以扩展的灵活性,当遇到流量洪峰的时候,新增服务器,对写入逻辑是无感的,接单库是一主多从,当一台故障,可以快速切换主从或者摘除故障机等手段进行修复。
而其中的任务库是订单引擎驱动执行,任务通过订单引擎的服务编排能力生成任务队列,首任务执行成功之后,会插入第二个任务或者同时插入第二个和第三个任务,如果插入任务失败,订单引擎会重新执行当前任务,这里需要每个任务的业务处理都需要保证幂等性。
刚才是任务的创建,下面说说任务的线程调度方式,任务使用多线程的异步方式进行调度,并根据配置选择是串行还是并发执行。
前面的任务执行失败,订单引擎是如何重新执行失败的任务的呢?这是通过任务状态机来实现的,状态机通过识别任务的状态,来判断每个任务是执行完成还是执行失败,并根据状态来进行任务调度,并且会多次执行失败的任务,重试调度的频次会逐渐递减,当超过一定的重试次数后,就会告警通知人工干预。
订单引擎正在执行调度远程服务的并非是订单引擎来调度的,而是由订单引擎调度订单管道,订单管道去调度远程真实的服务来执行的,其原因在于任务引擎本身就是多线程设计架构,对线程占用就比较高。
接下来我们再说下订单缓存的策略。为了保证订单的及时性,在插入订单和任务之后,接单任务会将订单通过接口写入到订单中心的缓存中,以支持用户在线支付之后,在我的订单列表里立即查询到我的订单。
总的来说就是,订单中心接到下单任务之后,会将订单落库。再同步更新缓存,在后续订单中心接收到台账的消息后,也会同时更新数据库和缓存,将订单状态更新为订单完成。
最后我着重说下订单引擎:根据任务编号依次进程任务调度,更新任务状态,并有任务状态机进行任务校验补偿处理。订单引擎通过调度订单管道,实现真实服务的远程调度,订单管道请求服务之后,将处理结果返回任务引擎,最后,订单中心会在接单服务创建订单时,异步写一份订单缓存在订单中心,然后再通过数据异构的方式,再次写一份数据在订单缓存中。
原文始发于微信公众号(二进制跳动):日万级和日千万级的订单系统的设计差异
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/166839.html