MyRPC版本5

RPC的概念

背景知识
  • RPC的基本概念,核心功能

image-20200805001037799

常见的RPC框架

Duboo基本功能
  1. 远程通讯
  2. 基于接口方法的透明远程过程调用
  3. 负载均衡
  4. 服务注册中心
RPC过程

client 调用远程方法-> request序列化 -> 协议编码 -> 网络传输-> 服务端 -> 反序列化request -> 调用本地方法得到response -> 序列化 ->编码->……


版本迭代过程

目录

从0开始的RPC的迭代过程:

  • version0版本:以不到百行的代码完成一个RPC例子
  • version1版本:完善通用消息格式(request,response),客户端的动态代理完成对request消息格式的封装
  • version2版本:支持服务端暴露多个服务接口, 服务端程序抽象化,规范化
  • version3版本:使用高性能网络框架netty的实现网络通信,以及客户端代码的重构
  • version4版本:自定义消息格式,支持多种序列化方式(java原生, json…)
  • version5版本: 服务器注册与发现的实现,zookeeper作为注册中心
  • version6版本: 负载均衡的策略的实现

5.MyRPC版本5

背景知识
  • zookeeper安装, 基本概念
  • 了解curator开源zookeeper客户端中的使用
本节问题
  • 如何设计一个注册中心

注册中心(如zookeeper)的地址是固定的(为了高可用一般是集群,我们看做黑盒即可), 服务端上线时,在注册中心注册自己的服务与对应的地址,而客户端调用服务时,去注册中心根据服务名找到对应的服务端地址。

zookeeper我们可以近似看作一个树形目录文件系统,是一个分布式协调应用,其它注册中心有EureKa, Nacos等

升级过程

前提

  1. 下载解压Zookeeper [地址](https://zookeeper.apache.org/releases.html)
  2. 学习一个zookeeper启动的例子 官方例子
    1. zoo.cfg 修改dataDir为一个存在目录
    2. windows启动命令: bin/zkServer.cmd
  3. java项目中引入Curator客户端,
<!--这个jar包应该依赖log4j,不引入log4j会有控制台会有warn,但不影响正常使用-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.1.0</version>
</dependency>

更新1 : 引入zookeeper作为注册中心

启动本地zookeeper服务端,默认端口2181。zookeeper客户端测试如下:

image-20200810105040168

先定义服务注册接口

// 服务注册接口,两大基本功能,注册:保存服务与地址。 查询:根据服务名查找地址
public interface ServiceRegister {
    void register(String serviceName, InetSocketAddress serverAddress);
    InetSocketAddress serviceDiscovery(String serviceName);
}

zookeeper服务注册接口的实现类

public class ZkServiceRegister implements ServiceRegister{
    // curator 提供的zookeeper客户端
    private CuratorFramework client;
    // zookeeper根路径节点
    private static final String ROOT_PATH = "MyRPC";
    // 这里负责zookeeper客户端的初始化,并与zookeeper服务端建立连接
    public ZkServiceRegister(){
        // 指数时间重试
        RetryPolicy policy = new ExponentialBackoffRetry(1000, 3);
        // zookeeper的地址固定,不管是服务提供者还是,消费者都要与之建立连接
        // sessionTimeoutMs 与 zoo.cfg中的tickTime 有关系,
        // zk还会根据minSessionTimeout与maxSessionTimeout两个参数重新调整最后的超时值。默认分别为tickTime 的2倍和20倍
        // 使用心跳监听状态
        this.client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
                .sessionTimeoutMs(40000).retryPolicy(policy).namespace(ROOT_PATH).build();
        this.client.start();
        System.out.println("zookeeper 连接成功");
    }
    @Override
    public void register(String serviceName, InetSocketAddress serverAddress){
        try {
            // serviceName创建成永久节点,服务提供者下线时,不删服务名,只删地址
            if(client.checkExists().forPath("/" + serviceName) == null){
               client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/" + serviceName); 
            }
            // 路径地址,一个/代表一个节点
            String path = "/" + serviceName +"/"+ getServiceAddress(serverAddress);
            // 临时节点,服务器下线就删除节点
            client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
        } catch (Exception e) {
            System.out.println("此服务已存在");
        }
    }
    // 根据服务名返回地址
    @Override
    public InetSocketAddress serviceDiscovery(String serviceName) {
        try {
            List<String> strings = client.getChildren().forPath("/" + serviceName);
            // 这里默认用的第一个,后面加负载均衡
            String string = strings.get(0);
            return parseAddress(string);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    // 地址 -> XXX.XXX.XXX.XXX:port 字符串
    private String getServiceAddress(InetSocketAddress serverAddress) {
        return serverAddress.getHostName() +
                ":" +
                serverAddress.getPort();
    }
    // 字符串解析为地址
    private InetSocketAddress parseAddress(String address) {
        String[] result = address.split(":");
        return new InetSocketAddress(result[0], Integer.parseInt(result[1]));
    }
}

更新2: 更新客户端得到服务器的方式, 服务端暴露服务时,注册到注册中心

首先new client不需要传入host与name, 而在发送request时,从注册中心获得

// 不需传host,port
RPCClient rpcClient = new NettyRPCClient();

客户端的改造

public class SimpleRPCClient implements RPCClient {
    private String host;
    private int port;
    private ServiceRegister serviceRegister;
    public SimpleRPCClient() {
        // 初始化注册中心,建立连接
        this.serviceRegister = new ZkServiceRegister();
    }
    // 客户端发起一次请求调用,Socket建立连接,发起请求Request,得到响应Response
    // 这里的request是封装好的,不同的service需要进行不同的封装, 客户端只知道Service接口,需要一层动态代理根据反射封装不同的Service
    public RPCResponse sendRequest(RPCRequest request) {
        // 从注册中心获取host,port
        InetSocketAddress address = serviceRegister.serviceDiscovery(request.getInterfaceName());
        host = address.getHostName();
        port = address.getPort();
        
        try {
            Socket socket = new Socket(host, port);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            System.out.println(request);
            objectOutputStream.writeObject(request);
            objectOutputStream.flush();
            RPCResponse response = (RPCResponse) objectInputStream.readObject();
            //System.out.println(response.getData());
            return response;
        } catch (IOException | ClassNotFoundException e) {
            System.out.println();
            return null;
        }
    }
}

服务端的改造:服务端反而需要把自己的ip,端口给注册中心

ServiceProvider serviceProvider = new ServiceProvider("127.0.0.1", 8899);

在服务暴露类加入注册的功能

public class ServiceProvider {
    /**
     * 一个实现类可能实现多个服务接口,
     */
    private Map<String, Object> interfaceProvider;
    private ServiceRegister serviceRegister;
    private String host;
    private int port;
    public ServiceProvider(String host, int port){
        // 需要传入服务端自身的服务的网络地址
        this.host = host;
        this.port = port;
        this.interfaceProvider = new HashMap<>();
        this.serviceRegister = new ZkServiceRegister();
    }
    public void provideServiceInterface(Object service){
        Class<?>[] interfaces = service.getClass().getInterfaces();
        for(Class clazz : interfaces){
            // 本机的映射表
            interfaceProvider.put(clazz.getName(),service);
            // 在注册中心注册服务
            serviceRegister.register(clazz.getName(),new InetSocketAddress(host,port));
        }
    }
    public Object getService(String interfaceName){
        return interfaceProvider.get(interfaceName);
    }
}
结果

成功运行!

image-20200810113516390

总结

此版本中我们加入了注册中心,终于一个完整的RPC框架三个角色都有了:服务提供者,服务消费者,注册中心

此版本最大痛点
  • 根据服务名查询地址时,我们返回的总是第一个IP,导致这个提供者压力巨大,而其它提供者调用不到

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值