在Elasticsearch集群部署的初期我们可能评估不到位,导致分配的主分片数量太少,单分片的数据量太大,导致搜索时性能下降,这时我们可以使用Elasticsearch提供的Split功能对当前的分片进行拆分,拆分到具有更多主分片的新索引。
而相反的,在数据规模比较大的集群中,可能存在一个数据量很小,但是分片数量非常庞大的索引,而分片的管理依赖于Master节点,一旦分片数量太大,将会降低集群的整体性能,故障恢复也更慢,这时候可以使用Elasticsearch提供的Shrink API降低分片数量。
Elasticsearch提供了Split API,用于将索引拆分到具有更多主分片的新索引。Split API的格式如下:
POST /<index>/_split/<target-index>
PUT /<index>/_split/<target-index>
要完成整个Split的操作需要满足以下条件:
以下API请求可以将索引设置为只读:
curl -X PUT "localhost:9200/my_source_index/_settings?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"index.blocks.write": true
}
}
'
如果当前索引是是一个data stream的写索引,则不允许进行索引拆分,需要对data stream进行回滚,创建一个新的写索引,才可以对当前索引进行拆分。
以下是使用Split API进行索引拆分的请求案例,Split
API支持settings
和aliases
。
curl -X POST "localhost:9200/my-index-000001/_split/split-my-index-000001?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"index.number_of_shards": 2
},
"aliases": {
"my_alias":{}
}
}
'
index.number_of_shards
指定的主分片的数量 必须是源分片数量的倍数。
索引拆分可以拆分的分片的数量由参数index.number_of_routing_shards
决定,路由分片的数量指定哈希空间,该空间在内部用于以一致性哈希的形式在各个 shard 之间分发文档。 例如,将 number_of_routing_shards 设置为30(5 x 2 x 3)的具有5个分片的索引可以拆分为 以2倍 或 3倍的形式进行拆分。换句话说,可以如下拆分:
5→10→30(拆分依次为2和3)
5→15→30(拆分依次为3和2)
5→30(拆分6)
index.number_of_routing_shards
是一个静态配置,可以在创建索引的时候指定,也可以在关闭的索引上设置。其默认值取决于原始索引中主分片的数量,默认情况下,允许按2的倍数分割最多1024个分片。但是,必须考虑主碎片的原始数量。例如,使用5个主碎片创建的索引可以被分割为10、20、40、80、160、320,或最多640个碎片。
如果源索引只有一个主分片,那么可以被拆分成为任意数量的主分片。
我们知道其实大多数的键值存储都支持随着数据的增长实现自动分片的自动扩展。但是为什么Elasticsearch不支持呢?
其实主要是因为Elasticsearch的底层结构和数据分布逻辑决定的,Elasticsearch需要使用一定的哈希的方法找到数据到底应该存放在哪个分片,这就决定了如果新增一个分片,则需要使用不同的哈希方案重新平衡现有的数据,那么整体的操作就会变得非常复杂。
其他键值存储系统解决这个问题的方案一般是使用一致性哈希,当分片数从N增加到N+1时,一致性哈希只需要对1/N的key进行重新分配。但是Elasticsearch分片的本质实际上是Lucene的索引,而从Lucene索引删除一小部分的数据,通常比键值存储系统的成本要高得多。所以Elasticsearch选择在索引层面上进行拆分,使用硬链接进行高效的文件复制,以避免在索引间移动文档。
对于仅追加数据而没有修改、删除等场景,可以通过创建一个新索引并将新数据推送到该索引,同时添加一个用于读操作的涵盖旧索引和新索引的别名来获得更大的灵活性。假设旧索引和新索引分别有M和N个分片,这与搜索一个有M+N个分片的索引相比没有任何开销。
使用Split API进行索引拆分,API正常返回并不意味着Split的过程已经完成,这仅仅意味着创建目标索引的请求已经完成,并且加入了集群状态,此时主分片可能还未被分配,副本分片可能还未创建成功。
一旦主分片完成了分配,状态就会转化为initializing
,并且开始进行拆分过程,直到拆分过程完成,分片的状态将会变成active
。
可以使用_cat recovery API
来监控Split进程,或者可以使用集群健康API通过将wait_for_status
参数设置为黄色来等待所有主分片分配完毕。
对于索引分片数量,我们一般在模板中统一定义,在数据规模比较大的集群中,索引分片数一般也大一些,在我的集群中设置为 24。但是,并不是所有的索引数据量都很大,这些小数据量的索引也同样有较大的分片数。在 elasticsearch 中,主节点管理分片是很大的工作量,降低集群整体分片数量可以降低 recovery 时间,减小集群状态的大小。很多时候,冷索引不会再有数据写入,此时,可以使用 shrink API 缩小索引分配数。缩小完成后,源索引可删除。
shrink API 是 ES5.0之后提供的新功能,他并不对源索引进行操作,他使用与源索引相同的配置创建一个新索引,仅仅降低分片数。由于添加新文档时使用对分片数量取余获取目的分片的关系,原分片数量是新分片倍数。如果源索引的分片数为素数,目标索引的分片数只能为1.
将现有索引缩小为具有更少主分片的新索引,一个索引要能够被shrink进行缩小,需要满足以下三个条件:
为了使分片分配更容易,可以先删除索引的复制分片,等完成了shrink操作以后再重新添加复制分片。
可以使用以下代码,实现删除所有的副本分片,将所有的主分片分配到同一个节点上,并且设置索引状态为只读:
curl -X PUT "localhost:9200/my_source_index/_settings?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"index.number_of_replicas": 0,
"index.routing.allocation.require._name": "shrink_node_name",
"index.blocks.write": true
}
}
'
重新分配源索引的分片可能需要一段时间,可以使用_cat API跟踪进度,或者使用集群健康API通过wait_for_no_relocating_shards参数等待所有分片完成重新分配。
当完成以上步骤以后就可以进行shrink操作了,以下为_shrink API的格式:
POST /<index>/_shrink/<target-index>
PUT /<index>/_shrink/<target-index>
以下案例将索引my-index-000001缩小主分片到shrunk-my-index-000001索引。
curl -X POST "localhost:9200/my-index-000001/_shrink/shrunk-my-index-000001?pretty"
{
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 1,
"index.codec": "best_compression"
"index.routing.allocation.require._name": null,
"index.blocks.write": null
}
"aliases": {
"my_search_indices": {}
}
}
收缩索引API允许您将现有索引收缩为主分片更少的新索引。目标索引中请求的主分片数量必须是源索引中主分片数量的一个因子。例如,包含8个主碎片的索引可以收缩为4个、2个或1个主碎片,或者包含15个主碎片的索引可以收缩为5个、3个或1个主碎片。如果索引中的碎片数量是一个质数,那么它只能收缩为一个主分片。在收缩之前,索引中每个分片的一个(主或副本)副本必须存在于同一个节点上。
如果当前索引是是一个data stream的写索引,则不允许进行索引收缩,需要对data stream进行回滚,创建一个新的写索引,才可以对当前索引进行收缩。
整个索引收缩的过程如下:
虽然Elasticsearch提供了Split和Shrink API,但是更建议的应该是做好更好的索引创建前的评估工作,因为使用Split和Shrink都有一定的成本。