前言
ES经典面试题:为什么主分片的数目不能修改?
一、实战演示
新建索引,尝试修改主分片。
DELETE my-index-000001 #创建索引,设置主分配数number_of_shards为4 PUT my-index-000001 { "settings": { "number_of_shards": "4", "number_of_replicas": "0" }, "mappings": { "properties": { "company": { "type": "text", "analyzer": "ik_smart" } } } } #修改主分片数 PUT my-index-000001/_settings { "number_of_shards": "2" }
执行结果:
{ "error" : { "root_cause" : [ { "type" : "illegal_argument_exception", "reason" : "Can't update non dynamic settings [[index.number_of_shards]] for open indices [[my-index-000001/hQKoWSt9SlCiKkiyzMueNQ]]" } ], "type" : "illegal_argument_exception", "reason" : "Can't update non dynamic settings [[index.number_of_shards]] for open indices [[my-index-000001/hQKoWSt9SlCiKkiyzMueNQ]]" }, "status" : 400 }
说明:
索引的主分片数index.number_of_shards的值不能够动态设置,只能在索引创建的时候被指定。
二、路由算法
在回答问题之前,我们先说明ES数据写入过程中的路由算法。
ES的路由算法指的是根据routing和文档id计算目前分片ID shardid的过程。
一般情况下,路由计算方式为下面的公式:
shard_num = hash(_routing) % num_primary_shards
默认情况下,_routing值就是文档id。
ES使用随机id和Hash算法来确保文档均匀地分配给分片。当使用自定义id或routing时,id或routing值可能不够随机,
造成数据倾斜,部分分片过大。在这种情况下,可以使用index.routing_partition_size配置来减少倾斜的风险。
routing_partition_size越大,数据的分布越均匀。
在设置了index.routing_partition_size的情况下,计算公式为:
shard_num = (hash(_routing) + hash(_id) % routing_partition_size ) % num_primary_shards
也就是说,_routing字段用于计算索引中的一组分片,然后使用_id来选择该组内的分片。
index.routing_partition_size取值应具有大于1且小于index.number_of_shards的值。
routing值是一个任意字符串,它默认是_id但也可以自定义,这个routing字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是0到number_of_primary_shards - 1,这个数字就是特定文档所在的分片。这也解释了为什么主切片的数量只能在创建索引时定义且不能修改:如果主切片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。所有的文档API(get、index、delete、bulk、update、mget)都接收一个routing参数,它用来自定义文档到分片的映射。
自定义路由值可以确保所有相关文档.比如用户的文章,按照用户账号路由,就可以实现属于同一用户的文档被保存在同一分片上。
三、GET基本流程
通过GET请求读取单个文档的流程如下:
说明:
客户端向Node1发送读请求
Node1使用文档ID来确定文档属于分片0,通过集群状态中的内容路由表信息获知分片0有三个副本数据,
位于所有的三个节点中,此时它可以将请求发送到任意节点,这里它将请求转发到Node2.
Node2将文档返回给Node1,Node1将文档返回给客户端。
Node1作为协调节点,会将客户端请求轮询发送到集群的所有副本来实现负载均衡。
根据文档ID获取文档信息的过程,核心是根据文档ID确定文档所属的分片Id。
四、问题解答
了解了ES中的分片路由算法和GET请求基本流程,现在我们可以来回答为什么ES中索引主分片的数目不能修改了。
在ES中索引数据写入时,需要根据主分片的数目和文档id来进行路由计算,确定文档保存的分片Id。
在根据文档Id读取单个文档数据时,也需要首先根据主分片的数目和文档id来进行路由计算,确定文档保存的分片Id。
如果在索引创建后,再对索引的主分片数进行修改 ,则会造成后续根据路由算法计算的分片id不准确,导致Get请求获取不到对应数据的问题。
核心:
主分片数目会影响分片路由计算的结果,导致数据查询失败。
五、索引膨胀问题
索引的主分片数目只能在创建索引时指定,且后续不能对主分片数目进行修改。
那么如果由于前期估算的误差,只给索引配置了的主分片数太少,而后续大量数据持续写入,就会造成
这些分片里面的数据不断增长,甚至出现一个分片几百G的情况。
针对这种索引主分片中数据膨胀的问题,我们该如何解决呢?
1、通过索引重建修改主分片数目
2、将数据保存到多个索引中,然后给索引指定相同的索引别名。
理论上来说,查询一个有8个分片的索引和2个具有4个分片的索引查询效率是一样的。
六、通过reindex索引重建修改索引主分片数目
对已有的索引,我们不能修改它的主分片数目。所以这里只能通过reindex索引重建来“曲线救国”。
索引重建的使用场景:
1、当你的数据量过大,而你的索引最初创建的分片数量不足,导致数据入库较慢的情况,此时需要扩大分片的数量,此时可以尝试使用Reindex。
2、当数据的mapping需要修改,但是大量的数据已经导入到索引中了,重新导入数据到新的索引太耗时;但是在ES中,一个字段的mapping在定义并且导入数据之后是不能再修改的,
所以这种情况下也可以考虑尝试使用Reindex。
索引重建示例:
说明:旧的索引主分片数目是4,现在创建新的索引将主分片数目改为8.
PUT my-index-old { "settings": { "number_of_shards": "4" }, "mappings": { "properties": { "company": { "type": "text", "analyzer": "ik_smart" } } } } PUT my-index-new { "settings": { "number_of_shards": "8" }, "mappings": { "properties": { "company": { "type": "text", "analyzer": "ik_smart" } } } }
通过_reindex索引重建命令导入数据
POST _reindex?slices=4&refresh { "source": { "index": "my-index-old", "size": 1000 }, "dest": { "index": "my-index-new" } }
说明:
1、slices用来控制Scroll遍历的切片,并行化重建索引过程,提高效率。slices的数量等于索引中的分片数量时,
查询性能最高效。
2、size控制批量写入的大小。
reindex请求超时问题
说明:有时候由于索引中的数据量太大会导致reindex请求超时问题。
{ "statusCode": 504, "error": "Gateway Time-out", "message": "Client request timeout" }
解决:
添加wait_for_completion=false参数,不用等待请求结束。
POST _reindex?slices=4&refresh&wait_for_completion=false { "source": { "index": "my-index-old", "size": 1000 }, "dest": { "index": "my-index-new" } }
七、索引数据倾斜问题
1、将数据根据特定维度保存在多个索引中,比如根据订单月份保存在多个索引中。
2、通过调整index.routing_partition_size,让数据在分片中均匀分配。
总结
本文主要通过介绍ES中索引的主分片数目不能修改的问题,展开介绍了一系列的问题和解决方案。
1、在创建索引的时候,需要根据索引的数据量和未来的数据增量,预估好索引的主分片数目。
一般一个分片的数据大小不要超过50G。
2、索引的主分片数只能在创建索引时指定,索引创建后就不能动态修改了。
3、介绍了ES的分片路由算法
4、介绍了ES中Get请求的流程
5、基于分片路由算法和Get请求流程,回答了主分片数目不能修改的原因。核心是会导致根据文档Id计算数据保存的分片Id错误导致查询失败。
6、介绍了通过reindex索引重建来修改索引的主分片数量
7、介绍了如何解决索引膨胀问题
8、介绍了索引数据倾斜的解决方案。