随笔 - 368, 文章 - 0, 评论 - 56, 阅读 - 94万
  博客园  :: 首页  :: 联系 :: 管理

大厂订单架构参考

Posted on   天戈朱  阅读(39)  评论(0编辑  收藏  举报

 随着订单量的增长、业务复杂度的提升,订单系统也在不断演变进化,从早期一个订单业务模块到现在分布式可扩展的高并发、高性能、高可用订单系统。整个发展过程中,订单系统经历了几个明显的阶段,通过不同的技术优化方案解决业务上遇到的问题。

1、京东到家系统架构

  • 这个系统架构主要由几个部分构成:用户端分别是C端用户和B端用户(B端用户针对的是像沃尔玛、永辉超市等的一些商家)
  • 商家生产需要用到我们的一些拣货APP和拣货助手,后面商家履约完成会用到配送端
  • 配送端就是给骑手接单抢单,最后是结算部分,分别给骑手和商家结算

  • C端针对的是用户,用户进来浏览、下单到支付,整个过程是用户的操作行为。
  • 基于用户的操作行为,我们有几大模块来支撑,首先是京东到家APP的后端业务支撑的基础服务,另外就是营销系统、业务系统等等。
  • 基于上面这些,我们需要有很多系统来支撑,比如运营支撑系统、管理后台的支撑系统、对商家的履约支撑系统。
  • 这些业务系统的底层大概有三块的持久化,分别是缓存(LocalCache、Redis等)、DB(MySQL、MongoDB等数据库)、ES

2、订单数据入库流程

   用户提单以后数据怎么流转?提单其实是一个把用户下单数据存储到数据库,提单系统做了一些分库分表。那么提完单的数据怎么下发到订单系统生产?

   首先我们会有一个管道,提单通过一个分布式异步任务来下发订单管道里。所有的订单下来都会放到管道里,我们通过一个异步的任务,按照一定的速率,均匀地把订单下发到订单生产系统。

   这样设计有一个好处,比如像大促时可能会有大量数据一下子下发到订单生产系统,对订单生产库有很大压力,所以我们中间设计出一个管道,通过异步任务来分发生产订单。 

  其实个人订单DB跟订单系统是不同维度的数据,因为个人订单其实是基于用户去做了一个分库分表,它每一个查询的订单都是基于这种个人,跟订单生产是不一样的,所以每个维度的数据都是单独的存储,来提高系统的稳定性,以及适合它自身业务特性的设计。

  那么订单系统跟个人中心是怎么交互的?首先异步,我们是通过MQ来交互这些订单状态的变更。另外C端的订单取消,是怎么同步到订单生产系统的?我们是通过RPC的调用来保证订单实时取消,有一个返回结果。

3、订单系统微服务及架构演进

   订单系统的架构演进如下图所示

 2017-2018年,我们根据2016年遇到的问题做了一些拆分,比如按领域拆分不同的APP应用。这样拆分做到的就是系统没有单点,负载均衡可以横向扩展,多点部署。包括引入Redis,其实我们用到了Redis的分布式锁、缓存、有序队列、定时任务。  

 我们数据库为什么升级?因为数据库的数据量越来越大,比如添加一些字段,它其实会做一些锁表操作,随着数据量越大,单表的数据越来越多,数据主从延迟以及一些锁表的时间会越来越长

 所以在加字段的时候对生产影响特别大,我们就会对数据做一个分离,把一些冷的数据单独做一个历史库,剩下的生产库只留最近几天的一些生产需要的数据,这样生产库的订单数据量就会很小,每次修改表的时间是可控的,所以我们会把数据按照冷备进行拆分。

 至于为什么引入ES,是因为订单在生产方面会有一些很复杂的查询,复杂查询对数据库的性能影响非常大,引入ES就可以很好地解决这个问题。 

 2018-2019年,我们发现之前在引入数据库时,用数据冗余来保证一些数据应用可互备互降。比如我们之前在用ES低版本1.7的时候,其实就是一个单点,当集群有问题时是会影响生产的。

我们后来引入了一个双集群,双ES集群互备,当一个集群有问题时,另一个集群可以直接顶上来,确保了应用的高可用和生产没有问题。

 数据的演进最终结构如上图,当这是基于目前业务的一个支撑,在未来业务不断发展的情况下,这个数据库架构是远远不够的。

 基于以上架构,我们主要是做到了一主多从的主备实时切换,同时确保主从在不同机房来保证数据库的容灾能力。同时通过业务隔离查询不同的从库给主库减轻压力,以及冷备数据的隔离的一个思路来保证订单数据库的稳定性。 

 4、ElasticSearch集群  

 最开始我们是单ES集群,DB会通过一个同步写写到ES集群。这个时候我们的ES是一个单机群,如果写失败的话,我们会起一个异步任务来保证数据的最终一致性,是这样的一个架构。

 在ES集群没问题的情况下,这个架构也是没问题的,但当集群有问题时,其实就没有可降级的了

 为了解决这个问题,我们引入了ES的冷备两个集群热集群只保存跟数据库一样的生产库的数据,比如说我们现在保证的就是5天的生产数据,其它所有数据都归档在一个ES的冷集群里。

 通过这种异步跟同步写,通过异步任务来保证最终的集群的数据一致性。这就是ES的架构升级

5、订单系统结构 

 如上图所示,是我们整个订单系统的结构。整个过程我们是通过业务网关、RPC高可用、业务聚合、DB冗余、多机房部署,来保证整个订单应用的一些系统架构高可用。上述就是整体的订单架构演进过程。

6、订单系统容灾能力 

 就像上面提到的,我们对开放平台、商家中心、京明管家等业务系统的支撑怎么做到互备?

 其实就是通过ES的冷热集群,冷集群存全量的数据热集群存最近几天的生产数据

 而Redis是做业务隔离,Redis 存储有一些大key会影响核心业务,我们就会把非核心的业务拆出来,拆到另外一个Redis集群。

 这就是我们系统的业务隔离和集群的互备。

 7、QA

 Q1:集群规模大概是什么样的?各集群节点规模如何?

 A:京东到家订单中心ES 集群目前大约有将近30亿文档数,数据大小约1.3TB,集群结构是8个主分片,每个主分片有两个副本,共24个分片。

       每个机器上分布1-2个分片,如果企业不差钱最好的状态就是每个分片独占一台机器。这些集群规模和架构设计不应该是固定的,每一个业务系统应该根据自身实际业务去规划设计

      这样做确定分片数:

  • ES是静态分片,一旦分片数在创建索引时确定那么后继不能修改;
  • 数据量在亿级别,8或者16分片够用,分片数最好是2的n次方
  • 如果后继数据量的增长超过创建索引的预期,那么需要创建新索引并重灌数据;
  • 创建mapping是请自行制定分片数,否则创建的索引的分片数是ES的默认值。这其实并不符合需求;
  • 副本数:一般设置为1,特色要求除外。

 Q4:ES主要是用于明细单查询,还是聚合统计?Join对资源耗用大吗?如何控制内存及优化?

  A:ES在订单系统中的实践主要是解决复杂查询的问题,ES不建议使用聚合统计,如果非要使用那我也拦不住你,哈哈哈。

 

8、参考:京东把 Elasticsearch 用得真牛逼!日均5亿订单查询完美解决!

    京东到家订单中心系统业务中,无论是外部商家的订单生产,或是内部上下游系统的依赖,订单查询的调用量都非常大,造成了订单数据读多写少的情况。

    我们把订单数据存储在MySQL中,但显然只通过DB来支撑大量的查询是不可取的。同时对于一些复杂的查询,MySQL支持得不够友好,所以订单中心系统使用了Elasticsearch来承载订单查询的主要压力    

  ES查询的原理,当请求打到某号分片的时候,如果没有指定分片类型(Preference参数)查询,请求会负载到对应分片号的各个节点上。

  而集群默认副本配置是一主一副,针对此情况,我们想到了扩容副本的方式,由默认的一主一副变为一主二副,同时增加相应物理机

  下图为订单中心ES集群各阶段性能示意图,直观地展示了各阶段优化后ES集群性能的显著提升:   

  

  当然分片数量和分片副本数量并不是越多越好。分片数可以理解为MySQL中的分库分表,而当前订单中心ES查询主要分为两类:单ID查询以及分页查询。

  分片数越大,集群横向扩容规模也更大,根据分片路由的单ID查询吞吐量也能大大提升,但聚合的分页查询性能则将降低;

  分片数越小,集群横向扩容规模也更小,单ID的查询性能也会下降,但分页查询的性能将会提升。

以如何均衡分片数量和现有查询业务,我们做了很多次调整压测,最终选择了集群性能较好的分片数。 

 

二、美团外卖订单中心的演进


  发文时业务体量:美团外卖由日均几单发展为日均500万单(9月11日已突破600万)

1、订单架构

  随着订单量的增长、业务复杂度的提升,外卖订单系统也在不断演变进化,从早期一个订单业务模块到现在分布式可扩展的高性能、高可用、高稳定订单系统。整个发展过程中,订单系统经历了几个明显的阶段,重点关注各阶段的业务特征、挑战及应对之道。

  外卖订单业务是一个需要即时送的业务,对实时性要求很高。从用户订餐到最终送达用户,一般在1小时内。如果最终送达用户时间变长,会带来槽糕的用户体验。

  在1小时内,订单会快速经过多个阶段,直到最终送达用户。各个阶段需要紧密配合,确保订单顺利完成。下图是一个用户视角的订单流程图。  

   从普通用户的角度来看,一个外卖订单从下单后,会经历支付、商家接单、配送、用户收货、售后及订单完成多个阶段。

   以技术的视角来分解的话,每个阶段依赖于多个子服务来共同完成,比如下单会依赖于购物车、订单预览、确认订单服务,这些子服务又会依赖于底层基础系统来完成其功能。

   第一阶段:早期,外卖整体架构简单、灵活,公共业务逻辑通过jar包实现后集成到各端应用,应用开发部署相对简单。比较适合业务早期逻辑简单、业务量较小、需要快速迭代的情况。

   随着业务的发展以及业务的逐步成熟,业务大框架基本成型,业务在大框架基础上快速迭代。大家共用一个大项目进行开发部署,相互影响,协调成本变高;多个业务部署于同一VM,相互影响的情况也在增多。

   为解决开发、部署、运行时相互影响的问题。我们将订单系统进行独立拆分,从而独立开发、部署、运行,避免受其它业务影响。系统拆分主要有如下几个原则:

  • 相关业务拆分独立系统;
  • 优先级一致的业务拆分独立系统;
  • 拆分系统包括业务服务数据。   

  基于以上原则,我们将订单系统进行独立拆分,所有订单服务通过RPC接口提供给外部使用。订单系统内部,我们将功能按优先级拆分为不同子系统,避免相互影响。订单系统通过MQ(队列)消息,通知外部订单状态变更。

  第二阶段:独立拆分后的订单系统架构如下所示:  

  其中,最底层是数据存储层,订单相关数据独立存储。订单服务层,我们按照优先级将订单服务划分为三个系统,分别为交易系统、查询系统、异步处理系统。

  订单系统经过上述独立拆分后,有效地避免了业务间的相互干扰,保障迭代速度的同时,保证了系统稳定性。

  这时,我们的订单量突破百万,而且还在持续增长。之前的一些小问题,在订单量增加后,被放大,进而影响用户体验

   比如,用户支付成功后,极端情况下(比如网络、数据库问题)会导致支付成功消息处理失败,用户支付成功后依然显示未支付。

   订单量变大后,问题订单相应增多。我们需要提高系统的可靠性,保证订单功能稳定可用。

   为了提供更加稳定、可靠的订单服务,我们对拆分后的订单系统进行进一步升级。下面将分别介绍升级涉及的主要内容

   1)、性能优化

  • 异步化:1、线程或线程池:将异步操作放在单独线程中处理,避免阻塞服务线程;2、消息异步:异步操作通过接收消息完成。
  • 并行化:对所有订单服务进行分析,将其中非相互依赖的操作并行化,从而提升整体的响应时间
  • 缓存:通过将统计信息进行提前计算后缓存,避免获取数据时进行实时计算,从而提升获取统计数据的服务性能。

   2)、一致性优化:订单系统是一个复杂的分布式系统,比如支付涉及订单系统、支付平台、支付宝/网银等第三方。仅通过传统的数据库事务来保障不太可行。对于订单交易系统的事务性,并不要求严格满足传统数据库事务的ACID性质,只需要最终结果一致即可。

  • 重试/幂等:通过延时重试,保证操作最终会最执行。比如退款操作,如退款时遇到网络或支付平台故障等问题,会延时进行重试,保证退款最终会被完成。重试又会带来另一个问题,即部分操作重复进行,需要对操作进行幂等处理,保证重试的正确性。
  • 2PC:指分布式事务的两阶段提交,通过2PC来保证多个系统的数据一致性。比如下单过程中,涉及库存、优惠资格等多个资源,下单时会首先预占资源(对应2PC的第一阶段),下单失败后会释放资源(对应2PC的回滚阶段),成功后会使用资源(对应2PC的提交阶段)

   3)、高可用: 针对订单系统而言,其主要组成组件包括三类:存储层、中间件层、服务层

  • 存储层:存储层的组件如MySQL、ES等本身已经实现了高可用,比如MySQL通过主从集群、ES通过分片复制来实现高可用。存储层的高可用依赖各个存储组件即可。
  • 中间件层:分布式系统会大量用到各类中间件,比如服务调用框架等,这类中间件一般使用开源产品或由公司基础平台提供,本身已具备高可用。
  • 服务层:在分布式系统中,服务间通过相互调用来完成业务功能,一旦某个服务出现问题,会级联影响调用方服务,进而导致系统崩溃。分布式系统中的依赖容灾主要有如下几个思路:
    • 依赖超时设置;
    • 依赖灾备;
    • 依赖降级;
    • 限制依赖使用资源;
  • 订单系统服务层都是无状态服务,通过集群+多机房部署,可以避免单点问题及机房故障,实现高可用。

 第三阶段:存储层的可扩展性改造,主要是对MySQL扩展性改造

   针对订单表超过单库容量的问题,需要进行分表操作,即将订单表数据进行拆分。单表数据拆分后,解决了写的问题,但是如果查询数据不在同一个分片,会带来查询效率的问题(需要聚合多张表)。

   具体来说,外卖主要涉及三个查询维度:订单ID、用户ID、门店ID。对订单表分表时,对于一个订单,我们存三份,分别按照订单ID、用户ID、 门店ID以一定规则存储在每个维度不同分片中。

   这样,可以分散写压力,同时,按照订单ID、用户ID、门店ID三个维度查询时,数据均在一个分片,保证较高的查询效率。

   订单表分表后,订单表的存储架构如下所示:  

  可以看到,分表后,每个维度共有100张表,分别放在4个库上面。对于同一个订单,冗余存储了三份。未来,随着业务发展,还可以继续通过将表分到不同机器上来持续获得容量的提升。

  分库分表后,订单数据存储到多个库多个表中,为应用层查询带来一定麻烦,解决分库分表后的查询主要有三种方案:MySQL服务器端、中间件、应用层

  • MySQL服务器端不能支持,我们只剩下中间件和应用层两个方案
  • 中间件方案对应用透明,但是开发难度相对较大,当时这块没有资源去支持
  • 我们采用应用层方案来快速支持。实现了一个轻量级的分库分表访问插件,避免将分库分表逻辑嵌入到业务代码

  通过分库分表,解决了写容量扩展问题。但是分表后,会给查询带来一定的限制,只能支持主要维度的查询其它维度的查询效率存在问题

 2、ES搜索

  订单表分表之后,对于ID、用户ID、门店ID外的查询(比如按照手机号前缀查询)存在效率问题。这部分通常是复杂查询,可以通过全文搜索来支持。

  在订单系统中,我们通过ES来解决分表后非分表维度的复杂查询效率问题。具体来说,使用ES,主要涉及如下几点

  • 通过databus将订单数据同步到ES
  • 同步数据时,通过批量写入来降低ES写入压力
  • 通过ES的分片机制来支持扩展性

 

三、vivo 全球商城订单中心架构


  vivo Elasticsearch集群应用实践参见:https://elasticsearch.cn/slides/287#page=5

 集群业务规模及集群配置: 

 1、订单系统架构  

  随着用户量级的快速增长,将订单模块从商城拆分出来,独立为订单系统,使用独立的数据库,为商城相关系统提供订单、支付、物流、售后等标准化服务。

 2、技术挑战

  1)、数据量和高并发问题

  •  数据量问题:随着历史订单不断累积,MySQL中订单表数据量已达千万级。

          我们知道InnoDB存储引擎的存储结构是B+树,查找时间复杂度是O(log n),因此当数据总量n变大时,检索速度必然会变慢, 不论如何加索引或者优化都无法解决,只能想办法减小单表数据量。

          数据量大的解决方案有:数据归档、分表

  • 高并发问题:订单量屡创新高,业务复杂度也在提升,应用程序对MySQL的访问量越来越高

          单机MySQL的处理能力是有限的,当压力过大时,所有请求的访问速度都会下降,甚至有可能使数据库宕机。

          并发量高的解决方案有:使用缓存、读写分离、分库

  方案进行简单描述:

  • 读写分离:主库负责执行数据更新请求,然后将数据变更实时同步到所有从库,用多个从库来分担查询请求。          

          订单数据的更新操作较多,下单高峰时主库的压力依然没有得到解决。且存在主从同步延迟,正常情况下延迟非常小,不超过1ms,但也会导致在某一个时刻的主从数据不一致。那就需要对所有受影响的业务场景进行兼容处理,可能会做一些妥协;          

  • 分库:分库又包含垂直分库和水平分库
    1. 水平分库:把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
    2. 垂直分库:按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。
  • 分表:分表又包含垂直分表和水平分表
    1. 水平分表:在同一个数据库内,把一个表的数据按一定规则拆到多个表中
    2. 垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段

3、分库分表技术选型

   参考之前项目经验,并与公司中间件团队沟通后,采用了开源的 Sharding-JDBC 方案。现已更名为Sharding-Sphere

4、分库分表策略

    结合业务特性,选取用户标识作为分片键,通过计算用户标识的哈希值再取模来得到用户订单数据的库表编号.

    假设共有n个库,每个库有m张表, 则库表编号的计算方式为:

  • 库序号:Hash(userId) / m % n
  • 表序号:Hash(userId) % m

    路由过程如下图所示:

  •  

5、分库分表的局限性和应对方案

  1.  分库分表解决了数据量和并发问题,但它会极大限制数据库的查询能力,有一些之前很简单的关联查询,在分库分表之后可能就没法实现了,那就需要单独对这些Sharding-JDBC不支持的SQL进行改写。 
  2.  全局唯一ID设计:分库分表后,数据库自增主键不再全局唯一,不能作为订单号来使用,但很多内部系统间的交互接口只有订单号,没有用户标识这个分片键,如何用订单号来找到对应的库表呢?
    • 我们在生成订单号时,就将库表编号隐含在其中了。这样就能在没有用户标识的场景下,从订单号中获取库表编号。
  3.  历史订单号没有隐含库表信息:用一张表单独存储历史订单号和用户标识的映射关系,随着时间推移,这些订单逐渐不在系统间交互,就慢慢不再被用到
  4. 管理后台需要根据各种筛选条件,分页查询所有满足条件的订单
    • 将订单数据冗余存储在搜索引擎Elasticsearch中,仅用于后台查询

6、怎么做 MySQL 到 ES 的数据同步   

    上面说到为了便于管理后台的查询,我们将订单数据冗余存储在Elasticsearch中,那么,如何在MySQL的订单数据变更后,同步到ES中呢? 

     这里要考虑的是数据同步的时效性和一致性、对业务代码侵入小、不影响服务本身的性能等。

  • MQ方案:ES更新服务作为消费者,接收订单变更MQ消息后对ES进行更新
  • Binlog方案:ES更新服务借助canal等开源项目,把自己伪装成MySQL的从节点,接收Binlog并解析得到实时的数据变更信息,然后根据这个变更信息去更新ES 

    •  

     其中BinLog方案比较通用,但实现起来也较为复杂,我们最终选用的是MQ方案 

     因为ES数据只在管理后台使用,对数据可靠性和同步实时性的要求不是特别高

    考虑到宕机和消息丢失等极端情况,在后台增加了按某些条件手动同步ES数据的功能来进行补偿  

 

 四、滴滴Elasticsearch全业务应用与实践 


  滴滴ES规模参见:https://elasticsearch.cn/slides/255#page=5  

 

ES服务架构 

 滴滴 ES 发展至今,承接了公司绝大部分端上文本检索、少部分日志场景和向量检索场景,包括地图 POI 检索、订单检索、客服、内搜及把脉日志 ELK 场景等。

 滴滴 ES 在2020年由2.X升级到7.6.0,近几年围绕保稳定、控成本、提效能和优生态这几个方向持续探索和改进

1、架构 

2、业务场景

  • 在线全文检索服务,如地图 POI 起终点检索
  • MySQL 实时数据快照,在线业务如订单查询
  • 一站式日志检索服务,通过 Kibana 查询,如 trace 日志
  • 时序数据分析,如安全数据监控
  • 简单 OLAP 场景,如内部数据看板
  • 向量检索,如客服 RAG

 3、部署模式

  • 物理机+小集群部署方式,最大集群机器规模100台物理机左右

 4、数据实时类同步方式

  •  

   如上图所示,实时类同步方式有2种

  • 一种是日志和 MySQL Binlog 通过采集工具采集到 MQ,之后通过统一封装的 DSINK 工具,通过 Flink 写入到 ES
  • 另一种是 MySQL 全量数据,其基于开源的 DataX 进行全量数据同步

 5、成本优化  

 

  成本优化主要包括降低机器成本降低用户成本

  • 降低机器成本核心是降低存储规模和降低 CPU 使用率,即降低机器数;
  • 降低用户成本的核心逻辑是降低业务用量

  所以 ES 整体成本优化策略如下:

  • 索引 Mapping 优化,禁止部分字段倒排、正排
  • 新增 ZSTD 压缩算法,CPU 降低15%
  • 接入大数据资产管理平台,梳理无用分区和索引,协助业务下线

 使用的 ES 版本是 7.6,而社区的最新版本已经更新至 8.13,两者之间存在约 4 年的版本差距。因此,我们今年的重点工作是将 ES 平滑升级至 8.13 版本,以解决以下问题:

  • 新版本的 ES Master 性能更优
  • 能够根据负载自动平衡磁盘使用
  • 减少 segment 对内存的占用
  • 支持向量检索的 ANN 等新特性

小结:

  1. 京东到家:关注个人订单DB;在引入数据库时,用数据冗余来保证一些数据应用可互备互降;对于复杂的查询,MySQL支持得不够友好,所以订单中心系统使用了Elasticsearch来承载订单查询的主要压力 ;
  2. 美团外卖:关注外卖主要涉及三个查询维度:订单ID、用户ID、门店ID。对订单表分表时,对于一个订单,我们存三份;对于ID、用户ID、门店ID外的查询(比如按照手机号前缀查询)存在效率问题。这部分通常是复杂查询,可以通过ES来支持。
  3. VIVO: 可关注分库分表的可选组件shardingsphere;管理后台需要根据各种筛选条件,分页查询所有满足条件的订单; 将订单数据冗余存储在搜索引擎Elasticsearch中,仅用于后台查询
  4. 滴滴:关注成本优化
相关博文:
阅读排行:
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· DeepSeek本地性能调优
· 一文掌握DeepSeek本地部署+Page Assist浏览器插件+C#接口调用+局域网访问!全攻略
点击右上角即可分享
微信分享提示