PB级大规模Elasticsearch集群运维与调优实践
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PB级大规模Elasticsearch集群运维与调优实践相关的知识,希望对你有一定的参考价值。
参考技术A 某中型互联网公司的游戏业务,使用了腾讯云的Elasticsearch产品,采用ELK架构存储业务日志。因为游戏业务本身的日志数据量非常大(写入峰值在100w qps),在服务客户的几个月中,踩了不少坑,经过数次优化与调整,把客户的ES集群调整的比较稳定,避免了在业务高峰时客户集群的读写异常,并且降低了客户的资金成本和使用成本。下面把服务客户过程中遇到的典型问题进行梳理,总结经验,避免再次踩坑。解决方案架构师A: bellen, XX要上线一款新游戏,日志存储决定用ELK架构,他们决定在XX云和我们之间二选一,我们首先去他们公司和他们交流一下,争取拿下!
bellen: 好,随时有空!
。。。
和架构师一起前往该公司,跟负责底层组件的运维部门的负责人进行沟通。
XX公司运维老大:不要讲你们的PPT了,先告诉我你们能给我们带来什么!
bellen: 。。。呃,我们有很多优势。。。比如灵活地扩容缩容集群,还可以一键平滑升级集群版本,并且提供有跨机房容灾的集群从而实现高可用。。
XX公司运维老大:你说的这些别的厂商也有,我就问一个问题,我们现在要存储一年的游戏日志,不能删除数据,每天就按10TB的数据量算,一年也得有个3PB多的数据,这么大的数量,都放在SSD云盘上,我们的成本太高了,你们有什么方案既能够满足我们存储这么大数据量的需求,同时能够降低我们的成本吗?
bellen: 我们本身提供的有冷热模式的集群,热节点采用SSD云硬盘,冷节点采用SATA盘,采用ES自带的ILM索引生命周期管理功能定期把较老的索引从热节点迁移到冷节点上,这样从整体上可以降低成本。另外一方面,也可以定期把更老的索引通过snapshot快照备份到COS对象存储中,然后删除索引,这样成本就更低了。
XX公司运维老大:存储到COS就是冷存储呗,我们需要查询COS里的数据时,还得再把数据恢复到ES里?这样不行,速度太慢了,业务等不了那么长时间,我们的数据不能删除,只能放在ES里!你们能不能给我们提供一个API, 让老的索引数据虽然存储在COS里,但是通过这个API依然可以查询到数据,而不是先恢复到ES, 再进行查询?
bellen: 。。。呃,这个可以做,但是需要时间。是否可以采用hadoop on COS的架构,把存量的老的索引数据通过工具导入到COS,通过hive去查询,这样成本会非常低,数据依然是随时可查的。
XX公司运维老大:那不行,我们只想用成熟的ELK架构来做,再增加hadoop那一套东西,我们没那么多人力搞这个事!
bellen: 好吧,那可以先搞一个集群测试起来,看看性能怎么样。关于存量数据放在COS里但是也需要查询的问题,我们可以先制定方案,尽快实施起来。
XX公司运维老大:行吧,我们现在按每天10TB数据量预估,先购买一个集群,能撑3个月的数据量就行,能给一个集群配置的建议吗?
bellen: 目前支持单节点磁盘最大6TB, cpu和内存的话可以放到8核32G单节点,单节点跑2w qps写入没有问题,后面也可以进行纵向扩容和横向扩容。
XX公司运维老大:好,我们先测试一下。
N 天后,架构师A直接在微信群里反馈:"bellen, 客户反馈这边的ES集群性能不行啊,使用logstash消费kafka中的日志数据,跑了快一天了数据还没追平,这是线上的集群,麻烦紧急看一下吧。。"
我一看,一脸懵, 什么时候已经上线了啊,不是还在测试中吗?
XX公司运维小B: 我们购买了8核32G*10节点的集群,单节点磁盘6TB, 索引设置的10分片1副本,现在使用logstash消费kafka中的数据,一直没有追平,kafka中还有很多数据积压,感觉是ES的写入性能有问题。
随后我立即查看了集群的监控数据,发现cpu和load都很高,jvm堆内存使用率平均都到了90%,节点jvm gc非常频繁了,部分节点因为响应缓慢,不停的离线又上线。。
经过沟通,发现用户的使用姿势是filebeat+kafka+logstash+elasticsearch, 当前已经在kafka中存储了有10天的日志数据,启动了20台logstash进行消费,logstash的batch size也调到了5000,性能瓶颈是在ES这一侧。客户8核32G*10节点的集群,理论上跑10w qps没有问题,但是logstash消费积压的数据往ES写入的qps远不止10w,所以是ES扛不住写入压力了,所以只能对ES集群进行扩容,为了加快存量数据的消费速度,先纵向扩容单节点的配置到32核64GB,之后再横向增加节点,以保证ES集群能够最大支持100w qps的写入(这里需要注意的是,增加节点后索引的分片数量也需要调整)。
所以一般新客户接入使用ES时,必须要事先评估好节点配置和集群规模,可以从以下几个方面进行评估:
上述场景2遇到的问题是业务上线前没有对集群配置和规模进行合理的评估,导致上线后ES集群负载就很高,通过合理的扩容处理,集群最终抗住了写入压力。但是又有新的问题出现了。
因为kafka积压的数据比较多,客户使用logstash消费kafka数据时,反馈有两个问题:
经过分析客户logstash的配置文件,发现问题出现的原因主要是:
分析后,对kafka和logstash进行了如下优化:
通过上述优化,最终使得logstash机器资源都被充分利用上,很快消费完堆积的kafka数据,待消费速度追平生成速度后,logstash消费kafka一直稳定运行,没有出现积压。
另外,客户一开始使用的是5.6.4版本的logstash,版本较老,使用过程中出现因为单个消息体过长导致logstash抛异常后直接退出的问题:
通过把logstash升级至高版本6.8避免了这个问题(6.x版本的logstash修复了这个问题,避免了crash)。
客户的游戏上线有一个月了,原先预估每天最多有10TB的数据量,实际则是在运营活动期间每天产生20TB的数据,原先6TB*60=360TB总量的数据盘使用率也达到了80%。针对这种情况,我们建议客户使用冷热分离的集群架构,在原先60个热节点的基础上,增加一批warm节点存储冷数据,利用ILM(索引生命周期管理)功能定期迁移热节点上的索引到warm节点上。
通过增加warm节点的方式,客户的集群磁盘总量达到了780TB, 可以满足最多三个月的存储需求。但是客户的需求还没有满足:
XX公司运维老大:给我们一个能存放一年数据的方案吧,总是通过加节点扩容磁盘的方式不是长久之计,我们得天天盯着这个集群,运维成本很高!并且一直加节点,ES会扛不住吧?
bellen: 可以尝试使用我们新上线的支持本地盘的机型,热节点最大支持7.2TB的本地SSD盘,warm节点最大支持48TB的本地SATA盘。一方面热节点的性能相比云盘提高了,另外warm节点可以支持更大的磁盘容量。单节点可以支持的磁盘容量增大了,节点数量就不用太多了,可以避免踩到因为节点数量太多而触发的坑。
XX公司运维老大:现在用的是云盘,能替换成本地盘吗,怎么替换?
bellen: 不能直接替换,需要在集群中新加入带本地盘的节点,把数据从老的云盘节点迁移到新的节点上,迁移完成后再剔除掉旧的节点,这样可以保证服务不会中断,读写都可以正常进行。
XX公司运维老大:好,可以实施,尽快搞起来!
云盘切换为本地盘,是通过调用云服务后台的API自动实施的。在实施之后,触发了数据从旧节点迁移到新节点的流程,但是大约半个小时候,问题又出现了:
XX公司运维小B: bellen, 快看一下,ES的写入快掉0了。
bellen: 。。。
通过查看集群监控,发现写入qps直接由50w降到1w,写入拒绝率猛增,通过查看集群日志,发现是因为当前小时的索引没有创建成功导致写入失败。
紧急情况下,执行了以下操作定位到了原因:
经过了这次扩容操作,总结了如下经验:
在稳定运行了一阵后,集群又出问题了。。
XX公司运维小B: bellen, 昨晚凌晨1点钟之后,集群就没有写入了,现在kafka里有大量的数据堆积,麻烦尽快看一下?
bellen: 。。。
通过cerebro查看集群,发现集群处于yellow状态,然后发现集群有大量的错误日志:
然后再进一步查看集群日志,发现有"master not discovered yet..."之类的错误日志,检查三个master节点,发现有两个master挂掉,只剩一个了,集群无法选主。
登陆到挂了了master节点机器上,发现保活程序无法启动es进程,第一直觉是es进程oom了;此时也发现master节点磁盘使用率100%, 检查了JVM堆内存快照文件目录,发现有大量的快照文件,于是删除了一部分文件,重启es进程,进程正常启动了;但是问题是堆内存使用率太高,gc非常频繁,master节点响应非常慢,大量的创建索引的任务都超时,阻塞在任务队列中,集群还是无法恢复正常。
看到集群master节点的配置是16核32GB内存,JVM实际只分配了16GB内存,此时只好通过对master节点原地增加内存到64GB(虚拟机,使用的腾讯云CVM, 可以调整机器规格,需要重启),master节点机器重启之后,修改了es目录jvm.options文件,调整了堆内存大小,重新启动了es进程。
3个master节点都恢复正常了,但是分片还需要进行恢复,通过GET _cluster/health看到集群当前有超过10w个分片,而这些分片恢复还需要一段时间,通过调大"cluster.routing.allocation.node_concurrent_recoveries", 增大分片恢复的并发数量。实际上5w个主分片恢复的是比较快的了,但是副本分片的恢复就相对慢很多,因为部分副本分片需要从主分片上同步数据才能恢复。此时可以采取的方式是把部分旧的索引副本数量调为0, 让大量副本分片恢复的任务尽快结束,保证新索引能够正常创建,从而使得集群能够正常写入。
总结这次故障的根本原因是集群的索引和分片数量太多,集群元数据占用了大量的堆内存,而master节点本身的JVM内存只有16GB(数据节点有32GB), master节点频繁full gc导致master节点异常,从而最终导致整个集群异常。所以要解决这个问题,还是得从根本上解决集群的分片数量过多的问题。
目前日志索引是按照小时创建,60分片1副本,每天有24*60*2=2880个分片,每个月就产生86400个分片,这么多的分片可能会带来严重的问题。有以下几种方式解决分片数量过多的问题:
和客户沟通过后,客户表示可以接受方式1和方式2,但是方式3和4不能接受,因为考虑到存在磁盘故障的可能性,必须保留一个副本来保证数据的可靠性;另外还必须保证所有数据都是随时可查询的,不能关闭。
在场景5中,虽然通过临时给master节点增加内存,抗住了10w分片,但是不能从根本上解决问题。客户的数据是计划保留一年的,如果不进行优化,集群必然扛不住数十万个分片。所以接下来需要着重解决集群整体分片数量过多的问题,在场景5的最后提到了,用户可以接受开启shrink以及降低索引创建粒度(经过调整后,每两个小时创建一个索引),这在一定程度上减少了分片的数量,能够使集群暂时稳定一阵。
辅助客户在kibana上配置了如下的ILM策略:
在warm phase, 把创建时间超过360小时的索引从hot节点迁移到warm节点上,保持索引的副本数量为1,之所以使用360小时作为条件,而不是15天作为条件,是因为客户的索引是按小时创建的,如果以15天作为迁移条件,则在每天凌晨都会同时触发15天前的24个索引一共24*120=2880个分片同时开始迁移索引,容易引发场景4中介绍的由于迁移分片数量过多导致创建索引被阻塞的问题,所以以360小时作为条件,则在每个小时只会执行一个索引的迁移,这样把24个索引的迁移任务打平,避免其它任务被阻塞的情况发生。
同时,也在warm phase阶段,设置索引shrink,把索引的分片数缩成5个,因为老的索引已经不执行写入了,所以也可以执行force merge, 强制把segment文件合并为1个,可以获得更好的查询性能。
另外,设置了ILM策略后,可以在索引模板里增加index.lifecycle.name配置,使得所有新创建的索引都可以和新添加的ILM策略关联,从而使得ILM能够正常运行。
客户使用的ES版本是6.8.2, 在运行ILM的过程中, 也发现一些问题:
这是因为shrink操作需要新把索引完整的一份数据都迁移到一个节点上,然后在内存中构建新的分片元数据,把新的分片通过软链接指向到几个老的分片的数据,在ILM中执行shrink时,ILM会对索引进行如下配置:
问题是索引包含副本,而主分片和副本分片又不能在同一个节点上,所以会出现部分分片无法分配的情况(不是全部,只有一部分),这里应该是触发了6.8版本的ILM的bug,需要查看源码才能定位解决这个bug,目前还在研究中。当前的workaround是通过脚本定期扫描出现unassigned shards的索引,修改其settings:
优先保证分片先从hot节点迁移到warm节点,这样后续的shrink才能顺利执行(也可能执行失败,因为60个分片都在一个节点上,可能会触发rebalance, 导致分片迁移走,shrink的前置条件又不满足,导致执行失败)。要完全规避这个问题,还得在ILM策略中设置,满足创建时间超过360个小时的索引,副本直接调整为0,但是客户又不接受,没办法。
在场景5和6中,介绍了10w个分片会给集群带来的影响和通过开启shrink来降低分片数量,但是仍然有两个需要重点解决的问题:
可以估算一下,按小时建索引,60分片1副本,一年的分片数为24*120*365=1051200个分片,执行shrink后分片数量24*10*350 + 24*120*15 = 127200(15天内的新索引为了保障写入性能和数据可靠性,仍然保持60分片1副本,旧的索引shrink为5分片1副本), 仍然有超过10w个分片。结合集群一年总的存储量和单个分片可以支持的数据量大小进行评估,我们期望集群总体的分片数量可以稳定为6w~8w,怎么优化?
可以想到的方案是执行数据冷备份,把比较老的索引都冷备到其它的存储介质上比如HDFS,S3,腾讯云的COS对象存储等,但是问题是这些冷备的数据如果也要查询,需要先恢复到ES中才可查,恢复速度比较慢,客户无法接受。由此也产生了新的想法,目前老的索引仍然是1副本,可以把老索引先进行冷备份,再把副本调为0,这样做有以下几点好处:
经过和客户沟通,客户接受了上述方案,计划把老索引冷备到腾讯云的对象存储COS中,实施步骤为:
其中步骤1的实施可以通过脚本实现,本案例中采用腾讯云SCF云函数进行实施,方便快捷可监控。实施要点有:
在实施完步骤1之后,就可以批量把对索引进行过备份的索引副本数都调为0, 这样一次性释放了很多磁盘空间,并且显著降低了集群整体的分片数量。
接下来实施步骤2,需要每天执行一次快照,多创建时间较久的索引进行备份,实施比较简单,可以通过crontab定时执行脚本或者使用腾讯云SCF执行。
步骤2实施之后,就可以修改ILM策略,开启cold phase, 修改索引副本数量为0:
此处的timing是创建时间20天后,需要保证步骤2中对过去老索引数据备份先执行完成才可以进入到cold phase.
通过老索引数据冷备并且降低索引副本,我们可以把集群整体的分片数量维持在一个较低的水位,但是还有另外一个问题待解决,也即shrink失败的问题。刚好,我们可以利用对老索引数据冷备并且降低索引副本的方案,来彻底解决shrink失败的问题。
在场景5中有提到,shrink失败归根接地是因为索引的副本数量为1, 现在我们可以吧数据备份和降低副本提前,让老索引进入到ILM的warm phase中时已经是0副本,之后再执行shrink操作就不会有问题了;同时,因为副本降低了,索引从hot节点迁移到warm节点迁移的数据量也减少了一半,从而降低了集群负载,一举两得。
因此,我们需要修改ILM策略,在warm phase就把索引的副本数量调整为0, 然后去除cold phase。
另外一个可选的优化项是,对老的索引进行冻结,冻结索引是指把索引常驻内存的一些数据从内存中清理掉(比如FST, 元数据等), 从而降低内存使用量,而在查询已经冻结的索引时,会重新构建出临时的索引数据结构存放在内存中,查询完毕再清理掉;需要注意的是,默认情况下是无法查询已经冻结的索引的,需要在查询时显式的增加"ignore_throttled=false"参数。
经过上述优化,我们最终解决了集群整体分片数量过多和shrink失败的问题。在实施过程中引入了额外的定时任务脚本实施自动化快照,实际上在7.4版本的ES中,已经有这个功能了,特性名称为 SLM (快照生命周期管理),并且可以结合ILM使用,在ILM中增加了"wait_for_snapshot"的ACTION, 但是却只能在delete phase中使用,不满足我们的场景。
在上述的场景4-7中,我们花费大量的精力去解决问题和优化使用方式,保证ES集群能够稳定运行,支持PB级别的存储。溯本回原,如果我们能有一个方案使得客户只需要把热数据放在SSD盘上,然后冷数据存储到COS/S3上,但同时又使冷数据能够支持按需随时可查,那我们前面碰到的所有问题都迎刃而解了。可以想象得到的好处有:
而这正是目前es开源社区正在开发中的Searchable Snapshots功能,从 Searchable Snapshots API 的官方文档上可以看到,我们可以创建一个索引,将其挂载到一个指定的快照中,这个新的索引是可查询的,虽然查询时间可能会慢点,但是在日志场景中,对一些较老的索引进行查询时,延迟大点一般都是可以接受的。
所以我认为,Searchable Snapshots解决了很多痛点,将会给ES带了新的繁荣!
经历过上述运维和优化ES集群的实践,我们总结到的经验有:
从一开始和客户进行接触,了解客户诉求,逐步解决ES集群的问题,最终使得ES集群能够保持稳定,这中间的经历让我真真正正的领悟到"实践出真知",只有不断实践,才能对异常情况迅速做出反应,以及对客户提的优化需求迅速反馈。
经验谈:ElasticSearch借助AWS缩放至PB规模
我管理着一个规模可观的ElasticSearch集群。多大?嗯,“大”只是相对而言的。确切地说在ElasticSearch数据节点这部分,目前的运行情况如下:
已为数据节点供应数PB存储
数千个Xeon E5 v3内核
十多TB内存
每天索引数十亿事件(24/7/365)
规模还在增长。个别集群的范围从48TB到PB规模不等,这里所说的“PB规模”也包括这样的集群:
从资源的角度来看,对现代化操作系统来说其实不算很大,但对ElasticSearch的世界来说已经不小了。一年多以来(使用ES 0.90.x - 2.2.x版)管理这些系统的过程中进行了无穷无尽的设计,制定各种运维策略,并获得了不少收获,我觉得有必要和大家分享一下。
本文涉及的每个话题都足以单独写一篇博文,或让读者花费数天甚至数周的时间做实验。如果要包含每个领域涉及的各种细节,本文的长度可能超过100页,因此我的目标是介绍在以某种规模运维ElasticSearch的过程中我认为一些比较重要的高层面结论。
本文会假设你已经多次听人说过:ElasticSearch实际上是一种分布式可复制数据库结构的抽象层,这种技术以Apache Lucene为基础,提供了(丰富的)搜索API。这种技术的索引实际上是Lucene实例。副本又是什么。是否要对专用Master使用仲裁(Quorum)等。我更希望分享一些通用的思维模式,例如如何运维,希望这些想法能对你的解决方案有所帮助。
那么,咱们开始吧!
这方面主要围绕两个因素介绍:我有一个索引驱动的工作负载,并且对数据保留有所要求(需要保留数天)。有关Shard数量的讨论往往会归结于妖术或是艺术,但实际情况比这个简单得多((...难道不是吗?)。
基本上,我们可以将Shard看作是一种基本的性能单位,不同时段可能会使用不同的性能描述文件(例如在持续创建索引时,资源需求的增长与合并操作中片段的大小有关,并会在片段大小达到最大值后趋于平稳)。另外取决于具体操作成本也是可预测的(例如大型合并操作会对垃圾回收程序造成极大压力,查询较大规模数据集会更长时间地占用查询线程池,导致查询复杂度增加并需要耗费更多CPU资源等)。最后,每个操作都是在明确的机械量(Mechanical quantity)中进行的。合并、搜索、索引等都需要线程参与。这些线程的数量需要精确定义(并且大部分时候需要可配置)。我们知道可以在每个Shard或每个节点上使用多少线程,而每个类型的操作其资源需求也有不同特征(CPU、磁盘、内存,或这些因素的组合)。这意味着一切都是可度量的。
ElasticSearch或其他任何复杂系统的问题在于,容量规划工作感觉更像是一种妖术或者艺术,因为大部分东西都是不可度量或不可控的。
我们的想法是:度量吧,控制吧。我们的集群是这样做的。
我们的标准数据节点使用了d2.2xlarge实例,这种实例包含8个内核的Xeon E5-2676v3处理器,61GB内存,以及6块2TB直接连接“可能是SATA或近线SAS的”磁盘。磁盘配置为6路LVM条带卷,提供11TB可用存储,(忽略fs和其他开销后)可稳定一致地提供约900MB/s的顺序读写速度*。
*(AWS不会对d2s的存储进行过量提供(Over-provision),整个Hypervisor包含24个主轴(Spindle),可将所有24个主轴分配给最大规模的8xl实例。不同规模下可以二等分访问资源,这样通过2xl便能获得6个磁盘/8个处理器内核/61GB内存。在测试中Hypervisor HBA还支持对所有磁盘同步饱和(Simultaneous saturation),这样用户就可以获得始终如一的性能。)
每个数据节点实际上充当了“性能单位”的“Shard容器”。选择任何一种标准化构建,借此创建单Shard索引并投入运转。随后会发现资源使用率呈现出一致的衰减和流动。举例来说,如果让索引操作满载运行,直到合并后的片段大小达到最大值,此时会看到CPU、存储IO、CPU、存储IO呈现周期性循环。这是片段合并工作进行压缩/解压缩(耗费CPU)以及将新生成的更大的片段保存到磁盘(大量顺序IO)操作造成的。合并操作达到最大化负载后会看到一定数量的处理器核心在满载运行合并线程(如果没有明确定义合并线程池的规模,则可根据CPU的核心数计算。请打开_nodes/_local/hot_threads,在这里可以看到大量[[index-name][shard-num]: Lucene Merge Thread #xxx线程]。触发写入合并结果的操作后会看到磁盘将在一段固定不变的时间内持续满载。合并、写入、合并、写入。索引就是这样做的。
索引比查询更可预测。在indexing/bulk线程队列接近上限(如果需要监控什么的话,也许就应该从_cat/thread_pool着手)或批(Bulk)索引所需的时间实在太久(我更关心这个问题)之前,你可以用某一“每秒文档数”速率(文档的大小和结构也真的非常重要)安全地将内容索引到一个Shard中。在我们的例子里,通过每个Shard每秒大约可以搜索2500篇文档。以这样的速率,合并操作会让3个核心满载(假设为了获得可预测性/伸缩能力,我们将每个Shard的合并线程池限制为3个,下文将提到这个问题),并且偶尔会用3-5秒将新生成的片段写入存储。虽然容量规划也要考虑自己的独特需求,但我的工作负载会优先处理索引任务,这样就可以确保5个核心和一个存储系统不会过载。我还可以在这个节点上另外创建一个Shard,实现每个节点5000文档/秒的速率。我们每个节点的索引容量大致就是这样。
限制为3个合并线程,处于峰值使用率下的一个Shard。hot_threads的输出结果显示在横线下方。
这些数字有可能是编造的,但相关概念是真实的。遵循这些建议而不要陷入有关集群规模或Shard数量的武断思维方式,借此可为每个Shard建立性能基线,这种方式很适合用在我们标准化的构建中。随后需要研究打算运行的工作负载并确定这些工作负载所需的Shard数量。最后还需要确定每台机器想要实现的工作负载密度(使用每台机器可实现峰值利用率的Shard数量控制)。需要多少预留余量(Headroom)才能保证节点有能力继续处理其他操作,例如查询、再平衡(Rebalance)、容错,或复制?我们需要通过计算得出所需Shard数量以及决定节点数量的预留余量需求。
只想实现40000文档/秒的速度,但不想过多考虑查询问题?
40000 / 2500 = 每索引16个Shard / 每节点2个Shard = 8 个节点
想要复制?直接将数量翻倍,使用16个节点就行了(不过伸缩并不是完全线性的,还有一些额外的内存和网络开销)。
说实在的,这种做法大部分情况下都可行。通过这样的计算进行估算可以进行粗略的伸缩,随后可以根据所获得的性能特征对伸缩进行微调(例如不同集群的查询性能各异)。我们已经成功地从500文档/秒的集群伸缩至超过150K的速度。
举例来说,如果之前每节点2个Shard的例子在满载的情况下可以实现5000文档/秒的速度,在对达到最大值的片段进行合并(外加大量线程开销*)时,我们最终让6个核心满负荷运行。空闲的2个核心足够处理查询负载吗?也许可以。但如果你希望频繁针对50GB索引数据执行复杂的正则表达式查询那肯定是无法满足的。
更高工作负载密度,更低预留容量:
相同工作负载在密度更低,预留容量更多情况下的表现:
一个重要问题:索引对资源的实际需求取决于很多因素。为了看到上文描述的行为,可能需要对ES的默认值进行大量有针对性的调整。正如上文所述,我的工作负载会优先处理索引。出于性能和伸缩性的考虑,将事件保存到磁盘中并让合并操作尽可能快速的运行,这样的做法更合理。我的资源需求符合这样的模式,但这是特意安排的,主要是因为index.translog.durability被配置为'async',而indices.store.throttle.type被配置为'none',此外我更倾向于数量更少但规模更大的批请求(Bulk request)以及更大量的事件。如果使用默认值,使用更小的事件、更多的请求以及更大的并发性,CPU可能会因为太多线程而严重超载,并且需要使用截然不同的伸缩模式。
容量规划工作的目标以及随之而来的集群规格可以帮助我们理解ElasticSearch的通用结构以及它与资源的关系和对资源的争夺。从我的观点看来,至少需要建立每个Shard的性能基线,并构建足够大规模的集群以提供所需数量的“伸缩单位”,并为其他操作提供足够的预留容量。虽然这只是一种想法但依然行之有效,不过你依然需要使用自己的数据进行测试。
可惜这些只是最简单的部分,如果只涉及ElasticSearch的一个功能(例如索引),这样做可以获得不错的效果。但如果需要包含其他用户和集群活动,例如查询、Shard再平衡,以及更长的GC暂停,容量问题将变得更为复杂。上文提到的“其他操作的预留容量”才是最困难的部分,而这个问题最有效的解决方法是对输入进行控制,下文将重点介绍这部分内容。
ElasticSearch稳定性的关键在于对输入的控制。虽然在下文的度量一节我会提到为了应对已知的输入量,例如索引进行的容量规划,但除了索引还有必要妥善管理所有操作的资源需求。否则规格的确定将没有任何意义,除非你才刚刚起步(并且完全不差钱)。
我觉得有3个主要类别的控制需要考虑:
索引
查询
常规的集群操作(Shard复制、再平衡等)
下文将按顺序介绍。
对于索引:我们使用了自行开发的索引服务。我认为索引程序一定要能配置或自行调整速率限制(可以由索引程序自行处理或在上游使用调节服务),并要能对批处理索引的参数进行控制(例如批的大小、超时写入触发器等)。你可能已经听说过批(Bulk)索引的使用,这样做是没错的。另外我还想补充一个可能被很多人忽略的重点:集群中索引程序可以处理的未决(Outstanding)索引请求的总数。如果按照容量规划一节的建议进行配置,每个节点都会对活跃的和队列中的批线程有所限制,未决索引请求的总数不能超过这个限制。
如果要构建一个速度达到60K文档/秒的集群,即可将索引程序的速率限制为60K,如果总数据摄入量超过ES的容量规格,可将上游请求加入队列(当然还要喝杯咖啡然后着手伸缩集群)。索引操作的控制非常重要,但我发现很多人会忽略这个问题,只是一味地投入更多资源。
上一节有关性能的话题中提到了批线程的成本:虽然需要决定批请求的速率和规模,但我更愿意在需要从索引程序中获得的性能和ES数据节点的批线程利用率之间进行权衡。举例来说,如果一切组件都扩展到最高规格,在不影响整体性能的情况下能将索引程序的请求速率回缩到什么程度,这样就可以重新获得ES集群的部分CPU资源。
对于查询:基本是相同的想法。我们使用了自行开发的查询服务,可以像处理索引那样处理查询。索引和查询都是需要占用大量资源的工作负载,对吧!我们还为并发查询的查询队列设置了TTL。这里需要注意的是,在ElasticSearch改进查询运行控制能力之前,如果要提供给外部用户,单一查询实际所能造成的影响可以认为几乎是不可确定的。除非从语句的构成方面阻止这种做法,目前几乎无法阻止用户提交那种可能需要1100个核心满负荷运行40分钟,随后还需要进行大量垃圾回收的查询。但另一方面,查询速率和并发性至少是一种可行的控制点。
对于集群活动:我觉得这一点非常好理解。在将ElasticSearch应用于生产环境的早期阶段(大概是0.90版的时候),会由一些问题造成非常奇怪的服务中断,例如初始状态下主要Shard和可用数据节点之间的失衡。一个数据节点可能达到非常高的使用率,导致Shard针对比较老的索引进行再平衡,但我并未对这些问题采取措施。结合在一起后翻倍的索引负载(在设计上并不针对这种一个节点包含两个主副本的情况)以及Shard的迁移会导致GC彻底暂停并超出集群的超时值,使得节点从集群中退出。
类似的情况我还见过很多,大部分都可以用一行代码解决。挺好笑的对吧:
最实际的解决方法还是对这种行为本身进行控制。很多控制可通过修改设置(例如主要Shard的隔离权重,[但我发现这个功能已经被废弃了])的方式实现,其余部分可以通过我开发的一个名为Sleepwalk的设置调度器服务加以解决。这样我们就可以根据时间安排短暂应用某种集群设置。例如“早九点到晚九点之间允许进行0次再平衡,并对传输速率进行更大程度的限制,其他时间不进行任何限制”。
由于大部分工作负载存在波动的情况,白天高夜间低,很容易就可以建立相应的索引生命周期。我会在索引峰值时期限制常规的集群操作,使用其他工具对索引进行优化和复制(下文详述),随后放开了让集群执行需要的任何操作:
上图中我们允许以几乎不限制传输速率的方式进行5个并发的Shard再平衡。随后当“Sleepwalk运行结束”后,任务也已完成,索引速率重新恢复正常。
当然了,如果打算将集群活动作为一种会造成负载的“输入”进行控制,你会发现对一些东西的监控同样重要,例如Shard的移动或复苏,这一点与GC和存储利用率的做法没什么区别。这些东西一定要监控,追踪Shard的移动/状态能为你提供极大的帮助。
经过一些快速简单的调整,这种方式起到了很好的效果,并成为我们的第三个控制点。实施所有这些措施(并额外用大量时间对不同类型的实例、存储以及集群技术进行测试和调优,JVM实在是太蠢了)后,我们的系统在性能和稳定性方面有了巨大的飞跃。
你可能依然有些想法。“那么我是否该用固态硬盘?”、“是否需要给页面缓存提供128GB内存?”
我不习惯在不了解具体情况的时候针对性能问题给出笼统的建议。为什么?引用一个别人的说法吧:
说“机械硬盘太慢了”,就好像是在说“金属太重了,浮不起来”,结论是不应该用金属造船。
上下文情境和具体规格很重要。按照“搜索x必须在100ms内返回结果”或者“最大尺寸的片段写入操作花费的时间不能超过5秒”这样的方式思考一下需求吧。在开始设计系统时,需要考虑不同组件如何帮你满足这些需求。
当然对于固态硬盘这个例子,无论如何其延迟(当然还有顺序读写IO)都会比机械硬盘出色很多。但这真的重要吗?用我的工作负载为例,完全由索引驱动,数据的保留意味着一切。200TB的ElasticSearch数据恰巧是花大钱或者花小钱的分界线。我们的文档相对来说比较大,每个约2KB+,索引(lz4解压缩/压缩周期,或ES 2.x中的DEFLATE)时会让内核满载。如果用标准的服务器运行索引并让每个内核都满负荷运行,这一过程中磁盘大部分时候都是闲置的:满负荷执行大量顺序写入,闲置,满负荷,闲置… 如果使用固态硬盘作为存储,也许会将写入时间从5秒缩短至2秒,但我真的在意这种改进吗?更何况数据保留期限是我接下来要考虑的问题,就算让存储成本提升数倍,但从可预料的用户体验标准来看,完全无法获得任何实际收益。
你可能还会问到查询。我会考虑一下对延迟容忍度的最高值和平均值(SLO到底是什么呢?)。以我的ES集群作为后端的应用所查询的问题与你使用基于Hadoop的应用查询的问题差不多,因此5秒的查询响应速度已经相当“快”了(一个集群提供超过70GB/s的顺序读取吞吐率总量,这已经很酷很实用啦)。大部分非常长的查询响应通常都涉及复杂的正则表达式,这种查询更依赖CPU而非存储。尽管如此,我也试过最大的i2.2-8xlarge实例。基本上表现不过如此,绝对不值得我们使用,存储和CPU的性能完全失衡了。在使用EBS优化渠道和GP2存储的情况下,索引性能甚至不如c3实例,这主要是文档大小和所进行的lz4操作的数量导致的。但i2也许是最适合你的实例。
但也不要误会我有关硬件的说法。个人而言,我家的所有设备都在使用固态硬盘,闪存实在是太赞了。但实际上,通常来说别人付你钱是为了让你给他们设计系统,而不是让你自己觉得赞。我的建议是只需要考虑系统本身的设计即可,不需要为一些泛化的概念花冤枉钱。
最终归纳来说,您可能需要更多CPU资源,大部分人可能最开始就猜错了。AWS的c4和d2实例是最实用的。如果数据量不大,预算充足或者对查询延迟很敏感,那么就用能买得起的最快速的存储吧。
通用的集群设计中还有一个因素需要考虑,必须让数据节点的规格能够满足跨集群传输对最短时间的要求。所有负责构建系统的人都需要记住一个数字:理想情况下,每1Gb网络吞吐率所能实现的数据移动速度理论最大值是439GB/小时。如果你觉得在1Gb带宽的情况下给每个节点存储20TB数据是一种很好的做法,因为能够满足对索引/查询的带宽需求,还能节约机柜空间,那这就是你自找的。如果某个节点故障需要从中撤出整个数据集,你觉得花费数天时间才能让集群恢复,这种状况会有什么后果?
在我们设计的更大规模的集群中,还需要把存储密度和存储总量与集群聚合存储吞吐率,以及对等网络带宽等特性放在一起进行权衡并作为一个考虑因素。我会考虑类似“如果我必须重新复制这20个索引,而每个索引都存储了300TB数据,我能接受在多久时间里完成?”这样的问题。
很多情况下可靠性与容量规划非常类似,并且它们之间绝对是有关系的。我将谈到可靠性的两个子类:稳定性和耐久性。
对于稳定性这个词,其实上文已经介绍过了(合适的规模以及对输入进行控制)。我觉得除此之外能对稳定性产生最大影响的因素应该是查询的复杂度和数据量。在我看来查询工作负载可以分为两类:
数量多但规模小
数量少但规模大
数量多但规模小是指那些“每秒钟处理xxxxx千个查询”的应用,但查询的复杂度以及时间范围(或者说数据集的范围)相对来说较小。数量小但规模大是指我所要处理的:“咱们用这250亿个文档做一次7层的正则表达式Dip吧”这种情况。针对非常大的数据集执行非常复杂的查询所产生的最大影响在于会耗尽CPU资源,并且随后会产生大量GC操作造成持续的压力。如果无法从语句结构上避免此类问题(且假设真有必要执行如此“狂野”的查询),此时最佳的做法可能是使用上文提到的查询调节方法,并严格限制并发的活跃查询。
如果你真的想要伸缩至更大量的复杂查询或实现非常高速率的小规模查询,通常可以实施多种类型的阶梯式集群节点体系结构。举例来说,如果索引量相对较低,但查询量相对较大,可以构建索引节点池(通过标签定义),为查询节点定义一个单独的池,并准备好所需数量的副本。这样就可以将副本节点视作独立于查询的一批容量(核心,堆(Heap)内存)。需要更多查询容量?将查询池从240核心/960GB的堆扩展至360核心1.44TB的堆,并继续准备相应数量的副本吧。
对于耐久性,ElasticSearch所提供的最主要的控制功能是Shard复制。只要无需考虑银河系会遭受类似拜占庭帝国那样的毁灭,副本策略远不如性能和总容量之类的因素重要。考虑到默认配置,我猜大部分人会使用每个主节点1个副本的默认设置对索引进行拆分。这样做对成本造成的最大影响是存储用量翻倍。此外有个不太容易发现的问题:相比写入非复制的索引,保存到复制的索引位置所能实现性能(明显)差很多。在进行度量之前还有一个不太容易发现的问题:使用复制后的性能一致性很不规则,至少在我看来是这样的。索引延迟和资源用量的波动范围远远超过写入非复制索引时的情况。这个问题重要吗?也许重要,但也许无所谓吧。
但这也让我产生了一些想法。听起来挺疯狂,但你也应该问问自己:“我真的需要对活跃索引进行复制吗?”。如果不是非常必要,不进行复制的效果其实更好。这样集群重启动的速度真的非常快(为了改善恢复机制,这个特性在新版ElasticSearch中的效果大幅下降)并且每个节点都能提供非常高的索引性能。非复制索引实现更可预测的一致性能,这也意味着我可以在更接近预留容量/闲置容量的水平下运行数据节点,而无须担心用量突然激增并抢占其他工作负载的资源。听起来有点危险是吧?当然危险了。其实可以轮换活跃索引并对刚刚停止的索引进行复制,借此建立完善的重放机制(Replay mechanism)和检查点。如果有节点下线,只需要挪用其他索引并接手后续的写入操作,为受影响的索引分配失去的Shard并重放数据。这样即可在后台自行恢复原先的索引。
类似的,你还应该问问自己:“数据变老后是否会失去价值?”。如果会,就无需在索引的整个生命周期内进行复制。集群外存储(例如S3或你自己数据中心内的大容量“温”存储)每GB成本可能更低。如果索引需要保存90天,但95%的查询针对命中的都是最近7天的数据,可以考虑将快照发送至集群之外,并停止对老旧索引的复制。如果能够配合SLO使用,将能大幅降低大集群的痕迹。
最后,这些话题大部分都与Shard的数量有关。但是需要提醒大家注意,Shard不是免费的(哪怕其中并未保存数据),而每个Shard都会占用集群的部分空间。其中保存的元数据主要是集群中每个节点的心跳信号,另外在一些Shard或节点数非常大的集群中我还遇到过有关可靠性的奇怪问题。在考虑任何问题时,请尽量将所用Shard数量降至最低。
大家都喜欢对分布式数据库进行升级。我觉得我会首先问自己团队一个(挺奇怪)的问题:“轮流重启动需要多长时间?我们的集群需要好几天!”。天呐,这个过程慢到离谱我也是醉了。
选项1很明显。如果完全不关心SLA,你可以安排一个停机窗口,执行全面的停止>升级>启动,这个过程大概需要3分钟(这个过程我说得很简单,但实际上还需要进行一些准备工作以及其他运维任务)。
选项2,如果活在现实世界里并以零停机时间为目标,我其实并不喜欢这种轮流升级/重启动的方法。需要执行大量数据移动、协调等工作并要付出大量时间。选项2需要:
1.将新节点接入集群(可将再平衡设置为0,或将新节点从分配中排除在外,这样再平衡过程才不会影响后续操作)。
2.选择一个分配给新节点的新索引,开始将新的传入数据写入这个索引。
3.随后设置排除老索引,并对再平衡进行调节(可使用cluster.routing.allocation.node_concurrent_recoveries和indices.recovery.max_bytes_per_sec设置)。
4.源节点的数据排空后将其移除。
新节点可能使用了全新配置的新版本ElasticSearch(但最好不要跨越大版本这样做;)),甚至可能是恢复操作的一部分(我们并没有试着修复节点;在第一次遇到这个故障后我们只是给集群新增了一个节点,并在故障节点上设置了排除)。
顺利完成这一切后的结果实在是不可思议。新的节点逐渐开始根据node_concurrent_recoveries和recovery.max_bytes_per_sec的设置运转(或使用Sleepwalk调度),等弃用的节点清空并删除就行了。
ElasticSearch的使用诀窍有很多,但我必须得说这也许是最强大同时也最简单的一个。就我个人来看,这也是不停机多次移动数PB规模ES的最佳方式。
希望本文能在系统体系和各种喧嚣的意见之间提供一种有趣的平衡。借此也可以管窥我所从事的一个炫酷的项目,我觉得我应该尽量从较高层次来介绍,原因有两个。
首先,出于一些原因我很少喜欢那种需要通过撰写“深度文”来探讨的ElasticSearch话题,所以通常我根本不会考虑写这些内容。其次,过去两年来我从事过一些非常大型的项目,这些项目有很多人的参与。我觉得需要通过一篇酷酷的故事向那些参与者表达感谢和称赞:
Chris作为项目经理截至目前也在积极参与我们的每次讨论,甚至经常在凌晨1点还在修复集群遇到的各种问题。负责项目推进的Nathan组建了一个只包含两人的团队(没错,他们也很重要),基本上是在通过一个附带项目(Side-project)管理着PB规模的ElasticSearch项目团队。负责运维/财务事项的Brad对项目组内部的沟通和实现起到了巨大的促进作用。Shaun帮助开发了很多本文没有涉及的自动化工具。Justin、Eric,以及Bill开发了我们的ElasticSearch索引/搜索客户端,经常面对各种艰巨的难题加班到深夜。
当然还要感谢Antoine Girbal、Pius Fung以及ElasticSearch团队其他对ElasticSearch的开发做出贡献的成员。最棘手的ElasticSearch技术问题他们也能非常快速地做出响应,这一点真是举世无双。另外这几位的办公室也非常有趣 ;)。
大数据杂谈
ID:BigdataTina2016
▲长按二维码识别关注
专注大数据和机器学习,
分享前沿技术,交流深度思考。
欢迎加入社区!
以上是关于PB级大规模Elasticsearch集群运维与调优实践的主要内容,如果未能解决你的问题,请参考以下文章
PB 级大规模 Elasticsearch 集群运维与调优实践