RPC项目一致性Hash算法负载均衡策略实现(Java代码)

        RPC 框架的负载均衡与 Web 服务的负载均衡的不同之处在于:RPC 框架并不是依赖一个负载均衡设备或者负载均衡服务器来实现负载均衡的,而是由 RPC 框架本身实现的,服务调用者可以自主选择服务节点,发起服务调用。RPC 框架不再需要依赖专门的负载均衡设备,可以节约成本;还减少了与负载均衡设备间额外的网络传输,提升了传输效率;并且均衡策略可配,便于服务治理。

        那么具体在我们的RPC项目中如何使用呢?首先我们定义一个负载均衡接口:

public interface LoadBalancer {
    /**
     *从一系列Instance中选择一个
    */
    Instance select(List<Instance> instances);
}

        我们先来实现两种最简单的负载均衡算法:轮询和随机

public class RandomLoadBalancer implements LoadBalancer {
    //随机算法
    @Override
    public Instance select(List<Instance> instances) {
        return instances.get(new Random().nextInt(instances.size()));
    }
}
public class RoundRobinLoadBalancer implements LoadBalancer {
    //轮询算法
    private int index = 0;
    @Override
    public Instance select(List<Instance> instances) {
        if(index >= instances.size()) {
            index %= instances.size();
        }
        return instances.get(index++);
    }
}

        可以看到随机算法借用了Random,从集群中随机选择单个rpc调用实例。而轮询算法按照顺序依次选择第一个、第二个、第三个……最后又回到第一个重新开始。

        在进行服务调用的时候,调用方可自己选择负载均衡算法 ,默认实现的是随机负载均衡:

public class NacosServiceDiscovery implements ServiceDiscovery {

    private static final Logger logger = LoggerFactory.getLogger(NacosServiceDiscovery.class);

    private final LoadBalancer loadBalancer;

    public NacosServiceDiscovery(LoadBalancer loadBalancer) {
        if(loadBalancer == null) {
            this.loadBalancer = new RandomLoadBalancer();
        }else{
            this.loadBalancer = loadBalancer;
        }
    }
    @Override
    public InetSocketAddress lookupService(String serviceName) {
        try {
            List<Instance> instances = NacosUtil.getAllInstance(serviceName);
            if(instances.size() == 0) {
                logger.error("找不到对应的服务: " + serviceName);
                throw new RpcException(RpcError.SERVICE_NOT_FOUND);
            }
            Instance instance = loadBalancer.select(instances);
            return new InetSocketAddress(instance.getIp(), instance.getPort());
        } catch (NacosException e) {
            logger.error("获取服务时有错误发生:", e);
        }
        return null;
    }

}

         最后来看我们的重头戏,一致性哈希环算法的实现!!关于一致性hash算法的原理,本文不做概述。本文说的是如何在本项目中用Java实现一致性Hash算法,大家可以先了解一下该算法以及项目注册中心的内容:

(3条消息) 每天进步一点点——五分钟理解一致性哈希算法(consistent hashing)_cywosp的博客-CSDN博客_一致性hash算法

        (1条消息) 自定义RPC项目——常见问题及详解(注册中心)_李孛欢的博客-CSDN博客

        代码如下:

public class IpHashLoadBalancer implements LoadBalancer{
    @Override
    public Instance select(List<Instance> instances , String address) {
        ConsistentHash ch = new ConsistentHash(instances, 100);
        HashMap<String,Instance> map = ch.map;
        return map.get(ch.getServer(address));
    }
}
//一致性hash 利用treemap实现
class ConsistentHash {
    //TreeMap中的key表示服务器的hash值,value表示服务器。模拟一个哈希环
    private static TreeMap<Integer,String> Nodes = new TreeMap();
    private static int VIRTUAL_NODES = 160;//虚拟节点个数,用户指定,默认160
    private static List<Instance> instances = new ArrayList<>();//真实物理节点集合
    public ConsistentHash(List<Instance> instances, int VIRTUAL_NODES){
        this.instances = instances;
        this.VIRTUAL_NODES = VIRTUAL_NODES;
    }

    public static HashMap<String,Instance> map = new HashMap<>();//将服务实例与ip地址一一映射
    //预处理 形成哈希环
    static {
        //程序初始化,将所有的服务器(真实节点与虚拟节点)放入Nodes(底层为红黑树)中
        for (Instance instance : instances) {
            String ip = instance.getIp();
            Nodes.put(getHash(ip),ip);
            map.put(ip,instance);
            for(int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip+"#"+i);
                Nodes.put(hash,ip);
            }
        }
    }
    //得到Ip地址
    public  String getServer(String clientInfo) {
        int hash = getHash(clientInfo);
        //得到大于该Hash值的子红黑树
        SortedMap<Integer,String> subMap = Nodes.tailMap(hash);
        //获取该子树最小元素
        Integer nodeIndex = subMap.firstKey();
        //没有大于该元素的子树 取整树的第一个元素
        if (nodeIndex == null) {
            nodeIndex = Nodes.firstKey();
        }
        return Nodes.get(nodeIndex);
    }
    //使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash^str.charAt(i))*p;
            hash +=hash <<13;
            hash ^=hash >>7;
            hash +=hash <<3;
            hash ^=hash >>17;
            hash +=hash <<5;
            //如果算出来的值为负数 取其绝对值
            if(hash < 0) {
                hash = Math.abs(hash);
            }
        }
        return hash;
    }
}

         注意要点:

  1. 我们这里的LoadBalancer 接口的抽象方法内多增加了一个参数Address,这是因为我们之前实现的轮询和随机算法与客户端的地址无关,而我们这里是同一个客户端在服务端没有发生变化的前提下都要打到同一台服务器上,这与客户端的地址有关。
  2. 哈希环采用TreeMap模拟实现,在TreeMap的api中有一个tailMap() 函数,输入一个fromKey,输出的是一个SortedMap有序Map,再使用firstKey()拿到最近一个大于客户端哈希值的元素。
  3. 用虚拟节点来防止某个节点的流量过大而导致的一些问题
  4. 使用Hashmap将服务实例和ip地址对应,因为getService()得到的是ip地址,而我们最后需要返回的是instance,可以看到,每个真实节点和其对应的虚拟节点均对应着同一个instance
  5. 实例化一致性哈希环时需要传入实例列表Instances和每个实例对应的虚拟节点个数,更贴切这个项目。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
一致性哈希是一种常见的分布式哈希算法,主要用于解决缓存、负载均衡、分布式数据库等领域的问题。下面是一个简单的 Java 代码实现一致性哈希算法的示例: ```java import java.util.*; public class ConsistentHash { private final SortedMap<Integer, String> circle = new TreeMap<>(); private final int virtualNodes; public ConsistentHash(int virtualNodes, Collection<String> nodes) { this.virtualNodes = virtualNodes; for (String node : nodes) { addNode(node); } } public void addNode(String node) { for (int i = 0; i < virtualNodes; i++) { String virtualNode = node + "#" + i; int hash = getHash(virtualNode); circle.put(hash, node); } } public void removeNode(String node) { for (int i = 0; i < virtualNodes; i++) { String virtualNode = node + "#" + i; int hash = getHash(virtualNode); circle.remove(hash); } } public String getNode(String key) { if (circle.isEmpty()) { return null; } int hash = getHash(key); if (!circle.containsKey(hash)) { SortedMap<Integer, String> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } return circle.get(hash); } private int getHash(String key) { // TODO: 实现自己的哈希算法 return key.hashCode(); } } ``` 上述代码中,我们通过 `SortedMap` 维护了一个环形空间,并将每个节点对应的虚拟节点散落在这个空间上。在查找节点时,首先计算出键值的哈希值,然后查找距离这个哈希值最近的节点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李孛欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值