滴滴 Elasticsearch 多集群架构实践

出品 | 滴滴技术node

做者 | 魏子珺mysql


Elasticsearch 是基于 Lucene 实现的分布式搜索引擎,提供了海量数据实时检索和分析能力。Elastic 公司开源的一系列产品组成的Elastic Stack,能够为日志服务、搜索引擎、系统监控等提供简单、易用的解决方案。
sql

滴滴 Elasticsearch 简介

滴滴2016年初开始构建Elasticsearch平台,现在已经发展到超过3500+Elasticsearch实例,超过5PB的数据存储,峰值写入tps超过了2000w/s的超大规模。restful

Elasticsearch在滴滴有着很是丰富的使用场景,例如线上核心的打车地图搜索,客服、运营的多维度查询,滴滴日志服务等近千个平台用户。
架构

超大的规模和丰富的场景给滴滴Elasticsearch平台带来了极大的挑战,咱们在这期间积累了丰富经验,也取得了一些成果。本文给你们分享下滴滴在Elasticsearch多集群架构的实践。
app

单集群架构瓶颈

介绍单集群架构瓶颈前,先来看下滴滴Elasticsearch单集群的架构。jvm

滴滴Elasticsearch单集群架构


滴滴在单集群架构的时候,写入和查询就已经经过Sink服务和Gateway服务管控起来。elasticsearch

| Sink服务

滴滴几乎全部写入Elasticsearch的数据都是经由kafka消费入到Elasticsearch。kafka的数据包括业务log数据、mysql binlog数据和业务自主上报的数据,Sink服务将这些数据实时消费入到Elasticsearch。tcp

最初设计Sink服务是想对写入Elasticsearch集群进行管控,保护Elasticsearch集群,防止海量的数据写入拖垮Elasticsearch,以后咱们也一直沿用了Sink服务,并将该服务从Elasticsearch平台分离出去,成立滴滴Sink数据投递平台,能够从kafka或者MQ实时同步数据到Elasticsearch、HDFS、Ceph等多个存储服务。
分布式

有了多集群架构后,Elasticsearch平台能够消费一份MQ数据写入多个Elasticsearch集群,作到集群级别的容灾,还能经过MQ回溯数据进行故障恢复。

| Gateway服务

全部业务的查询都是通过Gateway服务,Gateway服务实现了Elasticsearch的http restful和tcp协议,业务方能够经过Elasticsearch各语言版本的sdk直接访问Gateway服务,Gateway服务还实现了SQL接口,业务方能够直接使用SQL访问Elasticsearch平台。

Gateway服务最初提供了应用权限的管控,访问记录,限流、降级等基本能力,后面随着平台演进,Gateway服务还提供了索引存储分离、DSL级别的限流、多集群灾备等能力。

| Admin服务

整个Elasticsearch平台由Admin服务统一管控起来。Admin服务提供了索引的生命周期管理,索引容量自动规划,索引健康分,集群监控等丰富的平台能力,以及为Sink、Gateway服务提供索引、权限等元数据信息。

Elasticsearch单集群瓶颈

随着滴滴Elasticsearch平台规模的快速发展,Elasticsearch集群愈来愈大,最大的时候,是由几百台物理机组成集群,当时集群共 3000+ 的索引,超过了 50000 个 shard,集群总容量达到了PB级别。超大的Elasticsearch集群面临了很大的稳定性风险,这些风险主要来自于如下三个方面:

  • Elasticsearch架构瓶颈

  • 索引资源共享风险

  • 业务场景差别大

Elasticsearch架构瓶颈

Elasticsearch架构在集群变大到必定的规模会遇到瓶颈,瓶颈主要跟Elasticsearch任务处理模型有关。

Elasticsearch看起来是p2p架构,但实际上,仍然是中心化的分布式架构。整个集群只有一个active master。master负责整个集群的元数据管理。集群的全部元数据保存在ClusterState对象中,主要包括全局的配置信息、索引信息和节点信息。只要元数据发生修改,都得由master完成。

Elasticsearchmaster的任务处理是单线程完成的,每次处理任务,涉及到ClusterState的改动,都会将最新的ClusterState对象publish给集群的所有节点,并阻塞等待所有节点接受到变动消息,处理完变动任务后,才完成本次任务。

这样的架构模型致使在集群规模变大的时候出现很严重的稳定性风险。

  • 若是有节点假死,好比jvm内存被打满,进程还存活着,响应master任务时间会很长,影响单个任务的完成时间。

  • 有大量恢复任务的时候,因为master是单线程处理的,全部任务须要排队处理,产生大量的pending_tasks。恢复时间变得很长。

  • Elasticsearch的任务分了优先级,例如put-mapping任务优先级低于建立、恢复索引,若是一些业务上低优先级索引在恢复,正常索引有新字段写入时会被阻塞。

  • master任务处理模型,在任务执行完成后,会回调大量listener处理元数据变动。其中有些回调逻辑在索引、shard膨胀后,会出现处理缓慢的问题,当shard膨胀到5-6w时,一些任务处理须要8-9s的时间,严重影响了集群的恢复能力。

针对这些问题,Elasticsearch也在不断优化,针对相同类型的任务,好比put-mapping任务,master会一次性处理全部堆积在队列里的相同任务。ClusterState对象只传递diff内容,优化回调listener模块的处理耗时环节等等。

可是因为整个集群的任务都集中在一个master的一个线程中处理,在线程中须要同步元数据变动给集群的每一个节点,并阻塞等待所有节点同步完成。这个模型在集群规模不断膨胀时,稳定性会不断降低。

| 索引资源共享风险

Elasticsearch索引是由多个shard组成,master会动态给这些shard分配节点资源。不一样的索引会存在资源混部的状况。


Elasticsearch经过Shard Allocation Awareness的设计,能够将集群的节点按集合划分红不一样的rack。在分配索引时能够指定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,Sink服务能够将不一样topic的数据入到不一样的Elasticsearch集群。查询继续经过Gateway服务,并且业务方仍然像以前同样传递索引名称,而无需感知到平台内部的索引分布。全部的索引在不一样集群的分布细节,均由Gateway服务屏蔽。

整个改造最大的挑战在于查询方式的兼容。Elasticsearch查询索引的方式很是灵活,能够支持*号做为通配符匹配。这样一个索引query可能查询的是多个索引,好比有以下3个索引:

  • index_a

  • index_b

  • index_c


使用index*查询的时候,能够同时查询到index_a、index_b、index_c三个索引。 Elasticsearch这种实现方式很是简单,因为一次query最终查询的是多个shard的数据,因此不管对于具体的索引,仍是模糊的索引,都是先根据索引名称获得shard列表,再将多个shard的query结果merge到一块儿返回。

这样的使用方式,对于多集群方案就会遇到问题,好比index_a在A集群,index_b在B集群、index_c在C集群,对于index*的query,就没法在一个集群上完成。

tribenode介绍

通过调研,咱们发现Elasticsearchtribenode特性能够很好的知足多集群查询的特性。tribenode的实现很是巧妙。org.elasticsearch.tribe包下只有三个文件,核心类是TribeService。tribenode的核心原理就是merge每一个集群的ClusterState对象成一个公共的ClusterState对象,ClusterState包含了索引、shard和节点数据分布表。而Elasticsearch的工做逻辑都是基于ClusterState元数据驱动的,因此对外看起来就是一个包含所有索引的的clientnode。


tribenode经过配置多个Elasticsearch集群地址,而后以clientnode角色分别链接每一个集群,每一个集群看起来会多了一个clientnode。tribenode经过该clientnode角色获取到集群的ClusterState信息,并绑定listener监听ClusterState变化。tribenode将获取的全部集群的ClusterState信息merge到一块儿,造成一个对外部访问使用的ClusterState对象,对外提供服务。tribenode除了注册listener和merge ClusterState,其余的全部逻辑都是复用了clientnode的代码。

能够看到tribenode的优势:

  • 可以知足多集群访问的需求,对外使用是透明的。

  • 实现的简单、优雅,可靠性有保证。


同时tribenode有些不足的地方:

  • tribenode必须以clientnode加入到每一个Elasticsearch集群,master的变动任务必须等待tribenode的回应才能继续,可能影响到原集群的稳定性。

  • tribenode不会持久化ClusterState对象,重启时须要从每一个Elasticsearch集群获取元数据。而在获取元数据期间,tribenode就已经可以提供访问,会致使查询到还在初始化中的集群索引访问失败。tribenode链接的集群多了,初始化会变得很慢。针对该缺陷,咱们平台在重启某个tribenode集群时,将Gateway访问该集群的所有流量切到备份tribenode集群解决。

  • 若是多个集群有相同的索引名称,tribenode只能设置一种perfer规则:随机、丢弃、prefer指定集群。这可能带来查到不符合预期的异常。滴滴Elasticsearch平台经过统一管控索引,避免了同一个索引名称出如今tribenode链接的多个集群中。


正是tribenode有了这些瑕疵,Elasticsearch在高版本引入了Cross ClusterSearch的设计,Cross Cluster不会以节点的形式链接到其余集群,只是将请求代理。目前咱们还在评估Cross Cluster的方案,这里不展开介绍。

多集群架构拓扑

最终改造后,咱们的集群架构拓扑以下:


按照不一样的应用场景,平台将Elasticsearch集群划分红四种类型,Log集群、Binlog集群、文档数据集群、独立集群。公共集群通常最多100台datanode为基准组成一个集群。咱们利用滴滴云实现了集群的自动化部署和弹性扩缩容,能够很方便的水平扩展集群。

Elasticsearch集群前面是多组tribenode集群,主要是为了解决tribenode的稳定性问题。

Gateway会同时链接tribenode集群和Elasticsearch集群,根据应用访问的索引列表,配置应用访问的集群名称,Gateway根据集群名称,将请求代理到指定集群访问,若是访问的是tribenode集群,则该应用能够访问到多个集群的索引。

Admin服务则管控了全部的Elasticsearch集群,以及索引和集群的对应关系。一系列功能都针对多集群作了改造。

Sink服务已经从Elasticsearch平台分离出去,成立DSink数据投递平台,DSink Manager负责管理DSink节点,DSink Manager从Elasticsearch Admin服务获取索引的元数据信息,下发给对应的DSink节点。

多集群架构实践总结


| 多集群架构收益

Elasticsearch多集群架构改造给Elasticsearch平台带来了以下收益:

  • Elasticsearch平台的隔离性能够从物理节点级别上升到Elasticsearch集群级别。对于核心的线上应用,可使用独立的Elasticsearch集群支持。

  • 不一样类型的数据按集群划分,避免相互影响,减少了故障的影响面,对平台稳定性带来极大的提高。

  • Elasticsearch平台的扩展能力进一步提高,经过新增集群能够很好的作到水平扩展。

  • 多集群架构最终作到了对业务方无感知,业务看起来,Elasticsearch平台就像一个无限大的Elasticsearch集群,而无需感知索引真实的集群分布。

| 多集群架构实践经验

滴滴Elasticsearch平台多集群的架构已经演进了一年半时间,这期间也遇到一些多集群架构带来的挑战。

  • tribenode稳定性挑战:

    ▪ 随着集群数量愈来愈多,前面提到的tribenode不足愈来愈明显,好比初始化的时间愈来愈长等等。咱们采起的应对策略是部署多组tribenode集群,有几组链接全量的集群,互为灾备,有几组只链接核心的一些集群,用做更为重要的跨集群访问场景。

    ▪ tribenode的ClusterState元数据包含了太多的索引和shard,Elasticsearch的search逻辑在有些case处理下容易出现耗时过长的状况。Elasticsearch在client接收到search请求时,是在netty的io线程中完成请求转发给每一个shard的,低版本的Elasticsearch尚未限制一次query的shard数量,在一些复杂的模糊索引匹配shard的逻辑中,以及给每一个shard发送query请求时,会出现较高的耗时,可能有超过1-2s的case,这会影响到该netty worker上的其余的请求,形成部分响应飙高的状况。咱们优化了tribenode search流程中一些索引、shard膨胀以后的耗时逻辑,解决了该问题。


  • 多集群配置、版本统一的挑战:

    ▪ 在只有一个集群的时候,平台只用维护一份集群的配置和版本。当集群数量增多后,不一样集群间的_cluster settings信息会出现部分差别,这些差别,可能会致使集群间的负载不均,恢复速度过快或者过慢等问题,每一个集群还有一份基础的索引模板配置,这里面也出现了部分差别。这个问题目前咱们还在解决中,咱们计划将Admin服务分离成索引管理服务和集群管理服务,集群管理会专一于集群版本、配置、部署、扩容、监控等方面对Elasticsearch集群进行更全面的管控。

    ▪ 咱们作的一些Elasticsearch源码优化,会前后在部分集群上线,这样致使了集群间的版本混乱的问题。咱们的解决方案是在Elasticsearch和Lucene内增长内部的版本号,经过公司内部的发布系统,发布Elasticsearch的更新,后续集群管理服务会将集群的版本管理起来。


  • 多集群间容量均衡的挑战:

    ▪ 咱们主要从跨集群索引迁移和容量规划解决集群间容量均衡的挑战,在单Elasticsearch集群的时候,数据迁移能够依赖Elasticsearch的rebalance能力完成。在使用多集群架构后,平台内部的Elasticsearch集群会出现资源分配不均的问题,例若有些索引容量增加的很快,致使所在集群的资源紧张,有些索引数据减小,不须要占用太多资源,致使集群资源空闲。因而产生了索引跨集群迁移的需求。针对这个需求,咱们经过给索引添加版本号,解决了索引跨集群迁移问题。以后咱们有文章会详细的介绍该方案。

    ▪ 滴滴Elasticsearch平台实现了索引容量的自动规划,解决了集群间的容量均衡。Elasticsearch平台能够动态的规划索引的容量。当一个集群容量规划不足时,平台能够动态的迁移一部分索引到空闲的集群中。新的索引接入需求会优先接入在空闲的集群资源中。滴滴Elasticsearch平台是如何实现索引容量的自动规划,也请期待后续的分享。

总结

滴滴的多集群架构,最初是为了解决Elasticsearch单集群架构的瓶颈。为了支持多集群架构,后面的不少组件都须要考虑链接多个集群的场景,给平台架构带来了必定的复杂性。可是多Elasticsearch集群带来的稳定性和隔离性的提高,它所带来的收益远远大于架构的复杂性。改形成多集群架构后,咱们扛住了Elasticsearch平台规模爆炸式增加,Elasticsearch平台的规模翻了5倍多,多集群架构很好的支撑了业务的快速发展。

  • 魏子珺,大数据专家工程师,2016年加入滴滴,负责滴滴Elasticsearch引擎建设

相关文章
相关标签/搜索