前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dubbo 的负载均衡策略:一致性哈希策略

Dubbo 的负载均衡策略:一致性哈希策略

作者头像
LieBrother
发布2019-04-02 16:21:24
6970
发布2019-04-02 16:21:24
举报
文章被收录于专栏:LieBrotherLieBrother

本文简单介绍 Dubbo 中的负载均衡策略:一致性哈希策略。

1

一致性哈希策略

一致性哈希策略:指的是相同参数的请求总是发到同一个提供者实例。

简单描述一下一致性哈希,如下图所示。假设有 8 ,组成下面 A - H 的 8 个虚拟节点,A - H 哈希值分别是递增的,当有一个请求进来,通过计算参数的哈希值,比如该值刚好是 1 的位置,那么选中比 1 的值大的最小的那个实例,则结果该请求选中 C 虚拟节点;假如请求的参数的哈希值是在 2 的位置,那么找不到比 2 的值大的最小的实例,则选中所有实例中最小的一个,也就是 A。

2

Dubbo 实现方式

Dubbo 在使用一致性哈希的时候,会用到如下配置:

  1. 配置对哪些参数做哈希计算,默认是只对第一个参数: <dubbo:parameter key="hash.arguments" value="0,1" />
  2. 配置需要多少份虚拟节点,也就是一个提供服务的实例需要冗余多少份,默认是160份虚拟节点 <dubbo:parameter key="hash.nodes" value="320" />

Dubbo 在实现一致性哈希策略的时候使用了虚拟节点的概念,就是扩大实例个数,当然不是真的有那么多个实例,比如有 5 个实例,默认是 160 个备份,则真实的虚拟节点有 5 * 160 = 800 个。

为什么要采取虚拟节点?

假如不使用虚拟节点,只有 5 个节点,当移除掉 1 个节点的时候,会导致将改移除节点的请求分配到另外 4 个节点的时候不平衡,哈希计算并不保证平衡。当使用虚拟节点的时候,有更多个节点可分配,会更加均匀的分配到其他节点上。

3

一致性哈希策略的优点

  • 优点:在一些需要将同一个请求参数对应到同个服务的场景下很适合;当对实例进行增加或者删除的时候,能避免引起很大的变动。

4

ConsistentHashLoadBalance 源码

代码语言:javascript
复制
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "consistenthash";

    // 记录所有
    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 methodName = RpcUtils.getMethodName(invocation);
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
        // 计算哈希值 key,通过这个字段来识别出调用实例是否有变化,有变化则重新创建 ConsistentHashSelector
        int identityHashCode = System.identityHashCode(invokers);
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        if (selector == null || selector.identityHashCode != identityHashCode) {
            // 没有存在 selector 或者 invokers 实例有变化,重新创建
            selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }
        return selector.select(invocation);
    }

    private static final class ConsistentHashSelector<T> {

        private final TreeMap<Long, Invoker<T>> virtualInvokers;

        private final int replicaNumber;

        private final int identityHashCode;

        private final int[] argumentIndex;

        ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
            // 虚拟节点
            this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
            this.identityHashCode = identityHashCode;
            URL url = invokers.get(0).getUrl();
            // 虚拟节点数量
            this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
            // 需要哈希的参数,默认是第一个参数
            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) {
                String address = invoker.getUrl().getAddress();
                // 每一个实例存 replicaNumber 份虚拟节点
                for (int i = 0; i < replicaNumber / 4; i++) {
                    // 先将虚拟节点分为 (replicaNumber/4) 份,先做 MD5
                    byte[] digest = md5(address + i);
                    for (int h = 0; h < 4; h++) {
                        //再做哈希计算,得到 m,作为 Key
                        long m = hash(digest, h);
                        // 存到虚拟节点
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

        public Invoker<T> select(Invocation invocation) {
            // 得出要进行哈希的字符串
            String key = toKey(invocation.getArguments());
            // 进行 MD5
            byte[] digest = md5(key);
            return selectForKey(hash(digest, 0));
        }

        // 根据 hash.arguments 配置的对多少个参数进行哈希,得出拼接后的字符串
        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();
        }

        // 选择实例
        private Invoker<T> selectForKey(long hash) {
            // 找出大于或等于 hash 中最小的键值对
            Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
            if (entry == null) {
                // 找出最小的键值对
                entry = virtualInvokers.firstEntry();
            }
            return entry.getValue();
        }

        // 计算哈希值
        private long hash(byte[] digest, int number) {
            return (((long) (digest[3 + number * 4] & 0xFF) << 24)
                    | ((long) (digest[2 + number * 4] & 0xFF) << 16)
                    | ((long) (digest[1 + number * 4] & 0xFF) << 8)
                    | (digest[number * 4] & 0xFF))
                    & 0xFFFFFFFFL;
        }

        // MD5 加密
        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();
        }

    }

}

参考文章:

https://web.archive.org/web/20120605030524/http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent_hash.html

https://blog.csdn.net/cywosp/article/details/23397179/

http://www.zsythink.net/archives/1182

做个有梦想的程序猿

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 LieBrother 微信公众号,前往查看

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

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

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