Dubbo剖析-集群容錯

本篇主要對dubbo集群容錯進(jìn)行剖析涉波,主要下面幾個模塊

  1. cluster容錯方案
  2. Directory目錄服務(wù)
  3. route 路由解析
  4. loadBalance 軟負(fù)載均衡

一、調(diào)用鏈路


二、容錯方案


集群模式的配置

<dubbo:service cluster="failsafe" /> 服務(wù)提供方
<dubbo:reference cluster="failsafe" /> 服務(wù)消費(fèi)方

集群容錯實(shí)現(xiàn)

接口類 com.alibaba.dubbo.rpc.cluster.Cluster


Cluster實(shí)現(xiàn)類

1.AvailableCluster
獲取可用的調(diào)用贤姆。遍歷所有Invokers判斷Invoker.isAvalible,只要一個有為true直接調(diào)用返回沐飘,不管成不成功

2.BroadcastCluster
廣播調(diào)用。遍歷所有Invokers, 逐個調(diào)用每個調(diào)用catch住異常不影響其他invoker調(diào)用

3.FailbackCluster
失敗自動恢復(fù)桩皿, 對于invoker調(diào)用失敗, 后臺記錄失敗請求扳埂,任務(wù)定時重發(fā), 通常用于通知

//FailbackClusterInvoker
//記錄失敗的調(diào)用
private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, AbstractClusterInvoker<?>>();

protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            //失敗后調(diào)用 addFailed
            addFailed(invocation, this);
            return new RpcResult(); // ignore
        }
    }

private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {
    if (retryFuture == null) {
        synchronized (this) {
            if (retryFuture == null) {
                retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

                    public void run() {
                        // 收集統(tǒng)計(jì)信息
                        try {
                            retryFailed();
                        } catch (Throwable t) { // 防御性容錯
                            logger.error("Unexpected error occur at collect statistic", t);
                        }
                    }
                }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS);
            }
        }
    }
    failed.put(invocation, router);
}

//失敗的進(jìn)行重試业簿,重試成功后移除當(dāng)前map
void retryFailed() {
        if (failed.size() == 0) {
            return;
        }
        for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>(
                failed).entrySet()) {
            Invocation invocation = entry.getKey();
            Invoker<?> invoker = entry.getValue();
            try {
                invoker.invoke(invocation);
                failed.remove(invocation);
            } catch (Throwable e) {
                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
            }
        }
    }

4.FailfastCluster
快速失敗,只發(fā)起一次調(diào)用阳懂,失敗立即保錯梅尤,通常用于非冪等性操作

5.FailoverCluster default
失敗轉(zhuǎn)移,當(dāng)出現(xiàn)失敗岩调,重試其它服務(wù)器巷燥,通常用于讀操作,但重試會帶來更長延遲
(1) 目錄服務(wù)directory.list(invocation) 列出方法的所有可調(diào)用服務(wù)
獲取重試次數(shù)号枕,默認(rèn)重試兩次

int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;

(2) 根據(jù)LoadBalance負(fù)載策略選擇一個Invoker
(3) 執(zhí)行invoker.invoke(invocation)調(diào)用
(4) 調(diào)用成功返回
調(diào)用失敗小于重試次數(shù)缰揪,重新執(zhí)行從3)步驟開始執(zhí)行,調(diào)用次數(shù)大于等于重試次數(shù)拋出調(diào)用失敗異常

6.FailsafeCluster
失敗安全葱淳,出現(xiàn)異常時钝腺,直接忽略,通常用于寫入審計(jì)日志等操作赞厕。

7.ForkingCluster
并行調(diào)用艳狐,只要一個成功即返回,通常用于實(shí)時性要求較高的操作皿桑,但需要浪費(fèi)更多服務(wù)資源毫目。

注:
還有 MergeableCluster 和 MockClusterWrapper策略,但是個人沒有用過所以就不說了

三诲侮、Directory目錄服務(wù)


1. StaticDirectory

靜態(tài)目錄服務(wù)镀虐, 它的所有Invoker通過構(gòu)造函數(shù)傳入, 服務(wù)消費(fèi)方引用服務(wù)的時候沟绪, 服務(wù)對多注冊中心的引用刮便,將Invokers集合直接傳入 StaticDirectory構(gòu)造器

public StaticDirectory(URL url, List<Invoker<T>> invokers, List<Router> routers) {
    super(url == null && invokers != null && invokers.size() > 0 ? invokers.get(0).getUrl() : url, routers);
    if (invokers == null || invokers.size() == 0)
        throw new IllegalArgumentException("invokers == null");
    this.invokers = invokers;
}

StaticDirectory的list方法直接返回所有invoker集合

@Override
protected List<Invoker<T>> doList(Invocation invocation) throws RpcException {
    return invokers;
}

2. RegistryDirectory

注冊目錄服務(wù), 它的Invoker集合是從注冊中心獲取的绽慈, 它實(shí)現(xiàn)了NotifyListener接口實(shí)現(xiàn)了回調(diào)接口notify(List<Url>)诺核。

比如消費(fèi)方要調(diào)用某遠(yuǎn)程服務(wù)抄肖,會向注冊中心訂閱這個服務(wù)的所有服務(wù)提供方,訂閱時和服務(wù)提供方數(shù)據(jù)有變動時回調(diào)消費(fèi)方的NotifyListener服務(wù)的notify方法NotifyListener.notify(List<Url>) 回調(diào)接口傳入所有服務(wù)的提供方的url地址然后將urls轉(zhuǎn)化為invokers, 也就是refer應(yīng)用遠(yuǎn)程服務(wù)到此時引用某個遠(yuǎn)程服務(wù)的RegistryDirectory中有對這個遠(yuǎn)程服務(wù)調(diào)用的所有invokers窖杀。

RegistryDirectory.list(invocation)就是根據(jù)服務(wù)調(diào)用方法獲取所有的遠(yuǎn)程服務(wù)引用的invoker執(zhí)行對象

四、服務(wù)路由


dubbo路由功能貌似用的不多裙士,目的主要是對已注冊的服務(wù)進(jìn)行過濾入客,比如只能調(diào)用某些配置的服務(wù),或者禁用某些服務(wù)腿椎。

1. ConditionRouter條件路由

dubbo-admin 后臺進(jìn)行配置桌硫。


路由代碼入口

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {
    if (invokers == null || invokers.size() == 0) {
        return invokers;
    }
    try {
        if (!matchWhen(url, invocation)) {
            return invokers;
        }
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        if (thenCondition == null) {
            logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
            return result;
        }
    .............................

2. ScriptRouter腳本路由

按照dubbo腳本規(guī)則進(jìn)行編寫,程序識別

五啃炸、軟負(fù)載均衡


1. RandomLoadBalance default

隨機(jī)铆隘,按權(quán)重設(shè)置隨機(jī)概率。權(quán)重default=100
在一個截面上碰撞的概率高南用,但調(diào)用量越大分布越均勻膀钠,而且按概率使用權(quán)重后也比較均勻,有利于動態(tài)調(diào)整提供者權(quán)重裹虫。

    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // 總個數(shù)
        int totalWeight = 0; // 總權(quán)重
        boolean sameWeight = true; // 權(quán)重是否都一樣
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // 累計(jì)總權(quán)重
            if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                sameWeight = false; // 計(jì)算所有權(quán)重是否一樣
            }
        }
        if (totalWeight > 0 && !sameWeight) {
            // 如果權(quán)重不相同且權(quán)重大于0則按總權(quán)重數(shù)隨機(jī)
            int offset = random.nextInt(totalWeight);
            // 并確定隨機(jī)值落在哪個片斷上
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i), invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // 如果權(quán)重相同或權(quán)重為0則均等隨機(jī)
        return invokers.get(random.nextInt(length));
    }

算法含義
如果所有的服務(wù)權(quán)重都一樣肿嘲,就采用總服務(wù)數(shù)進(jìn)行隨機(jī)。如果權(quán)重不一樣筑公,則按照權(quán)重出隨機(jī)數(shù)雳窟,然后用隨機(jī)數(shù)減去服務(wù)權(quán)重,結(jié)果為負(fù)數(shù)則使用當(dāng)前循環(huán)的服務(wù)匣屡。其實(shí)也就是一個概率性問題 每個服務(wù)的概率就是 當(dāng)前服務(wù)的權(quán)重/ 總服務(wù)權(quán)重

2. RoundRobinLoadBalance

輪循封救,按公約后的權(quán)重設(shè)置輪循比率。
存在慢的提供者累積請求的問題捣作,比如:第二臺機(jī)器很慢誉结,但沒掛,當(dāng)請求調(diào)到第二臺時就卡在那虾宇,久而久之搓彻,所有請求都卡在調(diào)到第二臺上。

該負(fù)載算法維護(hù)著一個方法調(diào)用順序計(jì)數(shù)

private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();

以方法名作為key

輪循分為 普通輪詢和加權(quán)輪詢嘱朽。權(quán)重一樣時旭贬,采用取模運(yùn)算普通輪詢,反之加權(quán)輪詢搪泳。

下面看下具體的實(shí)現(xiàn)
RoundRobinLoadBalance#doSelect

i.普通輪詢

AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
    sequences.putIfAbsent(key, new AtomicPositiveInteger());
    sequence = sequences.get(key);
}
//獲取本次調(diào)用的服務(wù)器序號稀轨,并+1
int currentSequence = sequence.getAndIncrement();

//當(dāng)前序號和服務(wù)總數(shù)取模
return invokers.get(currentSequence % length);

ii.加權(quán)輪詢
下面貼下核心實(shí)現(xiàn)代碼。注意幾個變量

weightSum = 服務(wù)權(quán)重之和
invokerToWeightMap = 權(quán)重>0的 invoker map

int currentSequence = sequence.getAndIncrement();
if (maxWeight > 0 && minWeight < maxWeight) { // 權(quán)重不一樣

    // mod < weightSum岸军,下面for循環(huán)進(jìn)行weight遞減奋刽,weight大的服務(wù)被調(diào)用的概率大
    int mod = currentSequence % weightSum;
    for (int i = 0; i < maxWeight; i++) {
        for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
            final Invoker<T> k = each.getKey();
            final IntegerWrapper v = each.getValue();
            if (mod == 0 && v.getValue() > 0) {
                return k;
            }
            if (v.getValue() > 0) {
                v.decrement();
                mod--;
            }
        }
    }
}

可以舉個例子
兩個服務(wù) A 和 B瓦侮,權(quán)重分別是1和2
那么 mod=[0,1,2],經(jīng)過上面的邏輯佣谐,調(diào)用概率是 A B B A B B A B B ..... 顯然B的概率更大一些

3. LeastActiveLoadBalance

最少活躍調(diào)用數(shù)優(yōu)先肚吏,活躍數(shù)指調(diào)用前后計(jì)數(shù)差。使慢的提供者收到更少請求狭魂,因?yàn)樵铰奶峁┱叩恼{(diào)用前后計(jì)數(shù)差會越大罚攀。

每個服務(wù)有一個活躍計(jì)數(shù)器,我們假如有A,B兩個提供者.計(jì)數(shù)均為0.當(dāng)A提供者開始處理請求,該計(jì)數(shù)+1,此時A還沒處理完,當(dāng)處理完后則計(jì)數(shù)-1.而B請求接收到請求處理得很快.B處理完后A還沒處理完,所以此時A,B的計(jì)數(shù)為1,0.那么當(dāng)有新的請求來的時候,就會選擇B提供者(B的活躍計(jì)數(shù)比A小).這就是文檔說的,使慢的提供者收到更少請求。

int leastCount = 0; // 相同最小活躍數(shù)的個數(shù)
int[] leastIndexs = new int[length]; // 相同最小活躍數(shù)的下標(biāo)

i.最小活躍服務(wù)個數(shù)=1雌澄, 該服務(wù)優(yōu)先

if (leastCount == 1) {
    // 如果只有一個最小則直接返回
    return invokers.get(leastIndexs[0]);
}

ii.最小活躍服務(wù)個數(shù)>1, 最小活躍的服務(wù)按照權(quán)重隨機(jī)

if (!sameWeight && totalWeight > 0) {
    // 如果權(quán)重不相同且權(quán)重大于0則按總權(quán)重數(shù)隨機(jī)
    int offsetWeight = random.nextInt(totalWeight);
    // 并確定隨機(jī)值落在哪個片斷上
    for (int i = 0; i < leastCount; i++) {
        int leastIndex = leastIndexs[i];
        //權(quán)重越大斋泄,offsetWeight越快減成負(fù)數(shù)
        offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
        if (offsetWeight <= 0)
            return invokers.get(leastIndex);
    }
}

iii. 最小活躍服務(wù)個數(shù)>1, 權(quán)重相同,服務(wù)個數(shù)隨機(jī)

// 如果權(quán)重相同或權(quán)重為0則均等隨機(jī)
return invokers.get(leastIndexs[random.nextInt(leastCount)]);

4. ConsistentHashLoadBalance

  • 一致性 Hash镐牺,相同參數(shù)的請求總是發(fā)到同一提供者炫掐。
  • 當(dāng)某一臺提供者掛時,原本發(fā)往該提供者的請求睬涧,基于虛擬節(jié)點(diǎn)募胃,平攤到其它提供者,不會引起劇烈變動宙地。
  • 算法參見:http://en.wikipedia.org/wiki/Consistent_hashing
  • 缺省只對第一個參數(shù) Hash摔认,如果要修改,請配置 <dubbo:parameter key="hash.arguments" value="0,1" />
  • 缺省用 160 份虛擬節(jié)點(diǎn)宅粥,如果要修改参袱,請配置 <dubbo:parameter key="hash.nodes" value="320" />
配置樣例
<dubbo:reference id="demoService" interface="com.youzan.dubbo.api.DemoService" loadbalance="consistenthash">
    <!--缺省只對第一個參數(shù) Hash-->
    <dubbo:parameter key="hash.arguments" value="0,1" />
    <!--缺省用 160 份虛擬節(jié)點(diǎn),-->
    <dubbo:parameter key="hash.nodes" value="160" />
</dubbo:reference>
算法解析

ConsistentHashLoadBalance為使用該算法的服務(wù)維護(hù)了一個selectors,

key=invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName()
eg: com.youzan.dubbo.api.DemoService.sayHello

#com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance

private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();

@SuppressWarnings("unchecked")
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
    int identityHashCode = System.identityHashCode(invokers);

    //獲取該服務(wù)的ConsistentHashSelector秽梅,并跟進(jìn)本次調(diào)用獲取對應(yīng)invoker
    ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
    if (selector == null || selector.getIdentityHashCode() != identityHashCode) {
        selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
        selector = (ConsistentHashSelector<T>) selectors.get(key);
    }
    return selector.select(invocation);
}

ConsistentHashSelector作為ConsistentHashLoadBalance的內(nèi)部類抹蚀, 就是具體的一致性hash實(shí)現(xiàn)。

  • ConsistentHashSelector內(nèi)部元素
#com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance.ConsistentHashSelector

//該服務(wù)的所有hash節(jié)點(diǎn)
private final TreeMap<Long, Invoker<T>> virtualInvokers;
//虛擬節(jié)點(diǎn)數(shù)量
private final int replicaNumber;
//該服務(wù)的唯一hashcode企垦,通過System.identityHashCode(invokers)獲取
private final int identityHashCode;
  • 如何構(gòu)建該服務(wù)的虛擬節(jié)點(diǎn)环壤?
public ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
    // 創(chuàng)建TreeMap 來保存結(jié)點(diǎn)
    this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
    // 生成調(diào)用結(jié)點(diǎn)HashCode
    this.identityHashCode = System.identityHashCode(invokers);
    // 獲取Url 
    //dubbo://192.168.0.4:20880/com.youzan.dubbo.api.DemoService?anyhost=true&application=consumer-of-helloworld-app&check=false&class=com.youzan.dubbo.provider.DemoServiceImpl&dubbo=2.5.4&generic=false&hash.arguments=0,1&hash.nodes=160&interface=com.youzan.dubbo.api.DemoService&loadbalance=consistenthash&methods=sayHello&pid=32710&side=consumer&timestamp=1527383363936
    URL url = invokers.get(0).getUrl();
    // 獲取所配置的結(jié)點(diǎn)數(shù),如沒有設(shè)置則使用默認(rèn)值160
    this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
    // 獲取需要進(jìn)行hash的參數(shù)數(shù)組索引钞诡,默認(rèn)對第一個參數(shù)進(jìn)行hash
    String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
    argumentIndex = new int[index.length];
    for (int i = 0; i < index.length; i ++) {
        argumentIndex[i] = Integer.parseInt(index[i]);
    }
    // 創(chuàng)建虛擬結(jié)點(diǎn)
    // 對每個invoker生成replicaNumber個虛擬結(jié)點(diǎn)郑现,并存放于TreeMap中
    for (Invoker<T> invoker : invokers) {
        for (int i = 0; i < replicaNumber / 4; i++) {
            // 根據(jù)md5算法為每4個結(jié)點(diǎn)生成一個消息摘要,摘要長為16字節(jié)128位荧降。
            byte[] digest = md5(invoker.getUrl().toFullString() + i);
            // 隨后將128位分為4部分接箫,0-31,32-63,64-95,95-128,并生成4個32位數(shù)朵诫,存于long中辛友,long的高32位都為0
            // 并作為虛擬結(jié)點(diǎn)的key。
            for (int h = 0; h < 4; h++) {
                long m = hash(digest, h);
                virtualInvokers.put(m, invoker);
            }
        }
    }
}

代碼如果看的不是很懂剪返,也不用去深究了(我就沒看懂废累,瞻仰了網(wǎng)上大神的文章貼了帖注釋)邓梅,大家可以就粗略的認(rèn)為,這段代碼就是盡可能的構(gòu)建出散列均勻的服務(wù)hash表邑滨。

  • 如何從virtualInvokers選取本次調(diào)用的invoker日缨?
// 選擇invoker
public Invoker<T> select(Invocation invocation) {
    // 根據(jù)調(diào)用參數(shù)來生成Key
    String key = toKey(invocation.getArguments());
    // 根據(jù)這個參數(shù)生成消息摘要
    byte[] digest = md5(key);
    //調(diào)用hash(digest, 0),將消息摘要轉(zhuǎn)換為hashCode掖看,這里僅取0-31位來生成HashCode
    //調(diào)用sekectForKey方法選擇結(jié)點(diǎn)殿遂。
    Invoker<T> invoker = sekectForKey(hash(digest, 0));
    return invoker;
}

private String toKey(Object[] args) {
    StringBuilder buf = new StringBuilder();
    // 由于hash.arguments沒有進(jìn)行配置,因?yàn)橹蝗》椒ǖ牡?個參數(shù)作為key
    for (int i : argumentIndex) {
        if (i >= 0 && i < args.length) {
            buf.append(args[i]);
        }
    }
    return buf.toString();
}

//根據(jù)hashCode選擇結(jié)點(diǎn)
private Invoker<T> sekectForKey(long hash) {
    Invoker<T> invoker;
    Long key = hash;
    // 若HashCode直接與某個虛擬結(jié)點(diǎn)的key一樣乙各,則直接返回該結(jié)點(diǎn)
    if (!virtualInvokers.containsKey(key)) {
        // 若不一致,找到一個比傳入的key大的第一個結(jié)點(diǎn)幢竹。
        SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
        // 若不存在耳峦,那么選擇treeMap中第一個結(jié)點(diǎn)
        // 使用TreeMap的firstKey方法,來選擇最小上界焕毫。
        if (tailMap.isEmpty()) {
            key = virtualInvokers.firstKey();
        } else {
           // 若存在則返回
            key = tailMap.firstKey();
        }
    }
    invoker = virtualInvokers.get(key);
    return invoker;
}
  • 一致性hash環(huán)是什么東東蹲坷?和上面的算法什么關(guān)系?
    ConsistentHashSelector.virtualInvokers這個東西就是我們的服務(wù)hash節(jié)點(diǎn)邑飒,單純的從數(shù)據(jù)結(jié)構(gòu)上的確看不到什么環(huán)狀的存在循签,可以先示意下,當(dāng)前的數(shù)據(jù)結(jié)構(gòu)
    selector結(jié)構(gòu)
virtualInvokers

我們的服務(wù)節(jié)點(diǎn)只是一個普通的 map數(shù)據(jù)存儲而已疙咸,如何形成環(huán)呢县匠?其實(shí)所謂的環(huán)只是邏輯上的展現(xiàn),ConsistentHashSelector.sekectForKey()方法里通過 TreeMap.tailMap()撒轮、TreeMap.tailMap().firstKey乞旦、TreeMap.tailMap().firstKey() 結(jié)合case實(shí)現(xiàn)了環(huán)狀邏輯。下面我們畫圖說話题山。

第一步原始數(shù)據(jù)結(jié)構(gòu)兰粉,我們按照hash從小到大排列


A,B,C表示我們提供的服務(wù),改示意圖假設(shè)服務(wù)節(jié)點(diǎn)散列均勻

第二步選擇服務(wù)節(jié)點(diǎn)

i. 假設(shè)本地調(diào)用得到的key=2120, 代碼邏輯(指ConsistentHashSelector.sekectForKey)走到tailMap.firstKey()


那么讀取到 3986 A服務(wù)

ii.假設(shè)本地調(diào)用得到的key=9991, tailMap為空顶瞳,邏輯走到 virtualInvokers.firstKey() 回到起點(diǎn)


讀取到 1579 A服務(wù)

上述兩部情況基本已經(jīng)能夠描述清楚節(jié)點(diǎn)的選擇邏輯玖姑,至于hash直接命中,那么讀取對應(yīng)的服務(wù)即可慨菱,無需多講焰络。

最后環(huán)狀形成
上面兩部的介紹已經(jīng)描述hash算法,那么我們所謂的環(huán)狀是怎么一回事呢抡柿?其實(shí)也就是為了方便更好的理解這個邏輯舔琅,我們將線性的hash排列作為環(huán)狀,然后hash的選擇按照順時針方向選擇節(jié)點(diǎn)(等價于上面hash比較大兄蘖印)

環(huán)狀示意

節(jié)點(diǎn)選擇算法與上面等價备蚓,本圖主要用來示意课蔬,理想的hash環(huán)hash差距應(yīng)該是等差,均勻的排列郊尝。

參考:
https://blog.csdn.net/column/details/learningdubbo.html?&page=1
https://blog.csdn.net/revivedsun/article/details/71022871
http://www.reibang.com/p/53feb7f5f5d9

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末二跋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子流昏,更是在濱河造成了極大的恐慌扎即,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件况凉,死亡現(xiàn)場離奇詭異谚鄙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)刁绒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門闷营,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人知市,你說我怎么就攤上這事傻盟。” “怎么了嫂丙?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵娘赴,是天一觀的道長。 經(jīng)常有香客問我跟啤,道長诽表,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任腥光,我火速辦了婚禮关顷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘武福。我一直安慰自己议双,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布捉片。 她就那樣靜靜地躺著平痰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伍纫。 梳的紋絲不亂的頭發(fā)上宗雇,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音莹规,去河邊找鬼赔蒲。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舞虱。 我是一名探鬼主播欢际,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼矾兜!你這毒婦竟也來了损趋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤椅寺,失蹤者是張志新(化名)和其女友劉穎浑槽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體返帕,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桐玻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荆萤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畸冲。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖观腊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情算行,我是刑警寧澤梧油,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站州邢,受9級特大地震影響儡陨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜量淌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一骗村、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呀枢,春花似錦胚股、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至摘刑,卻和暖如春进宝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枷恕。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工党晋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓未玻,卻偏偏與公主長得像灾而,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子深胳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容