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