一致性哈希做负载均衡,基于dubbo的简化版本,超级简单容易理解!!!

如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。一致性哈希做负载均衡,基于dubbo的简化版本,超级简单容易理解!!!,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一致性哈希算法原理以及做分布式存储。一定先看:一致性哈希算法

dubbo提供了四种负载均衡实现:权重随机算法最少活跃调用数算法一致性哈希算法加权轮询算法

本文基于开源项目:guide-rpc-framework的一致性哈希算法做的负载均衡,这个项目的负载均衡是dubbo一致性哈希的简化版。

代码如下:

/**
 * refer to dubbo consistent hash load balance: https://github.com/apache/dubbo/blob/2d9583adf26a2d8bd6fb646243a9fe80a77e65d5/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/ConsistentHashLoadBalance.java
 *
 * @author RicardoZ
 * @createTime 2020年10月20日 18:15:20
 */
@Slf4j
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    private final ConcurrentHashMap<String, ConsistentHashSelector> selectors = new ConcurrentHashMap<>();

    @Override
    protected String doSelect(List<String> serviceAddresses, String rpcServiceName) {
        int identityHashCode = System.identityHashCode(serviceAddresses);

        ConsistentHashSelector selector = selectors.get(rpcServiceName);

        // check for updates
        if (selector == null || selector.identityHashCode != identityHashCode) {
            selectors.put(rpcServiceName, new ConsistentHashSelector(serviceAddresses, 160, identityHashCode));
            selector = selectors.get(rpcServiceName);
        }

        return selector.select(rpcServiceName);
    }

    static class ConsistentHashSelector {
        private final TreeMap<Long, String> virtualInvokers;

        private final int identityHashCode;

        ConsistentHashSelector(List<String> invokers, int replicaNumber, int identityHashCode) {
            this.virtualInvokers = new TreeMap<>();
            this.identityHashCode = identityHashCode;

            for (String invoker : invokers) {
                for (int i = 0; i < replicaNumber / 4; i++) {
                    byte[] digest = md5(invoker + i);
                    for (int h = 0; h < 4; h++) {
                        long m = hash(digest, h);
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

        static byte[] md5(String key) {
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("MD5");
                byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
                md.update(bytes);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }

            return md.digest();
        }

        static long hash(byte[] digest, int idx) {
            return ((long) (digest[3 + idx * 4] & 255) << 24 | (long) (digest[2 + idx * 4] & 255) << 16 | (long) (digest[1 + idx * 4] & 255) << 8 | (long) (digest[idx * 4] & 255)) & 4294967295L;
        }

        public String select(String rpcServiceName) {
            byte[] digest = md5(rpcServiceName);
            return selectForKey(hash(digest, 0));
        }

        public String selectForKey(long hashCode) {
            Map.Entry<Long, String> entry = virtualInvokers.tailMap(hashCode, true).firstEntry();

            if (entry == null) {
                entry = virtualInvokers.firstEntry();
            }

            return entry.getValue();
        }
    }
}

客户端要调用服务的名字即为rpcServiceName,客户端要通过服务名发送请求之前,先进行负载均衡,通过负载均衡找到合适的服务器ip地址,然后依据此ip地址发送请求。

分析ConsistentHashSelector这个类可以将多个服务器ip地址放到环形hash空间上,然后通过服务名找到一个ip地址。

那什么存储数据呢?virtualInvokers用来模拟环形hash空间用来放置ip地址和服务名。

invokers为ip地址列表。

        ConsistentHashSelector(List<String> invokers, int replicaNumber, int identityHashCode) {
            this.virtualInvokers = new TreeMap<>();
            this.identityHashCode = identityHashCode;

            // 对于每一个ip地址
            for (String invoker : invokers) {
                // 针对每一个ip地址创建(replicaNumber / 4)个重复节点
                for (int i = 0; i < replicaNumber / 4; i++) {
                    byte[] digest = md5(invoker + i);
                    // 针对每个一个重复节点将其等间隔的分布在环形hash空间上
                    for (int h = 0; h < 4; h++) {
                        // 计算节点hash值
                        long m = hash(digest, h);
                        // 将节点放到环形hash空间上
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

我们知道TreeMap不是环形的,他就是一个用来放东西的容器,这里只是为了迎合一致性哈希算法的概念中的环形hash空间。

比如说replicaNumber的值为8,那么就会有两个重复节点,对于每个重复节点又会计算4个hash值,如此一来在环形hash空间上也就是TreeMap上,会有2乘4个也就是8个value相同(value就是ip地址),而hash值不同的键值对。

如下图(为了简单起见,hash值就都是两位数以内了哈~~)

一致性哈希做负载均衡,基于dubbo的简化版本,超级简单容易理解!!!

现在我们就把这些服务器的ip地址都安置好了。

那么接下来就是让rpcServiceName依据自己的hash值顺时针在环形hash空间上找到第一个离他最近的ip地址啦。

一致性哈希做负载均衡,基于dubbo的简化版本,超级简单容易理解!!!

代码如何实现的顺时针寻找第一个最近的ip地址节点呢?

        public String selectForKey(long hashCode) {
            Map.Entry<Long, String> entry = virtualInvokers.tailMap(hashCode, true).firstEntry();

            if (entry == null) {
                entry = virtualInvokers.firstEntry();
            }

            return entry.getValue();
        }

通过TreeMap的tailMap方法可以进行顺时针寻找。通过firstEntry可以找到第一个最近的ip地址节点。

如此对于不同的服务则会根据自己的hash值去顺时针寻找离自己最近的服务器的ip地址。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/195937.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!