前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo负载均衡代码分析2(一致性hash策略)

dubbo负载均衡代码分析2(一致性hash策略)

作者头像
技术蓝海
发布2018-04-26 14:40:21
1.3K0
发布2018-04-26 14:40:21
举报
文章被收录于专栏:wannshan(javaer,RPC)wannshan(javaer,RPC)

接上篇 https://cloud.tencent.com/developer/article/1109586 说

这篇说dubbo一致性hash负载均衡策略。要先大致了解下,什么是一直性hash算法。 一致性hash算法最早是用来解决,分布式缓存在有节点变动(新增后者删除)后,节点负载不均衡问题的。 而用一致性hash算法,就是为了达到,当集群中有节点加入或者节点删除时,尽量把负载的变化(加负,减负)均摊到每一个节点。 而没有接点变化时,一直性hash本身就是基本均衡(看hash函数)的负载策略。 基于dubbo一致性Hash,相同参数的请求总是发到同一提供者。 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 我们以3个服务提供者(invoker1,invoker2,invoker3),每个invoker虚拟3个节点(v1,v2,v3),把这9个节点映射到[0,23]的值域为例 看下图:

那么dubbo认为 当一个接口方法参数(一个或者多个连接后)hash后得到的hash(key1)值19,那么它应该调用invoker2_v2节点,实际就是invoker2真实节点。 当一个接口方法参数(一个或者多个连接后)hash后得到的hash(key2)值7,那么它应该调用invoker3_v2节点,实际就是invoker3真实节点。

接下来看看具体代码实现:

代码语言:javascript
复制
/**
 * ConsistentHashLoadBalance
 *
 * @author william.liangf
 */
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    //接口名.方法名称为key hash选择器为value的map.每个方法一个选择器。
    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) {
        //接口名.方法名称为key
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        //取的invokers对象的hashcode,验证对象变化
        int identityHashCode = System.identityHashCode(invokers);
        //根据key获取hash选择器
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        if (selector == null || selector.identityHashCode != identityHashCode) {
            //选择器为null或者,对象已变化,就创建新选择器放入map中
            selectors.put(key, new ConsistentHashSelector<T>(invokers, invocation.getMethodName(), identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }
        //通过选择器的select方法,返回选中的invoker
        return selector.select(invocation);
    }

    private static final class ConsistentHashSelector<T> {
        //hash 环(值域)中,某些值(所有虚拟节点数)到虚拟节点的映射。
        private final TreeMap<Long, Invoker<T>> virtualInvokers;
        //每个invoker 需要虚拟的节点数
        private final int replicaNumber;

        private final int identityHashCode;

        private final int[] argumentIndex;

        ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
            //基于红黑树实现的有序map,有序很重要。
            this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
            this.identityHashCode = identityHashCode;
            URL url = invokers.get(0).getUrl();
            //获取虚拟节点数,默认160个节点,配置例子 <dubbo:parameter key="hash.nodes" value="320" />
            this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
            //获取需要hash的参数位置。配置例子<dubbo:parameter key="hash.arguments" value="0,1" /> 默认只hash第一个,0位置参数
            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]);
            }
            for (Invoker<T> invoker : invokers) {
                //获取提供者host:port形式地址,以160个虚拟节点为例
                String address = invoker.getUrl().getAddress();
                for (int i = 0; i < replicaNumber / 4; i++) {
                    byte[] digest = md5(address + i);//host:port(0,1,2,3.....39),40份
                    //每个再分别,hash 4次,(这样每个机器就虚拟了160份)
                    for (int h = 0; h < 4; h++) {
                        long m = hash(digest, h);
                        //160份虚拟节点,每一份都映射同一个实际节点
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

        public Invoker<T> select(Invocation invocation) {
            String key = toKey(invocation.getArguments());
            //对拼接后的参数做MD5指纹摘要
            byte[] digest = md5(key);
           //摘要后,hash计算
            return selectForKey(hash(digest, 0));
        }

        /***
         * 把参数直接拼接。
         * @param args
         * @return
         */
        private String toKey(Object[] args) {
            StringBuilder buf = new StringBuilder();
            for (int i : argumentIndex) {
                if (i >= 0 && i < args.length) {
                    buf.append(args[i]);
                }
            }
            return buf.toString();
        }
        //根据hash值,选择invoker方法的核心方法
        private Invoker<T> selectForKey(long hash) {
            Invoker<T> invoker;
            Long key = hash;
	    //如果key不在map里,也有可能key已在map里,直接走下面流程
            if (!virtualInvokers.containsKey(key)) {
                //方法加参数hash 参数的key,没在已近映射的ma中,
                //返回比key 大的map值
                SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key);
                if (tailMap.isEmpty()) {//如果key最大,就取虚拟节点第一个(key值最小的节点)
                    key = virtualInvokers.firstKey();
                } else {
                    //取比key大的keys中,最小的一个节点,(离key最近的,大于key的节点)
                    key = tailMap.firstKey();
                }
            }
            //获取key映射的实际invoker
            invoker = virtualInvokers.get(key);
            return invoker;
        }
//hash 算法,也很关键
        private long hash(byte[] digest, int number) {
            //number可以是,0,1,2,3 long 类型64 bit位
            //最后0xFFFFFFFFL;保证4字节位表示数值。相当于Ingter型数值。所以hash环的值域是[0,Integer.max_value]
            //每次取digest4个字节(|操作),组成4字节的数值。
            //当number 为 0,1,2,3时,分别对应digest第
            // 1,2,3,4;
            // 5,6,7,8;
            // 9,10,11,12;
            // 13,14,15,16;字节
            //4批
            return (
                    (
                     //digest的第4(number 为 0时),8(number 为 1),12(number 为 2),16(number 为 3)字节,&0xFF后,左移24位
                     (long) (digest[3 + number * 4] & 0xFF) << 24
                    )
                    |(
                     //digest的第3,7,11,15字节,&0xFF后,左移16位
                     (long) (digest[2 + number * 4] & 0xFF) << 16
                    )
                    |(
                     //digest的第2,6,10,14字节,&0xFF后,左移8位
                      (long) (digest[1 + number * 4] & 0xFF) << 8
                    )
                    |(
                    //digest的第1,5,9,13字节,&0xFF
                      digest[number * 4] & 0xFF
                    )
                    )
                    & 0xFFFFFFFFL;
        }
        //返回16字节总共128bit位的MD5指纹签名byte[]。
        private byte[] md5(String value) {
            MessageDigest md5;
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            md5.reset();
            byte[] bytes;
            try {
                bytes = value.getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            md5.update(bytes);
            return md5.digest();
        }

    }

}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档