栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

MyRPC版本5

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

MyRPC版本5

RPC的概念 背景知识

RPC的基本概念,核心功能

常见的RPC框架

Duboo基本功能
    远程通讯基于接口方法的透明远程过程调用负载均衡服务注册中心
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等

升级过程

前提

    下载解压Zookeeper [地址](https://zookeeper.apache.org/releases.html)学习一个zookeeper启动的例子 官方例子
      zoo.cfg 修改dataDir为一个存在目录windows启动命令: bin/zkServer.cmd
    java项目中引入Curator客户端,

    org.apache.curator
    curator-recipes
    5.1.0

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

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

先定义服务注册接口

// 服务注册接口,两大基本功能,注册:保存服务与地址。 查询:根据服务名查找地址
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 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 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);
    }
}
结果

成功运行!

总结

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

此版本最大痛点

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


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/737561.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号