随着腾讯云 Elasticsearch 云产品功能越来越丰富,ES 用户越来越多,云上的集群规模也越来越大。我们在日常运维工作中也经常会遇到一些由于前期集群规划不到位,导致后期业务增长集群规模大了之后带来的各种各样的集群可用性及稳定性问题。
这里列举下其中比较典型的几种集群规划问题:
正所谓磨刀不误砍柴工,只有前期做好充分的集群评估规划工作,后期才能省去大量的运维工作。且能够长期保证集群的高可用和高稳定性。
本文结合我们在给腾讯云 ES 集群日常运维工作中遇到的各种集群问题及总结沉淀的一些运维经验,来介绍下如何规划好集群容量及索引配置,以及所遵循的一些原则和经验。文章作者:吴容,腾讯云Elasticsearch研发工程师。
集群规模的评估主要评估以下三个方面:
第一,计算资源评估,计算资源的评估主要是评估单节点的CPU和内存。
ES的计算资源一般消耗在写入和查询过程,经过总结大量ES集群的运维经验,2C8G 的配置大概能支持 5k doc/s 的写入,32C64G 的配置大概能支撑 5w doc/s的写入能力。
第二,存储资源评估,存储资源的评估主要是评估磁盘的类型及容量大小。
例如ES集群使用什么类型的磁盘,SSD或者高性能云盘。以及每块盘的容量大小,是选择单盘多容量,还是多盘少容量。而对于冷热分离的集群,则默认使用SSD作为热节点,高性能云盘作为温节点。
另外腾讯云ES支持单节点挂载多块云硬盘,且经过性能压测,3块盘相比于1块盘,吞吐量大约有2.8倍的提升。因此如果对写入速度及IO性能要求较高,可选择挂载多块 SSD 磁盘。
ES冷热分离多盘集群示意图
第三,节点数量评估,节点数量的评估主要是评估集群数据节点的数量。
在同等集群性能的情况下,建议优先选择高配置少节点的集群。例如 32C64G*3 节点的集群相比于 8C16G*12 节点的集群,在集群稳定性和扩容的便捷性上都有一定的优势。
因为高配置的集群如果遇到性能瓶颈需要扩容,则只需要横向扩容,即向集群中加入更多同等配置的节点即可;而低配置的集群在扩容节点配置时,则需要纵向扩容。
目前云上的纵向扩容方式有两种:
第一种是滚动重启方式扩容,这对集群稳定性会有一定的影响。
第二种是数据迁移方式扩容,其原理是先向集群中加入同等数量的高配置节点,然后将低配置节点上的数据迁移到新节点上,最后再将低配置节点剔除集群,所以这种扩容流程时间会比较长,且成本较高。
数据迁移方式纵向扩容示意图
集群规模评估主要根据以下三点来评估:
这里结合我们的运维经验,给出集群规模评估的几点参考建议:
索引配置的评估主要评估两点:
第一,如何划分索引?
在使用 index 时,建议做好定期切换索引的计划。对于日志场景来说,写入不大的情况下建议按天创建索引,而写入较大的情况下,则建议按小时创建索引。
定期滚动索引的好处主要包括:能够控制单个索引的大小,提升读写性能;同时能够方式单个索引太大,影响故障恢复的时间;另外也能避免热索引过大,从而影响快照备份恢复的时间。
第二,如何设置索引主分片数?
云上的索引主分片数默认是5个,具体的大小则需要业务根据具体的场景及数据量来优化。下面会给出具体的一些准则和经验。
索引配置的评估同样也要结合具体的业务场景及索引的数据量来评估,尤其是单日新增的数据量。
索引配置的评估可根据下面几点准则进行评估:
特别需要说明的是集群分片总数的大小控制上,我们经过一些性能测试发现:当集群的总分片数超过 10w 个以后,创建索引时间会增长到分钟级。
尤其是对于写入量在百万 qps 以上的集群,如果总分片数在 10W+,且索引是自动创建的,那么就经常会在每次切换新索引时候,出现写入陡降、集群不可用的情况。
下面这张图是云上一个 100个节点,总分片数在 11W+ 的集群。每天 8点切换新索引时,写入直接掉0,集群不可用时间在数小时不等。
集群每天8点写入性能受到影响
对于这种问题,我们腾讯云ES团队也有一些非常成熟的优化方案。
其中对于每天八点切换新索引时写入陡降的问题,可通过提前创建索引来解决,且建议使用固定的 index mapping,避免大量的 put-mapping 元数据更新操作。因为对于这种节点数量和总分片数量都很大的集群来说,更新元数据是一个非常消耗性能的操作。
对于总分片数超过 10W 的问题,这种一般在日志分析场景中较为常见,如果历史数据不是很重要,则可定期删除历史索引即可。
而对于历史数据较为重要,任何数据都不能删除的场景,则可通过冷热分离架构+索引生命周期管理功能,将7天之前的数据存储到温节点,且在索引数据从热节点迁移到温节点时,通过 Shrink 来将主分片个数降低到一个较小的值,并且可将温节点数据通过快照方式备份到腾讯云COS中,然后将温节点上索引的副本设置为0,这样便可进一步降低集群中的总分片数量。
冷热分离+ILM+COS备份集群架构
ES集群的写入性能受到很多因素的影响,下面是一些写入性能方面的优化建议:
索引中每一个 doc 都有一个全局唯一的 doc_id,这个 doc_id 可自定义,也可以让ES自动生成。
如果自定义的话,则ES在写入过程中会多一步判断的过程,即先Get下该 doc_id 是否已经存在。如果存在的话则执行 Update 操作,不存在则创建新的 doc。
因此如果我们对索引 doc_id 没有特别要求,则建议让ES自动生成 doc_id,这样可提升一定的写入性能。
这一条优化建议在上面也提到了,因为创建索引及新加字段都是更新元数据操作,需要 master 节点将新版本的元数据同步到所有节点。
因此在集群规模比较大,写入qps较高的场景下,特别容易出现master更新元数据超时的问题,这可导致 master 节点中有大量的 pending_tasks 任务堆积,从而造成集群不可用,甚至出现集群无主的情况。
更新集群元数据超时
集群大量pending_tasks任务堆积
ES默认的 refresh_interval 是1s,即 doc 写入1s后即可被搜索到。
如果业务对数据实时性要求不高的话,如日志场景,可将索引模版的 refresh_interval 设置成30s,这能够避免过多的小 segment 文件的生成及段合并的操作。
越来越多的外部客户正选择将自建的ES集群迁移到腾讯云上来,客户通常是使用 logstash 来迁移数据,由于自建集群中完整保留了数据,因此这时候可以将云上的正在写入的索引副本设置为0, 这样可最快完成集群迁移工作。数据迁移完成后再将副本打开即可。
ES为了提升写入性能,提供了 Bulk 批量写入的API,通常客户端会准备好一批数据往ES中写入,ES收到 Bulk 请求后则根据routing 值进行分发,将该批数据组装成若干分子集,然后异步得发送给各分片所在的节点。
这样能够大大降低写入请求时的网络交互和延迟。通常我们建议一次Bulk的数据量控制在10M以下,一次Bulk的doc数在 10000 上下浮动。
ES Bulk请求示意图
上面我们提到ES提供了Bulk接口支持将数据批量写入到索引,虽然协调节点是异步得将数据发送给所有的分片,但是却需要等待所有的分片响应后才能返回给客户端,因此一次Bulk的延迟则取决于响应最慢的那个分片所在的节点。这就是分布式系统的长尾效应。
因此,我们可以自定义 routing 值,将一次Bulk尽量转发到较少的分片上。
POST _bulk?routing=user_id
自定义routing
云上目前提供多种类型的磁盘可用选择,其中1T的 SSD 云盘吞吐量为 260M/s,高性能云盘为 150M/s。因此使用SSD磁盘对于写入性能和IO性能都会有一定的提升。
另外腾讯云现在也提供了多盘的能力,相对于单盘节点来说,3块盘的吞吐量大约有2.8倍的提升。
我们知道ES的索引有三种状态,分别是 Open状态、Frozen状态和 Close状态。如下图所示:
ES索引的三种状态
Open状态的索引由于是通过将倒排索引以FST数据结构的方式加载进内存中,因此索引是能够被快速搜索的,且搜索速度也是最快的。
但是需要消耗大量的内存空间,且这部分内存为常驻内存,不会被GC的。1T的索引预计需要消耗2-4GB的JVM堆内存空间。
Frozen状态的索引特点是可被搜索,但是由于它不占用内存,只是存储在磁盘上,因此冻结索引的搜索速度是相对比较慢的。如果我们集群中的数据量比较大,历史数据也不能被删除,则可以考虑使用下面的API将历史索引冻结起来,这样便可释放出较多的内存空间。
POST /index_name/_freeze
对于冻结索引的搜索,可以在API中指定 ignore_throttled=false 参数:
GET /index_name/_search?ignore_throttled=false
{
"query": {
"match": {
"name": "wurong"
}
}
}
上面介绍了一些较为常见的写入性能优化的建议和经验,但是更为高效的优化还需要结合具体的业务场景和集群规模。
ES集群的健康状态分为三种,分别是Green、Yellow和Red。
我们可以通过下面的API来查询集群的健康状态及未分配的分片个数:
GET _cluster/health
{
"cluster_name": "es-xxxxxxx",
"status": "yellow",
"timed_out": false,
"number_of_nodes": 103,
"number_of_data_nodes": 100,
"active_primary_shards": 4610,
"active_shards": 9212,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 8,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 99.91323210412148
}
其中需要重点关注的几个字段有 status、number_of_nodes、unassigned_shards 和 number_of_pending_tasks。
number_of_pending_tasks 这个字段如果很高的话,通常是由于 master 节点触发的元数据更新操作,部分节点响应超时导致的大量的任务堆积。
我们可以通过下面的API来查看具体有那些 task 需要执行:
GET /_cat/pending_tasks
insertOrder timeInQueue priority source
1685 855ms HIGH update-mapping [foo][t]
1686 843ms HIGH update-mapping [foo][t]
1693 753ms HIGH refresh-mapping [foo][[t]]
1688 816ms HIGH update-mapping [foo][t]
其中 priority 字段则表示该 task 的优先级,翻看 ES 的源码可以看到一共有六种优先级:
IMMEDIATE((byte) 0),
URGENT((byte) 1),
HIGH((byte) 2),
NORMAL((byte) 3),
LOW((byte) 4),
LANGUID((byte) 5);
当集群Red时候,我们可以通过下面的API来查看分片未分配的原因:
GET _cluster/allocation/explain
查看分片未分配的原因
其中 index和shard 列出了具体哪个索引的哪个分片未分配成功。reason 字段则列出了哪种原因导致的分片未分配。这里也将所有可能的原因列出来:
INDEX_CREATED:由于创建索引的API导致未分配。
CLUSTER_RECOVERED :由于完全集群恢复导致未分配。
INDEX_REOPENED :由于打开open或关闭close一个索引导致未分配。
DANGLING_INDEX_IMPORTED :由于导入dangling索引的结果导致未分配。
NEW_INDEX_RESTORED :由于恢复到新索引导致未分配。
EXISTING_INDEX_RESTORED :由于恢复到已关闭的索引导致未分配。
REPLICA_ADDED:由于显式添加副本分片导致未分配。
ALLOCATION_FAILED :由于分片分配失败导致未分配。
NODE_LEFT :由于承载该分片的节点离开集群导致未分配。
REINITIALIZED :由于当分片从开始移动到初始化时导致未分配(例如,使用影子shadow副本分片)。
REROUTE_CANCELLED :作为显式取消重新路由命令的结果取消分配。
REALLOCATED_REPLICA :确定更好的副本位置被标定使用,导致现有的副本分配被取消,出现未分配。
detail 字段则列出了更为详细的未分配的原因。下面我会总结下在日常运维工作中常见的几种原因。
如果未分配的分片比较多的话,我们也可以通过下面的API来列出所有未分配的索引和主分片:
GET /_cat/indices?v&health=red
the node is above the high watermark cluster setting [cluster.routing.allocation.disk.watermark.high=95%], using more disk space than the maximum allowed [95.0%], actual free: [4.055101177689788%]
当我们执行 _cluster/allocation/explain 命令后看到上面的一行语句的话,则可以判断是该索引主分片所在的节点磁盘满了。
解决方法:扩容磁盘提升磁盘容量或者删除历史数据释放磁盘空间。
通常如果磁盘满了,ES为了保证集群的稳定性,会将该节点上所有的索引设置为只读。ES 7.x版本之后当磁盘空间提升后可自动解除,但是7.x版本之前则需要手动执行下面的API来解除只读模式:
PUT index_name/_settings
{
"index": {
"blocks": {
"read_only_allow_delete": null
}
}
}
failure IllegalArgumentException[number of documents in the index cannot exceed 2147483519
该限制是分片维度而不是索引维度的。因此出现这种异常,通常是由于我们的索引分片设置的不是很合理。
解决方法:切换写入到新索引,并修改索引模版,合理设置主分片数。
cannot allocate because a previous copy of the primary shard existed but can no longer be found on the nodes in the cluster
这种情况通常是由于某个节点故障或者由于负载较高导致的掉线。
解决方法:找到节点掉线原因并重新启动节点加入集群,等待分片恢复。
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。