阅读 1020

如何设计一个高可用的订单系统

订单系统的基本架构: image.png 前台有结算页提供用户去结算,当后台收到前台用户点击结算操作时就会开始处理下单服务,起初订单被写入到后台的数据库中,然后异构数据到缓存中以此提供用户在我的订单系统中进行订单查询,当用户支付完成后,收银台发送消息给下单的服务进行数据库和混存中订单状态的修改,这样简单的订单系统就完成了,但是真实的订单系统有更加复杂的业务,使系统更加复杂。 接下来,看看系统中哪些环节会出现丢单的问题

订单丢失:写数据库和接受和发送订单消息

1.关键逻辑不能使用读写分离的查询方式,避免从库同步延迟造成订单查询异常 image.png 2.关键逻辑不要使用缓存进行订单查询,由于缓存延迟造成订单反查询失败

image.png

3.订单补偿不要简单粗暴的使用消息队列的方式,避免中间件引发的订单丢失,比如在订单状态修改的时候,如果处理失败,就将这个订单信息插入到消息队列中重新消费,以此完成订单的补偿,这种方式在发送消息和接收消息时有可能存在丢消息的可能 image.png

4接收消息处理失败时一定要让消息重试,避免丢失尤其注意return,continue等关键字,比如一次消费多条记录一条条地进行处理,如果修改状态成功,就继续处理下一条,如果修改失败,可能会因为return或者continue等关键字将其余的消息都丢失掉了

image.png

如何设计一个支持日万级订单系统,考虑到前面可能丢单的问题,以及系统的稳定性和可用性,我们如何进行系统化的重构优化。之前的系统其实很容易处理日万级的订单,所以你只要注意几个关键点就可以了: 1.写数据库时,数据库事务的粒度不要太大,避免锁表,关注慢查询,比如最不要犯的错误就是在数据库事物里面同时去更新其他的数据源或者发送消息到消息中,这不仅不能保证数据的一致性还会把数据库的连接耗尽。

image.png 另外需要注意的是关注数据异构的性能和稳定性,尤其是在网路抖动的情况下,可能会影响用户体验 最后,要关注订单的幂等性,避免出现计费错误,影响后续的操作流程。 做好这几点前面的架构方案基本上能够满足日万级的订单系统了。

如何设计一个支撑日千万级的订单系统,与日万级的订单系统的区别在于量,由于量的增大造成系统负载过重,导致服务最终宕机,那么分析一下前面的系统架构中哪些是系统的瓶颈呢?

首先,前面的架构设计中过度的依赖于数据库,而且数据库还是订单库,持续的读写请求会给数据库造成很大的压力,比如修改订单状态的时候需要反查数据库并进行订单状态更新,这些操作在高并发请求时,会造成数据库资源的抢占,从而影响系统的稳定性,其次,为了避免数据不一致,请求访问主要集中在主库上,因此主库的压力比较大

image.png

因此在用分库分表的架构,下单服务为此也必须进行改造,支持分库分表的架构设计,但是由于热点数据的存在,可能导致数据库出现数据倾斜的问题,引发提早的数据库扩容问题

image.png

还有,由于下单服务耦合过重,使得即使是多集群的部署架构,也很难快速的处理业务响应,更何况不同业务的订单处理流程还是不一样的,使得系统的维护性越来越差,比如:创建订单时由于业务不同,数码,3c,图书等订单系统的信息时不一样的,这就需要特殊处理,这种特殊处理与创建订单耦合在一起,就会导致系统处理速度很慢,最后由于数据库的存储数据量增大,还会导致数据异构性能直线下降以及缓存容量的不断扩大,这都会极大的影响查询性能,而且可能出现业务间的互相影响等问题 总的来说,前面的系统出现问题:下单系统处理订单慢,数据库压力大,数据异构延迟高,缓存数据质量差 image.png

为了应对日千万级的订单量,我们对下单服务进行了拆分,使用的单独的接单服务处理订单,使用订单引擎和订单管道处理订单业务逻辑,改用双写和事物补偿的方式处理缓存,使用缓存过期的方式处理数据量,

image.png

接下来就具体的分析实现方案:当用户点击提交订单之后,接单服务就会在同一个事务里,将订单插入接单库,将首任务插入到任务库,再由订单引擎进行任务调度,什么是任务?任务就是执行订单操作的步骤,比如写订单缓存,减库存,发送订单通知等,以及前面提到的不同的特殊业务流程,这些都是一个个的任务,我们将整个订单处理流程分解成一个个的任务,逐个单独处理,来应对日千万级的订单处理压力。

image.png

其中接单库为多台数据库,通过随机的方式写入数据库中,之所以没有采用哈希等算法,其原因在于扩展能力更加灵活,当遇到流量洪峰来临时,新增数台数据库对写入逻辑是无感的,接单库采用一主多从的部署架构,当一台机器故障,可以通过快读切换主从或者摘除故障机等手段进行修复,

image.png 而其中任务库由订单引擎驱动执行,任务通过订单引擎的服务编排能力,生成任务队列,首任务执行成功之后,会插入第二个任务,或者同时插入第三个和第四个任务,如果任务插入失败,订单引擎会重新执行当前任务,执行成功之后,会继续执行插入操作,这里就需要每个任务的业务处理都需要保证幂等性, image.png

接下来,说一下任务的线程调度方式,任务使用多线程的异步方式进行调度,并根据配置选择是串行执行还是并行执行,有一点注意的是:前面说的任务线程调度执行,那么如果任务执行失败么,订单引擎如何重新执行任务失败的任务呢?这就是任务状态机来实现的,任务状态机就如同一个系统的守护线程。

image.png

任务状态机通过识别任务状态来识别每个任务是执行完成还是执行失败,并根据状态进行任务调度,并且对多次执行失败的任务重试调度的频次也会逐渐减弱,当超过一定重试次数之后就会发出报警进行人工干预, image.png 其实,订单引擎真正执行远程调度远程服务的并非订单引擎来执行的,而是由订单引擎调度订单管道,订单管道去调度远程真实的服务来执行的,其原因在于任务引擎本身就是多线程设计架构,对线程占用就比较高,而远程调度会注册很多服务,服务调度也会启动很多多线程去执行,如果共同部署在同一个系统里,就会出现线程数过多导致cpu飙升的情况,

image.png 接下来再来说一下订单缓存的实现策略:接单服务在处理完一些业务逻辑之后,最后调用下单服务提交订单到订单中心,而在此之前,为了保证订单的及时性,在插入订单和任务之后,接单服务会现将订单通过接口写入到订单中心的缓存中,以支持用户在支付之后,在我的订单列表中能立即查询到我的订单,总体来说,订单中心接到下单服务之后,会将订单落库,便同步到缓存中,在后续订单中心收到台帐的消息之后,也会同时更新数据库和缓存,将订单状态更新为订单完成。

image.png 最后,讲述日千万级的订单系统架构再概括的讲一下,用户在结算页点击结算,结算页调用后台的接单服务,接单服务接收到下单请求之后会负责接单,将订单插入到接单库,同时在一个事务里将首任务插入到任务库并通知调度起订单引擎开始执行任务,订单引擎根据任务编号依次执行任务调度并更新任务状态,并由状态机进行任务校验补偿处理,订单引擎通过调度订单管道实现真实远程调度,订单管道请求服务之后,将处理结果返回任务引擎,最后,订单中心会在接单服务创建订单时异步地写一份订单缓存到订单中心,然后再通过数据异构的方式再次写一份数据在订单缓存中,

image.png

了解完订单的处理流程之后,我们再整个流程中如何保证下单流程的高可用和高性能的,整个订单系统接单的核心流程及几乎为同步执行,只有少数任务,比如发送订单通知给下游,系统时采用消息异步的方式执行,以此来保证订单流程的高性能而整个处理过程,基于订单引擎的调度,通过服务流程编排确定一个订单的执行步骤,并有效地保证每个环节的正确执行避免订单丢单,卡单等异常问题出现,进而保证订单流程的高可用,

文章分类
后端
文章标签