泛谈阿里vs美团订单存储架构演进
1. 订单存储介绍
-
订单的存储背后支撑创单后的系列用户活动,围绕着业务的发展,大厂的订单的数据日益剧增
-
在手淘订单列表,我们可以看见天猫、飞猪、淘票票等诸多 BU 的订单
-
在美团的订单列表,我们可以看见火车票、酒店、美团优选等诸多业务的订单信息
-
如何保障在大数据量的情况,用户或者商家依然可以丝滑查出自己的订单,以及在未来业务的进一步发展中,订单的存储架构是否可以做到平滑升级,在技术上是一个极大挑战
-
在本文中,我们泛谈一下阿里VS美团的订单存储架构,希望对阅读文章的你在日常工作中、跳槽面试中有所帮助
2. 美团外卖订单存储演进
2.1 订单存储
-
美团外卖,送啥都快
-
小时候幻想黄袍加身,35 岁以后,实现了小时候的梦想
-
美团外卖从 2013 年 9 月的第一单成交以来,三个年头过去了,目前美团外卖发展为日均 500 万单,还不包括其他业务的单量,如果仅单库单表,肯定无法支撑如今美团的业务发展
-
众所周知,我们有句老话,时间就是金钱。在订单存储方面,美团大胆采用了 “钱换时间” 的设计架构
-
在订单爆炸增长的阶段,美团的技术人员采取了第一步 以 buyerId,用户纬度进行水平分表,用户进入订单列表查询的时候,可以迅速基于自身 ID 查询到自己的订单信息
-
订单继续增长中,美团的商家也有这样的诉求,我想按周、按月、按年度查询自己的售卖数据,但是利用商家 ID 查询非常的慢,因为此时仅以用户纬度进行分片,商家的数据分散在各个不同的节点中,所以需要进行全量表查询,才可得出该商家的数据,那么此时美团的技术人员采取了第二步,以 sellerId,门店纬度进行水平分表,支撑商家的数据查询
-
订单继续增长中,业务中,以 orderId,订单纬度进行查询的诉求也日益增长,照葫芦画瓢,美团的技术人员采取了第三部,以 ordreId,订单纬度进行水平分表,支撑直接的订单查询
- 订单进行分表之后,对于上述纬度,可以进行一个快速的订单得到,但是存在其他的纬度,比如用户手机号、某外卖品类查询、洗脚店顾客男女占比,这些复杂查询如何实现呢?于是美团的技术人员冗余了第四份数据,通过 es 来解决分库分表后复杂查询的效率问题
- 当然针对三个月前的数据,会使用* HBase 回流到历史库,但是 HBase 存在的一个问题就是,压缩存储后,无法进行顺序时间排序展示(目前暂不清楚美团内部如何处理,未在网上找到相关资料~)
- 所以总结下来,对于一份美团的订单,分别按照订单ID、用户ID、门店ID 以一定的规则存储在每个纬度不同的分片的 Mysql 中,同时 ES 中继续冗余一份数据,作为复杂查询的数据源
2.2 数据一致性
- 冗余 4 份数据后,美团如何保证每一份的数据都是完整的呢,在用户下单的那一刻一定要 4 份数据写入成功后,才算成功吗? 答案,显然不是,在用户下单那一刻,美团会保证写入以用户ID 纬度的分片表中,该表写入成功即返回成功,然后通过 databus 同步到其他纬度的数据表,保证最终一致性即可
- 这时候,有好奇宝宝举手问了,假设我利用 databus 同步失败了,如何保证这个最终一致性呢,所以此时我们需要重试 + 补偿 来保障强一致性,通常情况下我们会利用 本地消息表 来保障强一致性
- 这时候,另外一位好奇宝宝提问了,我下单后涉及到了库存、优惠资格等多个资源吗,下单时会首先预占资源,下单失败后会释放资源,此时我的订单写入和其他的系统如何保持一致性呢?既如何在分布式系统中,保障一致性呢,在 美团 采取的是 2PC 方案
- 2pc 方案示例图如下
2.3 平滑扩容方案
-
未来业务会进一步的发展,那么美团现有架构是什么,是否具备平滑扩容能力?
-
目前美团内部具有的一个框架为 DB Proxy,db 代理层
-
主要分为三层,第一层:权限校验;第二层:DBSQL 分析;第三层:DB 连接层
-
第一层,权限校验,主要 针对大商家的查询,因为大商家容易造成数据的倾斜,所以特例特管,在该层将特定商家路由到特定的数据库节点
-
第二层,DBSQL 分析,该层主要进行 SQL 的分析,主要分析在 join 语句查询的时候,涉及的表是否在同一数据库节点,如果在同一数据节点,则进行下放查询,否则需要进行拆分查询,当然针对于部分小表,可使用广播表,在每个节点都进行冗余,sharding jdbc 插件提供了该能力支持
-
第三层:DB 连接层,该层为线程池连接层,将所有的数据进行连接汇总
-
通过该架构的分析,据目前资料了解,美团内部暂时未有统一的平滑扩容方案
3. 淘宝订单存储演进
3.1 订单存储
- 淘宝从 2003 年成立至今,近 21 年的时间,流量不断增加,每天的实物和虚拟商品的交易达到了亿级别,每次交易都会涉及到商品的信息查询,订单的创建、优惠的扣减、订单的支付等等,每一个环节都涉及到数据库的记录创建或者查询、更新
- 尤其交易订单,更是用户下单过程中的重中之重
- 在淘宝起步阶段,系统是从老外那买的,使用的数据库为 Oracle 数据库,存储着所有的订单信息,订单的创建和历史订单的查询都是在同一数据库中查询
- 随着互联网的发展,单一的数据库已经无法支撑业务的前进,于是对交易的订单进行拆分,分为在线库以及历史库,将三个月前的历史订单迁移进历史库,但是由于数据量巨大,不能满足查询需求,因此那个阶段的用户只能查询三个月以内的订单信息
- 为了解决存储成本,以及历史订单的问题,淘宝将历史订单迁移到 HBase 数据库,整体方案上,没有采取美团那样财大气粗的冗余数据方案,整体方案采用主表 + 索引表,索引表是什么概念呢,索引表可能存在 userId、orderId 、分片位置,通过 userId 我们可以迅速得到一些常用属性信息,而无需进行二次查询。你熟悉吗?对,就是借助了 Mysql 中的覆盖索引思想。但是该方案存在的弊端在于,订单不一定按照时间顺序迁移到历史的订单库,很多类型的订单并不迁移到历史订单库,未完结的订单、退款中的订单、售后处理中的订单等等,所以会导致订单列表不是严格按照时间排序,用户可能发现自己的近期订单出现在不正确的位置
- 于是针对历史的订单开始采取自研的基于 X-Engine 引擎的 PolarDB-X 集群,进一步压缩了存储成本,也解决了订单乱序的问题。当然在线库依旧采取的是 Mysql 集群(InnoDb 引擎),但是只保存最近 90 天的订单数据,数据量少,可以保证较高的缓存命中率,确保读写延时,通过数据同步将在线库中的 90 天以上的订单迁移到历史库中,并从在线库中删除,历史库的存储引擎已切换为 X-Engine,保存超过 90 天的所有交易订单的数据,超过 90 天的订单读写,直接操作历史库。
3.2 平滑扩容方案
-
PolarDB 提供了一套系统性的扩容方案
-
PolarDB-X 1.0扩容原理步骤如下:
- 创建扩容计划
- 选择新增加RDS/PolarDB MySQL,并选定需要迁移到新RDS/PolarDB MySQL实例上的分库,提交任务后系统自动在目标RDS/PolarDB MySQL上创建数据库和账号,并提交任务进行数据迁移同步。
- 全量迁移
- 系统选择当前时间之前的一个时间点,将这个时间点之前的数据进行全量的数据复制迁移。
- 增量数据同步
- 完成全量迁移后,基于全量迁移开始之前时间点的增量变更日志进行增量同步,最终原分库和目标分库数据实时同步。
- 数据校验
- 增量达到准实时同步后,系统自动做全数据校验,并且订正因为同步延迟造成的不一致数据。
- 应用停写和路由切换
- 校验完成后,并且增量依然维持准实时同步,业务选定时间进行切换,为确保数据严格一致,建议应用停服(也可以不停,但可能面临同一条数据高并发写入覆盖问题),引擎层进行分库规则的路由切换,将后续流量转向新库,切换过程秒级完成。
4. 总结
-
泛谈完美团和阿里的订单存储,无论是美团的4份数据的冗余,还是阿里的全局二级索引,都有我们值得借鉴的思想
-
在当下的最好的设计不一定是未来的最好设计。往前走可能有更大的苹果,或者可能在我们未来看的这一部分代码,比如美团的设计,你或许会不理解,但是当时的这个方案一定是为了兼容业务 或者 时间的问题。存在即合理。辩证的眼光看待设计,取其精华,去其糟粕