elasticsearch shard split 思考

前面幾篇文章在代碼層面介紹了elasticsearch內(nèi)部是怎么實(shí)現(xiàn)shard split的,這篇文章主要回答兩個(gè)問題:

  • 為什么不能只split 一個(gè)shard
  • 怎么保證split 后數(shù)據(jù)分布式一致的,即不用對(duì)原有的數(shù)據(jù)重新hash

首先,elasticsearch是通過hash的方式確定每個(gè)文檔所屬的分片的。

hash(docId) % numShards

其中docId表示某個(gè)文檔的id,numShards表示索引有多少個(gè)shard。
那么問題來了,split后shard個(gè)數(shù)變了,每個(gè)文檔hash后再對(duì)shard個(gè)數(shù)取模值肯定也不一樣了。舉個(gè)列子:假設(shè)原來有兩個(gè)shard,有四篇文檔,hash后的文檔id分別為0,1,2,3。那么這四篇文檔存入elasticsearch后會(huì)是這種情況

Sshard0: 0, 2
Sshard1: 1, 3

每個(gè)shard包含兩個(gè)文檔。如果現(xiàn)在split成四個(gè)shard,即源索引的每個(gè)shard split成目標(biāo)索引的兩個(gè)shard。那么目標(biāo)shard 和源shard 對(duì)應(yīng)的關(guān)系如下:

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

其中Tshard表示目標(biāo)shard, Sshard表示源shard。因?yàn)閺那皫灼恼挛覀兞私獾竭x擇目標(biāo)shard的源shard算法為:

Sshard = Tshard / routingFactor
routingFactor = numTargetShard / numSourceShard

在這種情況下,routingFactor = 2。但這樣split好像行不通?。∪绻垂H∧4_定shard,那么文檔1在目標(biāo)索引中所屬的shard應(yīng)該為 1 % 4 = 1。分配情況如下:

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

即文檔1應(yīng)該在目標(biāo)Tshard1中,而Tshard1又是通過Sshard0 split得到。但是Sshard0中并沒有包含文檔1。那么elasticsearch是怎么解決這個(gè)問題的?
還記得在在分析一種我們就講過,如果要支持split有兩個(gè)限制條件,其一就是在創(chuàng)建索引的時(shí)候必須指定number_of_routing_shards參數(shù)。那么這個(gè)參數(shù)具體有什么作用呢?
先看下背后生成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);
    }

這個(gè)函數(shù)是生成shardId的算法,id參數(shù)就是文檔id,routing參數(shù)是自定義路由的參數(shù)。這里可以看到如果自定義路由,將不會(huì)使用文檔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();
    }

這里發(fā)現(xiàn)對(duì)hash值取模后還除了一個(gè)routingFactor。而且不是對(duì)numShards取模,而是對(duì)routingNumShards取模。IndexMetaData.getRoutigNumShards返回的就是創(chuàng)建索引指定的number_of_routing_shards的值,而IndexMetaData.getRoutingFactor的值其實(shí)是routingNumShards / numberOfShards。
這里其實(shí)用的是一致性哈希算法,哈希空間由routingNumShards參數(shù)指定,如果創(chuàng)建索引的時(shí)候沒有指定number_of_routing_shards參數(shù),則routingNumShards的值就等于numberOfShards。所以也明白了為什么split必須要指定number_of_routing_shards參數(shù),而且split最大次數(shù)受限于該參數(shù)。
現(xiàn)在反過來再看一次上面的例子,假設(shè)創(chuàng)建索引時(shí)指定了number_of_routing_shards等于8,shard數(shù)等于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。然后現(xiàn)在需要分裂,還是分裂成四個(gè)shard。注意,分裂時(shí)也有個(gè)routingFactor,要搞清兩個(gè)routingFactor的區(qū)別。分裂后shard之間的對(duì)應(yīng)關(guān)系還是沒變。

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

分裂后目標(biāo)索引的routingNumShards值等于源索引,所以也是8,單目標(biāo)索引的shard數(shù)變量,最終目標(biāo)索引分配情況如下:

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就沒問題了。雖然解決了這個(gè)問題,但這種方法也就導(dǎo)致了在split的時(shí)候只能源索引的每個(gè)shard都參與split,而不支持單獨(dú)某個(gè)shard split。因?yàn)樵谟?jì)算shardId的時(shí)候?qū)θ∧5慕Y(jié)果除了一個(gè)routingFactor,相當(dāng)于每個(gè)shard平均分配hash空間里的每個(gè)桶,所以不能出現(xiàn)某個(gè)shard占的hash桶比較多的情況。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容