美文网首页分布式存储
elasticsearch shard split 思考

elasticsearch shard split 思考

作者: 华饼 | 来源:发表于2018-02-05 10:04 被阅读0次

    前面几篇文章在代码层面介绍了elasticsearch内部是怎么实现shard split的,这篇文章主要回答两个问题:

    • 为什么不能只split 一个shard
    • 怎么保证split 后数据分布式一致的,即不用对原有的数据重新hash

    首先,elasticsearch是通过hash的方式确定每个文档所属的分片的。

    hash(docId) % numShards

    其中docId表示某个文档的id,numShards表示索引有多少个shard。
    那么问题来了,split后shard个数变了,每个文档hash后再对shard个数取模值肯定也不一样了。举个列子:假设原来有两个shard,有四篇文档,hash后的文档id分别为0,1,2,3。那么这四篇文档存入elasticsearch后会是这种情况

    Sshard0: 0, 2
    Sshard1: 1, 3

    每个shard包含两个文档。如果现在split成四个shard,即源索引的每个shard split成目标索引的两个shard。那么目标shard 和源shard 对应的关系如下:

    Tshard0 -> Sshard0
    Tshard1 -> Sshard0
    Tshard2 ->Sshard1
    Tshard3 -> Sshard1

    其中Tshard表示目标shard, Sshard表示源shard。因为从前几篇文章我们了解到选择目标shard的源shard算法为:

    Sshard = Tshard / routingFactor
    routingFactor = numTargetShard / numSourceShard

    在这种情况下,routingFactor = 2。但这样split好像行不通啊!如果按哈希取模确定shard,那么文档1在目标索引中所属的shard应该为 1 % 4 = 1。分配情况如下:

    Tshard0: 0
    Tshard1: 1
    Tshard: 2
    Tshard: 3

    即文档1应该在目标Tshard1中,而Tshard1又是通过Sshard0 split得到。但是Sshard0中并没有包含文档1。那么elasticsearch是怎么解决这个问题的?
    还记得在在分析一种我们就讲过,如果要支持split有两个限制条件,其一就是在创建索引的时候必须指定number_of_routing_shards参数。那么这个参数具体有什么作用呢?
    先看下背后生成shardId的算法

    OperationRouting

    public static int generateShardId(IndexMetaData indexMetaData, @Nullable String id, @Nullable String routing) {
            final String effectiveRouting;
            final int partitionOffset;
    
            if (routing == null) {
                assert(indexMetaData.isRoutingPartitionedIndex() == false) : "A routing value is required for gets from a partitioned index";
                effectiveRouting = id;
            } else {
                effectiveRouting = routing;
            }
    
            if (indexMetaData.isRoutingPartitionedIndex()) {
                partitionOffset = Math.floorMod(Murmur3HashFunction.hash(id), indexMetaData.getRoutingPartitionSize());
            } else {
                // we would have still got 0 above but this check just saves us an unnecessary hash calculation
                partitionOffset = 0;
            }
    
            return calculateScaledShardId(indexMetaData, effectiveRouting, partitionOffset);
        }
    

    这个函数是生成shardId的算法,id参数就是文档id,routing参数是自定义路由的参数。这里可以看到如果自定义路由,将不会使用文档id来确定shard。

    private static int calculateScaledShardId(IndexMetaData indexMetaData, String effectiveRouting, int partitionOffset) {
            final int hash = Murmur3HashFunction.hash(effectiveRouting) + partitionOffset;
    
            // we don't use IMD#getNumberOfShards since the index might have been shrunk such that we need to use the size
            // of original index to hash documents
            return Math.floorMod(hash, indexMetaData.getRoutingNumShards()) / indexMetaData.getRoutingFactor();
        }
    

    这里发现对hash值取模后还除了一个routingFactor。而且不是对numShards取模,而是对routingNumShards取模。IndexMetaData.getRoutigNumShards返回的就是创建索引指定的number_of_routing_shards的值,而IndexMetaData.getRoutingFactor的值其实是routingNumShards / numberOfShards。
    这里其实用的是一致性哈希算法,哈希空间由routingNumShards参数指定,如果创建索引的时候没有指定number_of_routing_shards参数,则routingNumShards的值就等于numberOfShards。所以也明白了为什么split必须要指定number_of_routing_shards参数,而且split最大次数受限于该参数。
    现在反过来再看一次上面的例子,假设创建索引时指定了number_of_routing_shards等于8,shard数等于2,那么。那么分配情况如下:

    routingNumShards=8,numberOfShards=2, routingFcator = 4
    Sshard0: 0,1,2,3 (0 % 8)/ 4 = 0, (1%8) / 4 = 0, (2%8) / 4 = 0 , (3%8) / 4 = 0
    Sshard1:

    可以看到所有文档都分配到了Sshard0。然后现在需要分裂,还是分裂成四个shard。注意,分裂时也有个routingFactor,要搞清两个routingFactor的区别。分裂后shard之间的对应关系还是没变。

    Tshard0 -> Sshard0
    Tshard1 -> Sshard0
    Tshard2 ->Sshard1
    Tshard3 -> Sshard1

    分裂后目标索引的routingNumShards值等于源索引,所以也是8,单目标索引的shard数变量,最终目标索引分配情况如下:

    routingNumShards=8,numberOfShards=4, routingFcator = 2
    Tshard0: 0, 1 (0 % 8) / 2 = 0, (1 % 8) / 2 = 0
    Tshard1: 2, 3 (2 % 8) / 2 = 1, (3 % 8 ) / 2 = 1
    Tshard2:
    Tshard3:

    这样,Sshard0 分裂成Tshard0, Tshard1就没问题了。虽然解决了这个问题,但这种方法也就导致了在split的时候只能源索引的每个shard都参与split,而不支持单独某个shard split。因为在计算shardId的时候对取模的结果除了一个routingFactor,相当于每个shard平均分配hash空间里的每个桶,所以不能出现某个shard占的hash桶比较多的情况。

    相关文章

      网友评论

        本文标题:elasticsearch shard split 思考

        本文链接:https://www.haomeiwen.com/subject/lbmszxtx.html