欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

MyRPC版本5

时间:2023-06-07
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,portRPCClient 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,导致这个提供者压力巨大,而其它提供者调用不到


Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。