问题背景
某天打开 Jaeger UI 后,发现里面没有任何数据了,这是个奇怪的问题。
然后立马上服务器检查了 jaeger-collector, jaeger-agent, jaeger-query 和 Elasticsearch 的服务进程、端口及网络通信。所有一切都正常。
然后进一步排查数据流向问题,通过排查 jaeger-collector 日志,发现 jaeger-agent -> jaeger-collector 之间的数据传输没有问题。
而 jaeger-collector -> ES 之间数据传输时报错了。错误如下:
{"level":"error","ts":1576483292.2617185,"caller":"config/config.go:130","msg":"Elasticsearch part of bulk request failed","map-key":"index","response":{"_index":"jaeger-span-2019-12-16","_type":"_doc","status":400,"error":{"type":"validation_exception","reason":"Validation Failed: 1: this action would add [10] total shards, but this cluster currently has [992]/[1000] maximum shards open;"}},"stacktrace":"github.com/jaegertracing/jaeger/pkg/es/config.(*Configuration).NewClient.func2\n\t/home/travis/gopath/src/github.com/jaegertracing/jaeger/pkg/es/config/config.go:130\ngithub.com/jaegertracing/jaeger/vendor/github.com/olivere/elastic.(*bulkWorker).commit\n\t/home/travis/gopath/src/github.com/jaegertracing/jaeger/vendor/github.com/olivere/elastic/bulk_processor.go:588\ngithub.com/jaegertracing/jaeger/vendor/github.com/olivere/elastic.(*bulkWorker).work\n\t/home/travis/gopath/src/github.com/jaegertracing/jaeger/vendor/github.com/olivere/elastic/bulk_processor.go:501"}
提取关键错误信息
this action would add [10] total shards, but this cluster currently has [992]/[1000] maximum shards open
根据报错,可以看出,目前集群的shard数量已经是992个,集群最大索引为1000个,将要添加的shard 数量超越了集群管理的最大值,所以数据无法写入。
1000 个shards的限制是怎么来的?
根据官方解释,从Elasticsearch v7.0.0 开始,集群中的每个节点默认限制 1000 个shard,如果你的es集群有3个数据节点,那么最多 3000 shards。这里我们是开发环境,只有一台es。所以只有1000。
ES 基本概念
如果您是Elasticsearch的新手,那么了解基本术语并掌握基本概念非常重要。
Elasticsearch集群的简单图
群集– Elasticsearch群集由一个或多个节点组成,并且可以通过其群集名称进行标识。
节点–一个Elasticsearch实例。在大多数环境中,每个节点都在单独的盒子或虚拟机上运行。
index–在Elasticsearch中,索引是文档的集合。
分片–由于Elasticsearch是分布式搜索引擎,因此索引通常会分为多个元素,这些元素称为分片,分布在多个节点上。Elasticsearch自动管理这些分片的排列。它还会根据需要重新平衡分片,因此用户无需担心细节。
副本–默认情况下,Elasticsearch为每个索引创建五个主要分片和一个副本。这意味着每个索引将包含五个主要分片,并且每个分片将具有一个副本。
分配多个分片和副本是分布式搜索功能设计的本质,它提供了高可用性并可以快速访问索引中的文档。主分片和副本分片之间的主要区别在于,只有主分片才能接受索引请求。副本和主分片都可以满足查询请求。
在上图中,我们有一个Elasticsearch集群,该集群由默认分片配置中的两个节点组成。Elasticsearch会自动在两个节点之间排列五个主要分片。每个主碎片都有一个副本碎片,但是这些副本碎片的排列与主要碎片的排列完全不同。
请记住,number_of_shards值与索引有关,而不与整个集群有关。此值指定每个索引的分片数量(而不是集群中的总主分片)。
副本主要是为了提高搜索性能,用户可以随时添加或删除它们。它们为您提供了额外的容量,更高的吞吐量和更强的故障转移。我们始终建议生产集群具有2个副本以进行故障转移。
解决方案
找到了问题原因,那么如何解决这个问题?
解决这个问题需要回答两个问题:
一,每个 Index 多少个 Shard 合适?
配置 Elasticsearch 集群后,对于分片数,是比较难确定的。因为一个索引分片数一旦确定,以后就无法修改,所以我们在创建索引前,要充分的考虑到,以后我们创建的索引所存储的数据量,否则创建了不合适的分片数,会对我们的性能造成很大的影响。
如果以后发现有必要更改分片的数量,则需要重新索引所有源文档。(尽管重新编制索引是一个漫长的过程,但可以在不停机的情况下完成)。
主分片配置与硬盘分区非常相似,在硬盘分区中,原始磁盘空间的重新分区要求用户备份,配置新分区并将数据重写到新分区上。
稍微过度分配是好的。但是如果你给每个 Index 分配 1000 个Shard 那就是不好的。
请记住,您分配的每个分片都需要支付额外费用:
由于分片本质上是Lucene索引,因此会消耗文件句柄,内存和CPU资源。
每个搜索请求都将触摸索引中每个分片的副本,当分片分布在多个节点上时,这不是问题。当分片争夺相同的硬件资源时,就会出现争用并且性能会下降。
我们的客户期望他们的业务增长,并且其数据集会相应地扩展。因此,始终需要应急计划。许多用户说服自己,他们将遇到爆炸性增长(尽管大多数用户从未真正看到无法控制的增长)。此外,我们所有人都希望最大程度地减少停机时间并避免重新分片。
如果您担心数据的快速增长,那么我们建议您关注一个简单的约束:Elasticsearch的最大JVM堆大小建议约为30-32GB。这是对绝对最大分片大小限制的可靠估计。例如,如果您确实认为可以达到200GB(但在不更改其他基础架构的情况下无法达到更大容量),那么我们建议分配7个分片,或最多8个分片。
绝对不要为从现在起三年后达到的太高的10 TB目标分配资源。
如果现在你的场景是分片数不合适了,但是又不知道如何调整,那么有一个好的解决方法就是按照时间创建索引,然后进行通配查询。如果每天的数据量很大,则可以按天创建索引,如果是一个月积累起来导致数据量很大,则可以一个月创建一个索引。如果要对现有索引进行重新分片,则需要重建索引.
修改默认的 Elasticsearch 分片数
这是正确的改变配置文件中的index.number_of_shards默认值将涉及更改所有节点上的设置,然后理想地按照rolling restarts的指导方针重新启动实例。
但是,如果这不是一个选项,如果在创建新索引时在设置中明确指定number_of_shards并不理想,那么解决方法将使用index templates
可以创建index_defaults默认值,如下所示
PUT /_template/index_defaults
{
"template": "*",
"settings": {
"number_of_shards": 4
}
}
这会将index_defaults模板中指定的设置应用于所有新索引。
重建索引
二,每个节点的 maximum shards open 设置为多大合适?
对于分片数的大小,业界一致认为分片数的多少与内存挂钩,认为 1GB 堆内存对应 20-25 个分片。因此,具有30GB堆的节点最多应有600个分片,但是越低于此限制,您可以使其越好。而一个分片的大小不要超过50G,通常,这将有助于群集保持良好的运行状况。
三,具体措施
我的观点是开发、测试环境,如果数据不那么重要的话,可以清空所有 index
DELETE /_all
然后,重新设置默认值,降低number_of_shards数量,同时提高max_shards_per_node的数量。
vim elasticsearch.yml
# Set the number of shards (splits) of an index (5 by default):
#
index.number_of_shards: 2
# Set the number of replicas (additional copies) of an index (1 by default):
#
index.number_of_replicas: 1
cluster.max_shards_per_node: 3000
对于生产环境
vim elasticsearch.yml
# Set the number of shards (splits) of an index (5 by default):
#
index.number_of_shards: 5
# Set the number of replicas (additional copies) of an index (1 by default):
#
index.number_of_replicas: 2
cluster.max_shards_per_node: 3000
Index 的配置参数在es配置文件里直接配置,会报错。不过可以用Kibana来设置。
打开 console
DELETE /_all
PUT _template/default
{
"index_patterns" : ["jaeger*"],
"order" : 1,
"settings": {
"number_of_shards": "2",
"number_of_replicas": "1"
}
}
PUT _template/default1
{
"index_patterns" : ["*"],
"order" : 0,
"settings": {
"number_of_shards": "5",
"number_of_replicas": "2"
}
}
PUT /_cluster/settings
{
"transient": {
"cluster": {
"max_shards_per_node":10000
}
}
}
order
(Optional,integer) Order in which Elasticsearch applies this template if index matches multiple templates.
Templates with lowerordervalues are merged first.
Templates with higherordervalues are merged later, overriding templates with lower values.
可以发现生效了
$ curl -X GET 'http://127.0.0.1:9200/\_cat/indices?v'
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open jaeger-span-2019-12-17 2DHx3EaGTnKlVC4mefsUJw 2 1 27 0 22kb 22kb
yellow open .kibana SeD9KqnhR7aKpzc2\_AcIDw 2 1 1 0 4.3kb 4.3kb
最重要的是,要定期删除无用数据,比如对于jaeger的数据,可以每个月初删除一个月前的所有数据,即只保留最近1个月的Index数据。
仅保存近30天的数据任务分解为
delete_by_query设置检索近30天数据;
执行forcemerge操作,手动释放磁盘空间。
#!/bin/sh
curl -XPOST "http://127.0.0.1:9200/jaeger-*/_delete_by_query?conflicts=proceed" -H'Content-Type:application/json' -d'{
"query": {
"range": {
"pt": {
"lt": "now-100d",
"format": "epoch_millis"
}
}
}
}
'
force merge API
这里有3个参数可以用
max_num_segments 期望merge到多少个segments,1的意思是强行merge到1个segment
only_expunge_deletes 只做清理有deleted的segments,即瘦身
flush 清理完执行一下flush,默认是true
forcemerge 脚本如下:
#!/bin/sh
curl -XPOST 'http://127.0.0.1:9200/_forcemerge?only_expunge_deletes=true&max_num_segments=1'
有没有更简便的方法?
有,使用ES官网工具——curator工具。
curator 简介
主要目的:规划和管理ES的索引。支持常见操作:创建、删除、合并、reindex、快照等操作。curator 官网地址
curator 适用场景
最重要的是:
仅以删除操作为例:curator可以非常简单地删除x天后的索引。不过前提是:索引命名要遵循特定的命名模式——如:以天为命名的索引:
jaeger-span-2019-09-20
jaeger-span-2019-10-07
jaeger-service-2019-10-11
命名模式需要和action.yml中的delete_indices下的timestring对应。
四,临时提高阈值
通过ES API零时修改
curl -X PUT "dev-jaeger-es01.bj:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
"persistent" : {
"cluster.max_shards_per_node" : "5000"
}
}
'
也可在kibana的tools中改变设置
PUT /_cluster/settings
{
"transient": {
"cluster": {
"max_shards_per_node":10000
}
}
}
参考