从头开始写一个简单RPC——第2天

gitee地址

项目基于guide-rpc-framwork 拆解,完整框架请参考guide-rpc-framwork

上篇地址:day1

本篇主要内容:

  • 使用zk 替代本地文件作为注册中心
  • 实现简单负载均衡
使用zk 替代本地文件作为注册中心

在之前的版本中,我们抽象出了一个注册中心的接口,并且提供一个本地文件注册中心的实现,这次,我们使用zk 作为注册中心,实现服务的注册与发现,并且监听服务实例状态的变化。

这里我们使用apache curator 作为zk 客户端,依赖如下:

<curator-version>4.2.0</curator-version>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>${curator-version}</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>${curator-version}</version>
</dependency>

代码如下:

/**
 * zk 服务注册中心
 */
public class ZkServiceRegister implements ServiceRegister{
    private static CuratorFramework zkClient;
    private static String zkUrl = "127.0.0.1:2181";
    private static final String BASE_SERVICE_PATH = "/simpleRpc/service/";
    private static ServiceLoadBalance serviceLoadBalance;
    private static Map<String, List<String>> serviceAddressMap = new ConcurrentHashMap();
    static {
        // 从配置文件中获取zkurl,简单实现默认开发环境的地址
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        zkClient = CuratorFrameworkFactory.newClient(zkUrl, retryPolicy);
        zkClient.start();
        // 从配置文件中获取负载均衡策略,简单实现为随机负载
        serviceLoadBalance = new RandomServiceLoadBalance();
    }

    @Override
    public void regist(String serviceName) {
        try {
            //  /simpleRpc/service/HelloService/127.0.0.1:12306
            String servicePath = BASE_SERVICE_PATH + serviceName + "/" + InetAddress.getLocalHost().getHostAddress() + ":" + NettyRpcServer.PORT;
            zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(servicePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 本地应该做一层缓存,并且监听zk 节点变化,有变化时接收到通知修改本地缓存
     * @param serviceName
     * @return
     */
    @Override
    public InetSocketAddress lookup(String serviceName) {
        //  /simpleRpc/service/HelloService/127.0.0.1:12306
        //  /simpleRpc/service/HelloService/127.0.0.1:12345
        // 比如有如上一个服务有两个实例,需要负载均衡
        String serviceDir = BASE_SERVICE_PATH + serviceName;
        try {
            List<String> services = getAllAddress(serviceDir);
            String address = serviceLoadBalance.selectService(services);
            String[] socketAddressArray = address.split(":");
            String host = socketAddressArray[0];
            int port = Integer.parseInt(socketAddressArray[1]);
            return new InetSocketAddress(host, port);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private List<String> getAllAddress(String serviceDir) throws Exception {
        if(serviceAddressMap.containsKey(serviceDir)) {
            return serviceAddressMap.get(serviceDir);
        }
        //获取服务目录下的所有ip:port
        List<String> addressList = zkClient.getChildren().forPath(serviceDir);
        serviceAddressMap.put(serviceDir, addressList);
        registerWatcher(serviceDir);
        return addressList;
    }

    private void registerWatcher(String serviceDir) throws Exception {
        //监听该目录的节点变化,有变化时接收通知
        PathChildrenCache pathChildrenCache = new PathChildrenCache(zkClient, serviceDir, true);
        PathChildrenCacheListener cacheListener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                List<String> list = client.getChildren().forPath(serviceDir);
                serviceAddressMap.put(serviceDir, list);
            }
        };
        pathChildrenCache.getListenable().addListener(cacheListener);
        pathChildrenCache.start();
    }
}

在静态代码块中,实例化了一个zk 客户端,并且选择了负载均衡策略(简单实现,参数正常应该从配置文件中读取)。

在注册服务方法regist(…) 中,通过zk 客户端创建了临时节点,而非持久节点,主要是考虑到 服务生产者 下线后通知 服务消费者 拉取最新的远程服务实例数据。但这样存在一个问题,如果某段时间 生产者 与zk 由于网络原因,导致暂时下线,然后过了一会网络又通了,此时服务并没有重新注册到zk 上,因此后续优化中,我们需要在生产者重新连接zk 的时候重新注册服务。

在发现服务方法lookup(…) 中,通过当前的远程服务名,去zk 上查找对应目录节点下的所有子节点,即查找当前服务的所有实例,然后通过辅助均衡器选择其中一个实例。
并且,消费者监听该服务名对应的目录节点的子节点变化事件,只要子节点有变动,就能收到通知,对应的场景是有新的服务实例上线或者服务下线,消费者能及时感知,及时进行RPC 调用或者停止RPC 调用。

实现简单负载均衡
public interface ServiceLoadBalance {
    String selectService(List<String> addresses);
}

public class RandomServiceLoadBalance implements ServiceLoadBalance{
    private Random random = new Random();
    @Override
    public String selectService(List<String> addresses) {
        if(addresses == null || addresses.size() == 0) {
            return null;
        }
        if(addresses.size() == 1) {
            return addresses.get(0);
        }
        return addresses.get(random.nextInt(addresses.size()));
    }
}

没啥好说的,,面向接口编程。。。

下集预告

本期内容比较简单,下集更精彩:
1、解决本次提出的问题,生产者暂时下线又上线,服务如何重新注册
2、使用注解方式标记远程服务,扫描并自动注册到zk

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值