腾讯 PB 级大规模 Elasticsearch 集群运维与调优实践
Posted 高效运维
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了腾讯 PB 级大规模 Elasticsearch 集群运维与调优实践相关的知识,希望对你有一定的参考价值。
背景
某中型互联网公司的游戏业务,使用了腾讯云的 Elasticsearch 产品,采用 ELK 架构存储业务日志。
因为游戏业务本身的日志数据量非常大(写入峰值在 100w qps),在服务客户的几个月中,踩了不少坑,经过数次优化与调整,把客户的 ES 集群调整得比较稳定,避免了在业务高峰时客户集群的读写异常,并且降低了客户的资金成本和使用成本。
场景 1:与客户的初次交锋
场景 2:集群扛不住压力了
存储容量:要考虑索引副本数量、数据膨胀、ES 内部任务额外占用的磁盘空间(比如 segment merge)以及操作系统占用的磁盘空间等因素,如果再需要预留 50%的空闲磁盘空间,那么集群总的存储容量大约为源数据量的 4 倍
计算资源:主要考虑写入,2 核 8GB 的节点可以支持 5000qps 的写入,随着节点数量和节点规格的提升,写入能力基本呈线性增长
索引和分片数量评估:一般一个 shard 的数据量在 30-50GB 为宜,可以以此确定索引的分片数量以及确定按天还是按月建索引。需要控制单节点总的分片数量,1GB 堆内存支持 20-30 个分片为宜;另外需要控制集群整体的分片数量,集群总体的分片数量一般不要超过 3w。
场景 3:logstash 消费 kafka 性能调优
-
增加多台 logstash 消费 kafka 数据,消费速度没有线性提升 -
kafka 的不同 topic 消费速度不均匀、topic 内不同 partition 消费的速度也不均匀
-
topic 的 partition 数量少,虽然 logstash 机器数量多,但是却没有充分利用机器资源并行消费数据,导致消费速度一直上不去 -
所有 logstash 的配置文件都相同,使用一个 group 同时消费所有的 topic,存在资源竞争的问题
分析后,对 kafka 和 logstash 进行了如下优化:
-
提高 kafka topic 的分区数量 -
对 logstash 进行分组;对于数据量较大的 topic,可以单独设置一个消费组进行消费,有一组 logstash 单独使用这个消费组对该 topic 进行消费;其它的数据量较小的 topic,可以共用一个消费组和一组 logstash -
每组 logstash 中总的 consumer_threads 数量和消费组总的 partion 数量保持一致,比如有 3 个 logstash 进程,消费的 topic 的 partition 数量为 24, 那么每个 logstash 配置文件中的 consumer_threads 就设置为 8
whose size is larger than the fetch size 4194304 and hence cannot be ever returned. Increase the fetch size on the client (using max.partition.fetch.bytes), or decrease the maximum message size the broker will allow (using message.max.bytes)
场景 4:磁盘要满了,紧急扩容?
1.GET _cluster/health
发现集群健康状态是 green,但是有大约 6500 个 relocating_shards, number_of_pending_tasks 数量达到了数万。
发现大量的”shard-started”任务在执行中,任务优先级是”URGENT”, 以及大量的排在后面的”put mapping”的任务,任务优先级是”HIGH”;”URGENT”优先级比”HIGH”优先级要高,因为大量的分片从旧的节点迁移到新的节点上,造成了索引创建的任务被阻塞,从而导致写入数据失败。
为什么会有这么多的分片在迁移中?通过 GET _cluster/settings 发现”cluster.routing.allocation.node_concurrent_recoveries”的值为 50,而目前有 130 个旧节点在把分片迁移到 130 个新节点中,所以有 130*50=6500 个迁移中的分片。而”cluster.routing.allocation.node_concurrent_recoveries”参数的值默认为 2,应该是之前在执行纵向扩容集群时,为了加快分片迁移速度人为修改了这个值(因为集群一开始节点数量没有很多,索引同时迁移中的分片也不会太多,所以创建新索引不会被阻塞)。
4.PUT _cluster/settings
现在通过 PUT _cluster/settings 把”cluster.routing.allocation.node_concurrent_recoveries”参数修改为 2。但是因为”put settings”任务的优先级也是”HIGH”, 低于”shard-started”任务的优先级,所以更新该参数的操作还是会被阻塞,ES 报错执行任务超时。此时,进行了多次重试,最终成功把”cluster.routing.allocation.node_concurrent_recoveries”参数修改为了 2。
现在通过 GET _cluster/health 看到迁移中的分片数量在逐渐减少,为了不增加新的迁移任务,把执行数据迁移的 exclude 配置取消掉:
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.exclude._name": ""
}
}
同时调大分片恢复时节点进行数据传输的每秒最大字节数(默认为 40MB),加速存量的分片迁移任务的执行:
PUT _cluster/settings
{
"transient": {
"indices": {
"recovery": {
"max_bytes_per_sec": "200mb"
}
}
}
}
等几百个迁移中的分片都执行完毕后,新建索引就比较快了,也不会再写入失败了。但是问题是当前正在执行云盘节点切换为本地盘的流程,需要把数据从旧的130个节点上迁移到新的130个节点上,数据迁移的任务不能停,那该怎么办?既然新创建索引比较慢,那就只好提前把索引都创建好,避免了在每个整点数据写入失败的情况。通过编写python脚本,每天执行一次,提前把第二天的每个小时的索引创建好,创建完成了再把”cluster.routing.allocation.exclude._name”更改为所有的旧节点,保证数据迁移任务能够正常执行。
总量 400TB 的数据,大约经过 10 天左右,终于完成迁移了。配合提前新建索引的 python 脚本,这 10 天内也没有出现写入失败的情况。
-
分片数量过多时,如果同时进行迁移的分片数量过多,会阻塞索引创建和其它配置更新操作,所以在进行数据迁移时,要保证”cluster.routing.allocation.node_concurrent_recoveries”参数和”cluster.routing.allocation.cluster_concurrent_rebalance”为较小的值。 -
如果必须要进行数据迁移,则可以提前创建好索引,避免 ES 自动创建索引时耗时较久,从而导致写入失败。
场景 5:10 万个分片?
在稳定运行了一阵后,集群又出问题了。
XX 公司运维 B: bellen, 昨晚凌晨 1 点钟之后,集群就没有写入了,现在 kafka 里有大量的数据堆积,麻烦尽快看一下?
通过 cerebro 查看集群,发现集群处于 yellow 状态,然后发现集群有大量的错误日志:
{"message":"blocked by: [SERVICE_UNAVAILABLE/1/state not recovered / initialized];: [cluster_block_exception] blocked by: [SERVICE_UNAVAILABLE/1/state not recovered / initialized];","statusCode":503,"error":"Service Unavailable"}
登陆到挂了的 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, 让大量副本分片恢复的任务尽快结束,保证新索引能够正常创建,从而使得集群能够正常写入。
-
可以在 ILM 的 warm phase 中开启 shrink 功能,对老的索引从 60 分片 shrink 到 5 分片,分片数量可以降低 12 倍; -
业务可以把每小时创建索引修改为每两个小时或者更长,可以根据每个分片数量最多支持 50GB 的数据推算多长时间创建新索引合适; -
对老的索引设置副本为 0,只保留主分片,分片数量能够再下降近一倍,存储量也下降近一倍; -
定期关闭最老的索引,执行{index}/_close。
场景 6:有点坑的 ILM
-
新添加的策略只能对新创建的索引生效,存量的索引只能通过批量修改索引 settings 里的 index.lifecycle.name 执行策略; -
如果一个策略进行了修改,那么所有存量的索引,不管是有没有执行过该策略,都不会执行修改后的策略,也即修改后的策略只对修改成功后新创建的索引生效。比如一开始的策略没有开启 shrink, 现在修改策略内容添加了 shrink 操作,那么只有之后新创建的索引在达到策略触发条件(比如索引已经创建超过 360 个小时)后才会执行 shrink, 而之前的所有索引都不会执行 shrink,此时若想对存量的索引也执行 shrink,只能够通过脚本批量执行了; -
在 warm phase 同时执行索引迁移和 shrink 会触发 es 的 bug, 如上图中的 ILM 策略,索引本身包含 60 分片 1 副本,初始时都在 hot 节点上,在创建完成 360 小时之后,会执行迁移,把索引都迁移到 warm 节点上,同时又需要把分片 shrink 到 5,在实际执行中,发现一段时间后有大量的 unassigned shards,分片无法分配的原因如下:
"deciders" : [
{
"decider" : "same_shard",
"decision" : "NO",
"explanation" : "the shard cannot be allocated to the same node on which a copy of the shard already exists [[x-2020.06.19-13][58], node[LKsSwrDsSrSPRZa-EPBJPg], [P], s[STARTED], a[id=iRiG6mZsQUm5Z_xLiEtKqg]]"
},
{
"decider" : "awareness",
"decision" : "NO",
"explanation" : "there are too many copies of the shard allocated to nodes with attribute [ip], there are [2] total configured shard copies for this shard id and [130] total attribute values, expected the allocated shard count per attribute [2] to be less than or equal to the upper bound of the required number of shards per attribute [1]"
}
{
"index.routing" : {
"allocation" : {
"require" : {
"temperature" : "warm",
"_id" : "LKsSwrDsSrSPRZa-EPBJPg"
}
}
}
}
{
"index.routing" : {
"allocation" : {
"require" : {
"temperature" : "warm",
"_id" : "LKsSwrDsSrSPRZa-EPBJPg"
}
}
}
}
场景 7:自己实现 SLM
上文介绍了 10w 个分片会给集群带来的影响和通过开启 shrink 来降低分片数量,但是仍然有两个需要重点解决的问题:
索引不断新建,如何保证一年内,集群总的分片数量不高于 10w,稳定在一个较低的水位?
ILM 中执行 shrink 可能会导致部分分片未分配以及 shrink 执行失败,怎么彻底解决呢?
可以估算一下,按小时建索引,60 分片 1 副本,一年的分片数为 24120365=1051200 个分片,执行 shrink 后分片数量 2410350 + 2412015 = 127200(15 天内的新索引为了保障写入性能和数据可靠性,仍然保持 60 分片 1 副本,旧的索引 shrink 为 5 分片 1 副本), 仍然有超过 10w 个分片。结合集群一年总的存储量和单个分片可以支持的数据量大小进行评估,我们期望集群总体的分片数量可以稳定为 6w~8w,怎么优化?
可以想到的方案是执行数据冷备份,把比较老的索引都冷备到其它的存储介质上比如 HDFS、S3、腾讯云的 COS 对象存储等。但是问题是这些冷备的数据如果也要查询,需要先恢复到 ES 中才可查,恢复速度比较慢,客户无法接受。由此也产生了新的想法,目前老的索引仍然是 1 副本,可以把老索引先进行冷备份,再把副本调为 0,这样做有以下几点好处:
集群整体分片数量能降低一半;
数据存储量也能降低一半,集群可以存储更多数据;
老的索引仍然随时可查;
极端情况下,磁盘故障引起只有一个副本的索引数据无法恢复时,可以从冷备介质中进行恢复。
经过和客户沟通,客户接受了上述方案,计划把老索引冷备到腾讯云的对象存储 COS 中,实施步骤为:
所有存量的老索引,需要批量处理,尽快地备份到 COS 中,然后批量修改副本数量为 0;
最近新建的索引,采用按天备份的策略,结合 ILM, 修改策略,在 ILM 执行过程中修改索引副本数为 0(ILM 的 warm phase 和 cold phase 都支持设置副本数量)。
其中第一个步骤的实施可以通过脚本实现,本案例中就采用了腾讯云 SCF 云函数进行实施,方便快捷可监控。实施要点有:
按天创建 snapshot,批量备份每天产生的 24 个索引,如果是按月或者更大粒度创建快照,因数据量太大如果执行快照过程中出现中断,则必须全部重来,耗时耗力;按小时创建快照也不适用,会造成快照数量太多,可能会踩到坑。
每创建一个快照,后续需要轮询快照的状态,保证前一个快照 state 为”SUCCESS”之后,再创建下一个快照;因为快照是按天创建的,快照名字可以为 snapshot-2020.06.01, 该快照只备份 6 月 1 号的所有索引。而在检查到 snapshot-2020.06.01 快照执行成功后,然后新建下一个快照时,需要知道要对哪天的索引打快照,因此需要记录当前正在执行哪一个快照。有两种方式记录,一是把当前正在执行的快照日期后缀”2020.06.01”写入到文件中, 脚本通过定时任务轮询时,每次都读文件;另外一种方式是创建一个临时的索引,把”2020.06.01”写入到这个临时索引的一个 doc 中,之后对该 doc 进行查询或者更新。
创建快照时,可以把”include_global_state”置为 false, 不对集群的全局状态信息进行备份。
场景 8:客户十分喜欢的 Searchable Snapshots!
-
只需要更小规模的集群和非常廉价的 COS/S3 对象存储就可以支持 PB 级别的数据量,客户的资金成本非常低; -
小规模的集群只需要能够支撑热索引的写入和查询即可,集群整体的分片数不会太多,从而避免了集群不稳定现象的发生。
总结
-
新集群上线前务必做好集群规模和节点规格的评估 -
集群整体的分片数量不能太多,可以通过调整使用方式并且借助 ES 本身的能力不断进行优化,使得集群总体的分片数维持在一个较低的水位,保证集群的稳定性 -
Searchable Snapshots 利器会给 ES 带来新的生命力,需要重点关注并研究其实现原理
9月25日,GOPS全球运维大会 2020 · 深圳站,近80位演讲大咖,18个名企专场组团来袭~中行、腾讯、阿里、京东、招行、广东移动、神州泰岳,哪个是你的菜?
专场一览⬇️
腾讯金融科技 运营平台技术负责人专家工程师谢海林将分享“磐石双体系:腾讯金融级高可用运维平台实战”的精彩议题,敬请期待。
报名通道
长按二维码,立享 9 折优惠
近期好文:
以上是关于腾讯 PB 级大规模 Elasticsearch 集群运维与调优实践的主要内容,如果未能解决你的问题,请参考以下文章
PB 级大规模 Elasticsearch 集群运维与调优实践