dubbo技术内幕八 一致性hash负载均衡ConsistentHashLoadBalance

关于一致性hash的讲解网上很多,我们不说,具体看下dubbo里面是如何的实现一致性hash算法的。
我们先看下ConsistentHashLoadBalance里面的doSelect方法

 private final ConcurrentMap> selectors = new ConcurrentHashMap>();

    @SuppressWarnings("unchecked")
    @Override
    protected  Invoker doSelect(List> invokers, URL url, Invocation invocation) {
        String methodName = RpcUtils.getMethodName(invocation);
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
        int identityHashCode = System.identityHashCode(invokers);
        //等于说selectors里面缓存了 key(服务名+方法)对应的ConsistentHashSelector
        ConsistentHashSelector selector = (ConsistentHashSelector) selectors.get(key);
       //当selector为空,说明这个key对应的服务方法还没有调用过
       //如果selector.identityHashCode != identityHashCode说明invokers发生了动态变化
       //都需要重构ConsistentHashSelector
        if (selector == null || selector.identityHashCode != identityHashCode) {
            selectors.put(key, new ConsistentHashSelector(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector) selectors.get(key);
        }
        //使用选择器针对invocation进行选择,我们可以看到这个selectors的这个缓存是很大的,如果有一万个服务,每个服务一万个方法,那么这个缓存的大小相当的恐怖了,
但是底层使用了currentmap,会自动的进行优化

        return selector.select(invocation);
    }

那接下来我们看下ConsistentHashSelector具体是怎么实现的,如下

 private static final class ConsistentHashSelector {
        //虚拟节点 Long 为其对应的hash环上的值  Invoker为实际对应的invoker
        private final TreeMap> virtualInvokers;
       //hash环上的复制节点数
        private final int replicaNumber;
        //此ConsistentHashSelector对应的hash值,用于判断此ConsistentHashSelector是否有过期(如果invokers发生了变化,这个值会失效)
        private final int identityHashCode;
        //根据哪些argumentIndex来计算virtualInvokers里面的Long值
        private final int[] argumentIndex;
}

我们看看其构造函数

ConsistentHashSelector(List> invokers, String methodName, int identityHashCode) {
            this.virtualInvokers = new TreeMap>();
            this.identityHashCode = identityHashCode;
            URL url = invokers.get(0).getUrl();
            //缺省160个 
            this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);   
            //缺省使用第一个参数计算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]);
            }
           //默认将每个invoker复制160份,分散到hash环
            for (Invoker invoker : invokers) {
                String address = invoker.getUrl().getAddress();
                for (int i = 0; i < replicaNumber / 4; i++) {
                    byte[] digest = md5(address + i);
                    for (int h = 0; h < 4; h++) {
                        long m = hash(digest, h);
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

我们再看下最核心的一个方法

//直接根据hash值在红黑树上面找到小于hash值的第一个Invoker
//如果没有找到,那么返回第一个(要形成环)
private Invoker selectForKey(long hash) {
            Map.Entry> entry = virtualInvokers.tailMap(hash, true).firstEntry();
            if (entry == null) {
                entry = virtualInvokers.firstEntry();
            }
            return entry.getValue();
        }

如上,根据treeMap就巧妙的实现了一个一致性hash环。

你可能感兴趣的:(dubbo技术内幕八 一致性hash负载均衡ConsistentHashLoadBalance)