云集微店亿级交易额下的Order子系统架构演变

云集微店亿级交易额下的Order子系统架构演变

前言

随着云集微店在移动互联网领域的异军突起,短短一年时间内商家从0到50W的线性增长,以及每月热卖活动交易额均超过9位数来看,不得不说云集微店是一个奇迹。相比其他已经悄然消失于公众视野的初创电商企业,云集的成功仿佛更像是顺理成章,正是每一位云集同学的携手努力,才换来今天的野蛮生长。


当然,在傲人的成绩面前,云集微店的技术团队默默支撑起了整个平台系统,为了能够让用户顺利购买到心仪的商品,我们经历过无数的通宵,无数的会议、无数的版本迭代、无数的体验优化。当然,不得不说的还是在今年我们对Order子系统进行了一次意义非常重大的架构调整,使之在原有的基础上,每秒的订单处理能力暴增,更是为云集各种形式的热卖活动提供了强有力的支撑和保障。


一、Order子系统架构升级关键点

任何业务系统初期不可能预估用户流量有多大,都是在不停的试错过程中一步一步演变其自身架构满足其自身业务。所以我们常说,互联网领域几乎没有哪一个系统天生就是支持高并发、高可用的大型系统,因为往往系统做大与业务做大是成正比的,技术永远在为业务服务,脱离业务后,技术将会显得毫无意义。


Order子系统具备的几个特点,如下所示:

1、面对用户和商家;

2、订单业务较为复杂;

3、订单业务发展迅速;


针对Order子系统的业务特点,其技术架构需要逐步演变升级。早期Order子系统的架构特点是小而精,系统分层,外围依赖尽可能的少,以DB为中心。但是随着用户流量的成倍提升,以及业务发展得越来越快,高并发、大流量、高可用、海量数据、网络安全等需求刻不容缓


Order子系统架构升级的核心点,如下所示:

1、拆分子系统、共享业务服务化;

2、数据一致性;

3、垂直分库、水平分区;

4、DB宕机系统仍然具备下单能力;

5、完善的监控告警平台;


二、Order子系统架构演变过程

初期架构,小而精,系统分层,外围依赖尽可能的少,如下所示:

云集微店亿级交易额下的Order子系统架构演变


上述架构非常“精简”,适用于业务早期,随着用户流量的增大,系统性能瓶颈开始逐渐暴露,吞吐量严重下降,数据库负载较高、服务器经常无响应等问题层出不穷。当然,最重要的还是应用服务器无法根据用户流量的变化而动态伸缩,由于受限于DB连接数(往往横扩过程中,连接数是机器数的平方),因此我们的技术团队选择了基于Dubbo做服务化,如下所示:

云集微店亿级交易额下的Order子系统架构演变

经过服务化改造后,系统之间以服务的方式进行交互,各个子系统之间的职责变得非常清晰。当然并不是说服务化是必须的,我们也不建议任何团队、任何系统一开始就做服务化,只建议共享业务多、团队庞大或者底层资源链接数达到上限时,才开始做服务化,否则得不偿失。


由于用户下单实际上是对DB进行大量的写入操作,那么数据库到最后一定会成为瓶颈。从宏观来看,Mysql单表超过500万后、以及用户下单量较大时,数据库的TPS\QPS会开始下降,服务端经常处理超时,这一切都是因为数据库的并行处理能力不够,以及单表数据量过大造成的。因此到了这一步时,我们不得不出最后的杀手锏——垂直分库,水平分区,也就是大家常说的Sharding,如下所示:

云集微店亿级交易额下的Order子系统架构演变


云集微店的Order子系统演变到这一步,基本上大多数技术问题都已经得到了解决,那么剩下的就是其细节性问题的改造。


三、Sharding

当下,有Redis等高性能缓存服务做支撑,想要构建一个高QPS的系统并不复杂,横向扩展redis即可,但是Order子系统的业务特点注定了大部分的操作是实打实的写入操作,由于无法依赖缓存消峰,因此单点DB想要抗住某一波下单高峰是不现实的,所以垂直分库、水平分区是互联网领域“标配”的解决方案。


或许有同学会有疑问,为什么不做Mysql Cluster,而要做Mysql Sharding?单纯从技术上来讲,Mysql Cluster仅仅只是一个数据库集群,其优势只是扩展了数据库的并行处理能力,但使用成本、维护成本相当高。而Sharding是一个成熟且实惠的方案,不仅可以解决数据库的并行处理能力,还能够解决单表数据过大的检索瓶颈。简单来说,前者是集群模式,而后者是分布式模式,因此无论从任何一个维度来看,Sharding无疑是当下互联网最好的选择


我们根据自身业务特点,将consumerid和shopid作为路由条件,DB一主一从读写分离,一共拆出了64个订单库,32个主库和32个从库,分别按照db_0000—db_0031进行分布。由于consumerid和shopid面向的是不同的落盘维度,因此任何一份订单数据都是冗余存储的,那么订单表也就由2张逻辑相关,内容相同的数据表构成,逻辑上订单表一共有2048张,1024张consumer_order、shop_order、order_detail主表和1024张consumer_order、shop_order、order_detail从表,每一个订单库均包含32张订单表,分别按照tab_0000—tab_0031进行分布。


Sharding中间件Shark

分布式mysql分库分表中间件,sharding领域的一站式解决方案。具备丰富、灵活的路由算法支持,能够方便DBA实现库的水平扩容和降低数据迁移成本。shark站在巨人的肩膀上(springjdbc、druid),采用应用集成架构,放弃通用性,只为换取更好的执行性能与降低分布式环境下外围系统的宕机风险。目前shark每天为不同的企业、业务提供超过千万级别的sql读/写服务。


我们最终选择使用Shark中间件作为分布式数据路由层,对Sharding后的订单库和订单表进行读/写操作。


Github地址:https://github.com/gaoxianglong/shark


四、Order冗余表数据一致性

为什么需要冗余表?由于Mysql做了Sharding,因此我们使用consumerid和shopid等2个路由维度落盘订单数据。为了提升系统执行性能和降低延迟,使用数据冗余方案是最直接的方式,简单来说,其实就是一份订单数据分别存储在consumer_order和shop_order这2张表中,这样既可满足用户和商家的需求。


既然有面向商家和用户等2个维度的订单表,那么用户下单时,到底是优先写入consumer_order表呢?还是优先写入shop_order表呢?我们的观点谁对业务的影响较小,就优先写入。试想一下,如果优先写入shop_order表,而consumer_order表写入失败,那么在数据补偿之前,由于用户无法查看自己的订单,自然也就无法继续推进后续的付款流程,因此,对于商家而言,毫无意义。


我们的冗余表实现方案选择的是服务异步写,用户下单时,订单服务先写入到consumer_order表,然后再由线程异步写入到shop_order表。这样做的好处是不用同步等待2张表都写入后再返回,下单处理流程短。相对的,系统的复杂度更大,并且有可能会出现数据不一致,如下所示:

云集微店亿级交易额下的Order子系统架构演变


既然服务异步写可能造成数据不一致的情况出现,那么如何保障数据一致性将会是一件非常令人头疼的事情。目前我们主要采用基于RocketMQ的“实时线上消息对”服务程序实现冗余表数据不一致性检测和数据补偿操作,如下所示:

云集微店亿级交易额下的Order子系统架构演变

当用户下单时,首先将订单数据写入到consumer_order表,成功后再写入消息到消息队列。然后将订单数据写入到shop_order表,成功后再写入消息到消息队列。假设补偿程序3秒钟内无法从消息队列中消费到2条对等的消息,则检查冗余表的数据一致性,如果不一致则进行数据补偿。


五、DB宕机不影响下单流程

如果我们的订单库宕机,也不能够影响用户的整体下单流程。用户下单首先写入到redis,然后再写入到订单库的冗余表中。假设redis写入成功,订单库写入失败,仍然意味着下单成功,后期job定时比对redis与订单库中的订单号,如果不一致就进行数据补偿。当然之前已经比对过的订单号,将会进行标记,下次则不再进行比对。

云集微店亿级交易额下的Order子系统架构演变

大型网站架构技术

程序员修炼之道

大型web系统数据缓存设计

基于 Redis 实现分布式应用限流

Cache缓存技术全面解析

京东到家库存系统分析

Nginx 缓存引发的跨域*

浅谈Dubbo服务框架

数据库中间件架构 | 架构师之路

MySQL优化精髓



看完本文有收获?请转发分享给更多人


欢迎关注“畅聊架构”,我们分享最有价值的互联网技术干货文章,助力您成为有思想的全栈架构师,我们只聊互联网、只聊架构!打造最有价值的架构师圈子和社区。


长按下方的二维码可以快速关注我们

云集微店亿级交易额下的Order子系统架构演变