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客户端,更新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
更新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
成功运行!
总结此版本中我们加入了注册中心,终于一个完整的RPC框架三个角色都有了:服务提供者,服务消费者,注册中心
此版本最大痛点根据服务名查询地址时,我们返回的总是第一个IP,导致这个提供者压力巨大,而其它提供者调用不到