当前位置: 首页 > 科技观察

日均5亿查询量,京东到家订单中心ES架构演进

时间:2023-03-17 10:30:44 科技观察

京东到家订单中心系统业务,无论是外部商户的订单生产,还是内部上下游系统的依赖,数量订单查询的调用量非常大,导致订单数据读多写少的情况。我们将订单数据存储在MySQL中,但是仅仅通过DB来支持大量的查询显然是不可取的。同时,对于一些复杂的查询,MySQL不够友好,所以订单中心系统使用Elasticsearch来承载订单查询的主要压力。Elasticsearch作为一个强大的分布式搜索引擎,支持近实时的数据存储和搜索,在京东到家的订单系统中发挥着巨大的作用。目前订单中心ES集群存储10亿条文档,日均查询量5亿条。随着近几年京东到家业务的快速发展,订单中心的ES搭建方案也在不断演进。到目前为止,ES集群搭建是一套实时互备的方案,很好的保证了ES集群读写的稳定性。下面就给大家介绍一下这个过程以及开发过程中遇到的一些坑。ES集群架设演进过程初期订单中心ES初期就像一张白纸,基本没有架设方案,很多配置都是维持集群的默认配置。整个集群部署在集团的弹性云上,ES集群节点和机器部署混乱。同时,按照集群维度,一个ES集群会存在单点问题,这对于订单中心业务来说显然是不允许的。在集群隔离阶段,和很多业务一样,ES集群采用混合分布的方式。但是,由于订单中心ES存储的是在线订单数据,偶尔出现的混合集群可能会占用大量的系统资源,导致整个订单中心ES服务出现异常。显然,任何影响订单查询稳定性的情况都是不能容忍的。因此,针对这种情况,首先将订单中心ES所在的弹性云移出占用系统资源较多的集群节点,ES集群情况略有好转。但是随着集群数据的不断增加,弹性云的配置已经不能满足ES集群,为了完全的物理隔离,最终将订单中心ES集群部署在了高配置的物理机上,ES的性能集群得到改善。ES在节点复制调优阶段的性能与硬件资源有很大关系。当ES集群单独部署在一台物理机上时,集群内部的节点不会独占整台物理机的资源。集群运行时,同一台物理机上的节点仍然会出现资源抢占的问题。所以在这种情况下,为了让单个ES节点能够使用最大的机器资源,每个ES节点都部署在单独的物理机上。但是紧接着,问题又来了。如果单个节点出现瓶颈怎么办?我们应该如何优化它?ES查询的原理是,当请求命中一定数量的分片时,如果没有指定分片类型(Preference参数)Query,则请求会加载到分片号对应的各个节点。集群的默认副本配置是一主一副本。针对这种情况,我们想到了扩容副本的方法,从默认的一主一副本改为一主两副本,同时增加相应的物理机。订单中心ES集群架设示意图如图所示。整个设置方法通过VIP对外部请求进行负载均衡:第一层Gateway节点本质上是ES中的ClientNode,相当于一个智能负载均衡器,起到分发请求的作用。第二层是DataNode,负责存储数据和进行与数据相关的操作。整个集群有一组primaryshard和两组secondaryshard(一主二从),从网关节点转发过来的请求在到达数据节点之前会通过轮询进行均衡。通过在集群中加入一组副本,扩展机器容量的方法,增加了集群的吞吐量,从而提高了整个集群的查询性能。下图是订单中心ES集群每个阶段的性能示意图,直观的展示了ES集群经过每个阶段的优化后性能的显着提升:当然还有分片数量和分片数量分片副本并不是越多越好。在这个阶段,我们选择合适的分片数量进行了进一步探索。分片数在MySQL中可以理解为分库分表,目前订单中心ES查询主要分为两类:单ID查询分页查询分片数越大集群水平扩展规模越大,根据shardroutingSingleID的查询吞吐量也可以大幅提升,但是聚合分页查询性能会有所降低。分片数量越少,集群横向扩展的规模越小,单个ID的查询性能也会下降,但分页查询性能会提高。那么如何平衡分片数量和现有的查询业务,我们做了很多调整和压力测试,最终选择了集群性能更好的分片数量。至此在主从集群调整阶段,订单中心的ES集群已经初具规模。但由于订单中心业务时效性要求高,因此对ES查询稳定性要求也高。如果集群中任何一个节点出现异常,都会影响查询服务,从而影响整个订单生产流程。显然,这种异常情况是致命的,所以为了应对这种情况,我们最初的想法是增加一个备集群。当主集群出现异常时,查询流量可以实时降级到备集群。备份集群应该如何设置?如何同步主备之间的数据?备份集群应该存储什么样的数据?考虑到ES集群暂时没有很好的主备方案,同时为了更好的控制ES数据的写入,我们采用业务双写的方式搭建主备集群。每次业务操作需要写入ES数据时,同步写入主集群数据,然后异步写入备集群数据。同时,由于ES查询流量大部分来自最近几天的订单,而订单中心数据库数据有一套归档机制,将指定天数之前关闭的订单调入历史订单数据库。因此在归档机制中加入了删除备集群文档的逻辑,使得新建备集群中存储的订单数据与订单中心在线数据库中的数据量保持一致。同时在查询服务中使用ZK做一个流控开关,保证查询流量可以实时降级到备集群。至此,订单中心主从集群搭建完成,ES查询服务的稳定性大大提升。今天:双集群实时互备阶段,主集群ES版本低于1.7,目前ES稳定版已经迭代到6.x。新版ES不仅对性能进行了大幅优化,还提供了一些新的实用功能。于是我们对主集群进行了版本升级,直接从原来的1.7升级到了6.x版本。集群升级过程繁琐冗长,既要保证线上业务不受影响,又要平滑无感知升级。同时,由于ES集群不支持从1.7到6.x跨多个版本的数据迁移,需要通过重建索引的方式升级主集群。具体升级过程这里不再赘述。主集群升级时,难免会出现不可用,但对于订单中心ES查询服务来说,这种情况是不允许的。因此在升级阶段,备集群临时充当主集群,支持所有在线ES查询,保证升级过程不影响正常在线业务。同时,对于线上业务,我们重新规划定义了两个集群,对承担的线上查询流量进行了重新划分。备集群存放的是最近几天在线的热点数据,数据量比主集群小很多,大约是主集群文档数量的十分之一。集群数据量小。在相同的集群部署规模下,备集群的性能优于主集群。但是在线上真实场景中,大部分线上查询流量也来自于热点数据,所以备集群就是用来承载这些热点数据的查询,备集群逐渐演变成热点数据集群。之前的主集群存储了全量数据,用这个集群来支撑剩下的一小部分查询流量。这部分查询主要是需要查询全量订单的特殊场景查询和订单中心系统内部查询。主集群也慢慢演变成冷数据集群。同时备份集群增加一键降级到主集群的功能。两个集群的状态同等重要,但都可以降级到另一个集群。双写策略也优化如下:假设有一个AB集群,普通同步方式写入master(A集群),异步方式写入backup(B集群)。当集群A发生异常时,同步写入集群B(主),异步写入集群A(备)。ES订单数据同步方案MySQL数据同步到ES,大致分为两种方案。方案1监控MySQL的Binlog,分析Binlog,同步数据到ES集群:优点:业务与ES数据耦合度低,无需关心业务逻辑中ES数据的写入。缺点:Binlog模式只能使用ROW模式,引入了新的同步服务,增加了开发维护成本,同时也增加了ES同步的风险。方案二通过ESAPI直接向ES集群写入数据:优点:简洁明了,可以灵活控制数据的写入。缺点:与业务耦合严重,强烈依赖业务系统的写法。考虑到订单系统ES服务的业务特殊性,订单数据的实时性要求比较高。显然,监听Binlog的方式相当于异步同步,可能会造成较大的延迟。而且,方案一与方案二本质上是相似的,只是引入了新的系统,同时也增加了维护成本。因此,订单中心ES采用直接通过ESAPI写入订单数据的方式。这种方式简单灵活,可以很好的满足订单中心数据同步到ES的需求。由于ES订单数据的同步写在业务中,当创建或更新文档出现异常时,重试必然会影响正常业务操作的响应时间。因此,对于每个业务操作,ES只更新一次。如果出现错误或异常,则向数据库中插入一个补救任务,一个Worker任务会实时扫描数据,并根据数据库订单数据再次更新ES数据。通过这种补偿机制,保证了ES数据与数据库订单数据的最终一致性。遇到的一些坑需要实时性高的查询才能上DB。了解ES写入机制的同学可能知道,新加入的文档会被收集到IndexingBuffer中,然后写入到文件系统缓存中,再到文件系统缓存中,然后就可以像其他文件一样进行索引了.但是,默认情况下,文档会每秒自动从IndexingBuffer刷新到文件系统缓存(即Refresh操作)。所以这就是为什么我们说ES是接近实时搜索而不是实时的原因:文档的更改不会立即对搜索可见,而是在一秒钟内变得可见。目前的订单系统ES使用默认的Refresh配置,所以对于那些订单数据实时性比较高的业务,直接使用数据库查询,保证数据的准确性。避免深度分页查询。ES集群的分页查询支持from和size参数。查询时,每个分片都要构造一个长度为from+size的优先级队列,然后发回给网关节点。排序以找到正确尺寸的文档。假设一个索引有6个primaryshards,from为10000,size为10,每个shard要产生10010个结果,在gateway节点聚合60060个结果,最终找到10个符合要求的文档。可以看出,当from足够大时,即使不发生OOM,也会影响CPU和带宽,从而影响整个集群的性能。所以应该避免深度分页查询,尽量不要使用。FieldData和DocValuesFieldData在线查询偶尔会超时。通过调试查询语句,发现与排序有关。排序使用es1.x版本中的FieldData结构。FieldData占用JVMHeap内存,JVM内存有限。将为FieldDataCache设置一个阈值。如果空间不够,使用最长未使用(LRU)算法移除FieldData,同时加载新的FieldDataCache。加载过程会消耗系统资源并花费大量时间。因此,这个查询的响应时间猛增,甚至影响了整个集群的性能。对于这种问题,解决方案是使用DocValues。DocValuesDocValues是一个列式数据存储结构,类似于FieldData,但是它的存储位置是在Lucene文件中,即不会占用JVMHeap。随着ES版本的迭代,DocValues比FieldData更稳定,DocValues从2.x开始就是默认设置。总结架构的快速迭代源于业务的快速发展。正是由于近年来到家业务的快速发展,订单中心的结构也在不断优化升级。没有最好的架构方案,只有最合适的方案。相信再过几年,订单中心的架构会焕然一新,但吞吐量更大、性能更好、稳定性更强的就是订单中心系统。永恒的追求。