Elasticsearch是一个基于Lucene的分布式搜索引擎,提供对海量数据的实时检索和分析能力。ElasticStack由Elastic的一系列开源产品组成,可为日志服务、搜索引擎、系统监控等提供简单易用的解决方案。滴滴Elasticsearch简介滴滴从2016年初开始搭建Elasticsearch平台,目前已经发展到超过3500+Elasticsearch实例,超过5PB的数据存储,峰值写入TPS超过2000w/s的超大规模。Elasticsearch在滴滴拥有非常丰富的使用场景,如在线核心出租车地图搜索、客服运营多维查询、滴滴日志服务等近千平台用户。超大规模和丰富的场景给滴滴Elasticsearch平台带来了巨大的挑战。这期间,我们积累了丰富的经验,取得了一定的成绩。本文将与大家分享滴滴在Elasticsearch多集群架构下的实践。单集群架构瓶颈在介绍单集群架构瓶颈之前,我们先来看看滴滴的Elasticsearch单集群架构。滴滴Elasticsearch单集群架构滴滴在单集群架构时,写入和查询已经通过Sink服务和Gateway服务进行控制。①Sink服务滴滴写入Elasticsearch的数据几乎全部被Kafka消费到Elasticsearch中。Kafka的数据包括业务日志数据、MySQLBinlog数据以及业务自上报的数据。Sink服务将这些数据实时消费到Elasticsearch中。Sink服务的设计初衷是管理和控制写入Elasticsearch集群,保护Elasticsearch集群,防止海量数据写入拖累Elasticsearch。此后,我们继续沿用Sink服务,将服务从Elasticsearch平台分离出来,建立了滴滴Sink数据分发平台,可以将数据从Kafka或MQ实时同步到Elasticsearch、HDFS、Ceph等多种存储服务中时间。Elasticsearch平台采用多集群架构,可以消费一个MQ数据写入多个Elasticsearch集群实现集群级容灾,也可以通过MQ回溯数据进行故障恢复。②网关服务所有业务查询都经过Gateway服务,Gateway服务实现了Elasticsearch的HTTPRestful和TCP协议。业务方可以通过Elasticsearch各语言版本的SDK直接访问Gateway服务。Gateway服务也实现了SQL接口,业务方可以直接使用SQL访问Elasticsearch平台。Gateway服务最初提供应用权限控制、访问记录、限流、降级等基础能力。后来随着平台的演进,Gateway服务也提供了索引存储分离、DSL级限流、多集群容灾等能力。③Adminservice整个Elasticsearch平台都是由Adminservice来管理和控制的。Admin服务提供丰富的平台能力,如索引生命周期管理、自动索引容量规划、索引健康评分、集群监控,以及索引和Sink和Gateway服务的权限等元数据信息。Elasticsearch单集群瓶颈随着滴滴Elasticsearch平台的快速发展,Elasticsearch集群越来越大。当时,它由数百台物理机器组成。当时集群共有3000+个索引,超过50000个Shard,集群总容量达到PB级。非常大的Elasticsearch集群面临着很大的稳定性风险,这些风险主要来自以下三个方面:Elasticsearch架构瓶颈指标资源共享风险业务场景差异很大瓶颈主要与Elasticsearch任务处理模型有关。Elasticsearch看起来是一个P2P架构,但实际上,它仍然是一个中心化的分布式架构。整个集群只有一个ActiveMaster。Master负责整个集群的元数据管理。集群的所有元数据都存储在ClusterState对象中,主要包括全局配置信息、索引信息、节点信息。只要修改了元数据,就必须由Master来完成。ElasticsearchMaster的任务处理是在单线程中完成的。每处理完一个任务,就会向集群中的所有节点发布最新的ClusterState对象,并等待所有节点收到变更消息,处理完变更任务后,这个任务就完成了。这样的架构模型在集群规模增大时会带来严重的稳定性风险:如果某个节点卡住了,比如JVM内存满了,而进程还活着,那么响应Master任务的时间会很长,影响完成单个任务的时间。当有大量的恢复任务时,由于Master是单线程处理的,所有的任务都需要排队等待处理,导致大量的pending_tasks。恢复时间变得很长。Elasticsearch任务有优先级。例如,put-mapping任务的优先级低于创建和恢复索引的优先级。如果某些业务正在恢复一些低优先级的索引,写入新字段时会阻塞正常的索引。Master任务处理模型,任务执行完成后,会回调大量的Listener来处理元数据变化。部分回调逻辑在索引和分片扩容后会处理的比较慢。当shard扩容到5-6w时,有些任务处理需要8-9s,严重影响集群的恢复能力。针对这些问题,Elasticsearch也在不断优化。对于相同类型的任务,比如put-mapping任务,Master会一次性处理所有堆积在队列中的相同任务。ClusterState对象只传输diff内容,优化回调Listener模块的耗时过程等。但是由于整个集群的任务都是在一个Master的一个线程中处理的,所以需要在线程中同步集群各个节点的元数据变化,阻塞等待所有节点同步完成。当集群规模不断扩大时,该模型的稳定性会不断下降。①索引资源共享风险Elasticsearch索引由多个分片组成,Master会为这些分片动态分配节点资源。不同的索引将具有混合资源。通过ShardAllocationAwareness的设计,Elasticsearch可以根据集合将集群的节点划分到不同的Racks中。在分配索引时,可以指定Rack列表,这样索引只会分配到指定Rack对应的节点列表中,从而实现物理资源的隔离。但是在实际使用中,由于资源有限,会在一些节点上混杂很多小容量的索引。在这种情况下,由于个别索引的查询和写入量飙升,其他索引的稳定性将受到影响。如果一个节点出现故障,将会影响整个集群的稳定性。整个集群的Master和Clientnode资源是共享的。之前单独提到过Master风险。Clientnode共享带来的GC、抖动、异常等问题都会影响到集群中的所有索引。②业务场景差异大Elasticsearch适用的业务场景差异很大:对于在线核心门户搜索,索引后一般按城市划分,索引容量不大,数据写入不是实时的,也不是实时的TPS-时间写作非常少。比如地图POI数据是线下更新的,外卖商家和菜品的写入量也很少。但是查询的QPS很高,查询对RT的平均时间和抖动要求很高。对于日志检索场景,实时写入量特别大,部分索引甚至超过100w/s的TPS。该场景对吞吐量要求高,但对查询QPS和查询RT要求不高。对于Binlog数据的检索,写入量远小于日志,但对查询复杂度、QPS、RT有一定要求。对于监控分析场景,聚合查询需求会比较多,对Elasticsearch内存压力很大,容易造成节点抖动和GC。这些场景不同,对稳定性和性能的要求也不同。一个Elasticsearch集群即使采用各种优化手段,也很难满足所有需求。最好的办法是根据业务场景划分Elasticsearch集群。多集群的挑战是单集群面临非常大的稳定性风险,所以我们开始规划多集群架构。我们在设计多集群方案的时候,期望业务端的认知度为零。无论是写的还是通过Kafka,sinkservice都可以将不同topic的数据导入到不同的Elasticsearch集群中。查询继续通过Gateway提供服务,业务方仍然像以前一样传递索??引名称,而不知道平台内的索引分布。Gateway服务屏蔽了所有索引在不同集群中的分布细节。整个改造最大的挑战在于查询方式的兼容性。Elasticsearch查询索引的方式非常灵活,可以支持*作为通配符匹配。这样一个索引Query可能会查询多个索引,例如有如下三个索引:index_aindex_bindex_c使用index*查询时,可以同时查询index_a、index_b、index_c三个索引。Elasticsearch的实现非常简单,因为一个Query最终是查询多个Shards的数据。所以无论是具体索引还是模糊索引,都是先根据索引名得到分片列表,然后将多个分片的查询结果合并在一起返回。这种使用方式对于多集群方案会遇到问题。比如index_a在A集群,index_b在B集群,index_c在C集群。对于index*的查询,无法在一个集群上完成。Tribenode简介经过研究,我们发现ElasticsearchTribenode特性可以很好的满足多集群查询的特点。Tribenode的实现非常简洁。org.elasticsearch.tribe包下只有三个文件,核心类是TribeService。Tribenode的核心原理是将每个集群的ClusterState对象合并成一个公共的ClusterState对象。ClusterState包括索引、分片和节点数据分布表。Elasticsearch的工作逻辑是由ClusterState元数据驱动的,所以它看起来像一个包含所有索引的Clientnode。Tribenode配置了多个Elasticsearch集群地址,然后以Clientnode的角色连接到各个集群,每个集群好像多了一个Clientnode。Tribenode通过Clientnode角色获取集群的ClusterState信息,并绑定Listener监听ClusterState的变化。Tribenode将获取到的所有集群的ClusterState信息合并在一起,形成一个ClusterState对象供外部访问,对外提供服务。除了注册Tribenode的Listener和MergeClusterState外,其他所有逻辑都是复用Clientnode的代码。可以看到Tribenode的优势:可以满足多集群访问的需求,对外透明。实现简单、优雅且可靠。同时,Tribenode也有一些不足:Tribenode必须作为Clientnode添加到每个Elasticsearch集群中,Master的变更任务必须等待Tribenode的响应才能继续,这可能会影响原有集群的稳定性。Tribenode不会持久化ClusterState对象,需要在重启时从每个Elasticsearch集群中获取元数据。在获取元数据的过程中,Tribenode已经可以提供访问,这会导致查询无法访问到还在初始化的集群索引。Tribenode连接的集群比较多,初始化会变得很慢。针对这个缺陷,我们的平台在重启一个Tribenode集群时,将Gateway访问集群的流量全部切换到备份Tribenode集群来解决。如果多个集群有相同的索引名称,Tribenode只能设置一个Perfer规则:Random、Discard、Prefer指定的集群。这可能会导致发现意外的异常。滴滴Elasticsearch平台通过统一的索引管理,避免Tribenode连接的多个集群出现相同的索引名称。具有这些缺陷的是Tribenode。Elasticsearch在高版本引入了CrossClusterSearch的设计。CrossCluster不会以节点的形式连接到其他集群,只会代理请求。我们目前正在评估CrossCluster方案,这里不再介绍。经过最终的多集群架构拓扑改造后,我们的集群架构拓扑如下:根据不同的应用场景,平台将Elasticsearch集群分为四种类型:日志集群、Binlog集群、文档数据集群、独立集群。公有集群一般以最多100个Datanode为基准组成集群。我们使用滴滴云来实现集群的自动部署和弹性扩缩容,可以很方便的对集群进行横向扩展。Elasticsearch集群前面是多组Tribenode集群,主要解决Tribenode的稳定性问题。Gateway会同时连接Tribenode集群和Elasticsearch集群,根据应用访问的索引列表配置应用访问的集群名称。网关根据集群名称将请求代理到指定的集群。如果访问的是Tribenode集群,应用可以访问多个集群的索引。Admin服务管理所有的Elasticsearch集群以及索引和集群的对应关系。针对多集群修改了一系列函数。Sink服务已经从Elasticsearch平台中分离出来,形成DSink数据交付平台。DSinkManager负责管理DSink节点。DSinkManager从ElasticsearchAdmin服务中获取索引的元数据信息,发送给对应的DSink节点。多集群架构实践总结多集群架构带来的好处Elasticsearch多集群架构改造为Elasticsearch平台带来了以下好处:可以将Elasticsearch平台的隔离性从物理节点级别提升到Elasticsearch集群级别。对于核心线上应用,可以使用独立的Elasticsearch集群支持。将不同类型的数据划分成集群,避免相互影响,减少故障影响,大大提高平台稳定性。Elasticsearch平台的扩展性进一步提升,通过增加新的集群可以很好的实现横向扩展。多集群架构最终是业务端不感知的。从业务角度看,Elasticsearch平台就像最大的Elasticsearch集群,无需感知索引的真实集群分布。多集群架构实战经验Elasticsearch平台的多集群架构已经演化了一年半,期间也遇到了多集群架构带来的一些挑战。①Tribenode稳定性挑战随着集群数量的增加,上述Tribenode的不足也越来越明显,比如初始化时间越来越长。我们采用的策略是部署多组Tribenode集群,部分集群全连接用于容灾,部分集群只连接部分核心集群,用于更重要的跨集群访问场景。Tribenode的ClusterState元数据包含过多的索引和分片,而Elasticsearch的搜索逻辑在某些情况下往往会耗时过长。当Elasticsearch收到客户端的搜索请求后,将请求转发给Netty的IO线程中的各个shard。低版本的Elasticsearch不限制一次查询的分片数量。在一些复杂的模糊索引匹配分片的逻辑中,在向每个分片发送Query请求时,会出现很高的耗时,可能会出现超过1-2s的情况,影响其他NettyWorker上的请求,导致部分响应中的尖峰。我们优化了TribenodeSearch过程中一些索引和分片扩容后的耗时逻辑,解决了这个问题。②多集群配置和版本统一的挑战当只有一个集群时,平台只需要维护一个集群配置和版本。当集群数量增加时,不同集群之间的_cluster设置信息会有一些差异。这些差异可能导致集群间负载不均,恢复速度过快或过慢等。每个集群也有一个基本的索引模板配置,也有一些差异。我们仍在解决这个问题。我们计划将Admin服务拆分为索引管理服务和集群管理服务。集群管理将围绕集群版本、配置、部署、扩展、监控等方面进行更全面的管控Elasticsearch集群。我们做的一些Elasticsearch源码优化,会在部分集群陆续上线,导致集群间版本混淆的问题。我们的解决方案是在Elasticsearch和Lucene中增加内部版本号,通过公司内部发布系统发布Elasticsearch的更新,后续的集群管理服务会管理集群版本。③多集群间容量均衡的挑战我们主要从跨集群索引迁移和容量规划两个方面解决集群间容量均衡的挑战。在单个Elasticsearch集群的情况下,可以依靠Elasticsearch的Rebalance能力完成数据迁移。使用多集群架构后,平台内部的Elasticsearch集群会出现资源分配不均的问题。例如,一些索引的容量快速增加,导致集群资源紧张,而一些索引数据减少,不需要占用太多资源,导致集群资源闲置。所以需要跨集群进行索引迁移。针对这个需求,我们通过在索引上添加版本号的方式解决了索引跨集群迁移的问题。稍后,我们将有一篇文章详细介绍该程序。滴滴Elasticsearch平台实现了索引容量的自动规划,解决了集群间的容量均衡问题。Elasticsearch平台可以动态规划索引的容量。当集群容量规划不足时,平台可以动态迁移部分索引到空闲集群。新的索引访问需求会在空闲的集群资源中优先访问。滴滴Elasticsearch平台如何实现索引容量的自动规划,敬请期待后续分享。总结滴滴的多集群架构,最初是为了解决Elasticsearch单集群架构的瓶颈而设计的。为了支持多集群架构,下面的很多组件都需要考虑连接多个集群的场景,这给平台架构带来了一定的复杂性。但是,多个Elasticsearch集群带来的稳定性和隔离性的提升远远超过了架构的复杂性。转型为多集群架构后,我们抵挡住了Elasticsearch平台的爆发式增长。Elasticsearch平台的规模增长了5倍以上。多集群架构很好的支撑了业务的快速发展。
