一、RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

  RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
  二、调用过程
  1.调用客户端句柄;执行传送参数
  2.调用本地系统内核发送网络消息
  3.消息传送到远程主机
  4.服务器句柄得到消息并取得参数
  5.执行远程过程
  6.执行的过程将结果返回服务器句柄
  7.服务器句柄返回结果,调用远程系统内核
  8.消息传回本地主机
  9.客户句柄由内核接收消息
  10.客户接收句柄返回的数据
  三、调用过程,大概就是这样子,这样子来张图简单一点
  

  1、这张图是dubbo官网的模型图

  2、过程:服务——>注册地址到zk

        客户端——>通过zk获取对应服务地址

        代理——>通过接口代理,实现服务调用

        调用方式——>这个不一定了,一般tcp方式比较直接

  四、简易版rpc实现,注册中心用的zk,可以使用其他注册中心

  1)目录结构

  

  2)依赖的jar

      <dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>Zookeeper</artifactId>
<version>3.4.0</version>
</dependency>

  3)zookeeper的连接工具

import org.apache.zookeeper.*;

import java.io.IOException;
import java.util.concurrent.CountDownLatch; public class ZookeeperUtils { private static ZooKeeper zooKeeper = null; public static ZooKeeper connect() throws IOException, InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
//连接zk
zooKeeper = new ZooKeeper("localhost:2182", 60000, watchedEvent -> {
if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
latch.countDown();
}
});
//无连接阻塞
latch.await();
return zooKeeper;
} }

  4)数据传输的实体

import java.io.Serializable;

/**
* 请求需要的数据
*/
public class RpcRequest implements Serializable{
//接口名称,用于反射
private String interfaceName;
//调用方法
private String method;
//参数类型
private Class<?>[] parameterTypes;
//参数
private Object[] params; public String getInterfaceName() {
return interfaceName;
} public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
} public String getMethod() {
return method;
} public void setMethod(String method) {
this.method = method;
} public Class<?>[] getParameterTypes() {
return parameterTypes;
} public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
} public Object[] getParams() {
return params;
} public void setParams(Object[] params) {
this.params = params;
}
}
import java.io.Serializable;

/**
* 相应对象
*/
public class RpcResponse implements Serializable { //返回状态,当然这里可以,加入对应的异常等(这里简化)
private String status;
//返回的数据
private Object data; public String getStatus() {
return status;
} public void setStatus(String status) {
this.status = status;
} public Object getData() {
return data;
} public void setData(Object data) {
this.data = data;
}
}

  5)测试的服务以及实现

import com.pinnet.zookeeper.service.ITestService;

public class TestServiceImpl implements ITestService{

    public String test(String name) {
System.out.println(name);
return "return" + name;
}
}

  6)提供方

import com.pinnet.zookeeper.bean.RpcBeanFactory;
import com.pinnet.zookeeper.data.RpcRequest;
import com.pinnet.zookeeper.data.RpcResponse;
import com.pinnet.zookeeper.service.ITestService;
import com.pinnet.zookeeper.service.impl.TestServiceImpl;
import com.pinnet.zookeeper.zookeeper.ZookeeperUtils;
import org.apache.zookeeper.*; import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket; /**
* rpc服务端
*/
public class Server { //服务器设定的目录
private String registryPath = "/registry";
//接口,这里方便测试用
private String serviceName = ITestService.class.getName();
//地址目录
private static String addressName = "address";
//本地地址
private static String ip = "localhost";
//监听接口
public static Integer port = 8000; //链接zk
public ZooKeeper connect() throws Exception {
ZooKeeper zooKeeper = ZookeeperUtils.connect();
return zooKeeper;
} //创建节点,也就是访问的,目录
public void createNode(ZooKeeper zooKeeper) throws Exception {
if (zooKeeper.exists(registryPath, false) == null) {
//创建永久目录,接口服务,可以创建永久目录
zooKeeper.create(registryPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} String servicePath = registryPath + "/" +serviceName;
if (zooKeeper.exists(servicePath, false) == null) {
//接口目录
zooKeeper.create(servicePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} String addressPath = servicePath + "/" +addressName;
//地址目录,这里ip就是本地的地址,用于tcp链接使用
//这里创建的是临时目录,当zk服务断连过后,自动删除临时节点
zooKeeper.create(addressPath, (ip + ":"+ port).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} //监听过程
private void accept() throws Exception {
//当然这里也可以使用netty来进行监听和其他过程
//这里简化
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
System.out.println("监听中。。。。。。。");
Socket socket = serverSocket.accept();
resultData(socket);
}
} //执行并返回数据
private void resultData(Socket socket) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//读取请求的参数
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
//这里从容器中获取bean,当然这里的bean可以自己缓存,独立spring容器之外
Object bean = RpcBeanFactory.getBean(rpcRequest.getInterfaceName());
//方法调用
Method method = bean.getClass().getMethod(rpcRequest.getMethod(), rpcRequest.getParameterTypes());
Object data = method.invoke(bean, rpcRequest.getParams());
//返回数据
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setStatus("success");
rpcResponse.setData(data);
objectOutputStream.writeObject(rpcResponse);
} public static void main(String[] args) throws Exception {
//模拟spring容器的加载过程
RpcBeanFactory.putBean(ITestService.class.getName(), new TestServiceImpl());
Server server = new Server();
ZooKeeper zooKeeper = server.connect();
//创建节点,用于地址访问
server.createNode(zooKeeper);
//监听,当然多线程更加理想,这里只显示效果
server.accept();
} }

  7)调用方

import com.pinnet.zookeeper.bean.RpcBeanFactory;
import com.pinnet.zookeeper.data.RpcRequest;
import com.pinnet.zookeeper.data.RpcResponse;
import com.pinnet.zookeeper.service.ITestService;
import com.pinnet.zookeeper.zookeeper.ZookeeperUtils;
import org.apache.zookeeper.ZooKeeper; import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* rpc客户端
*/
public class Client { //缓存地址
private Map<String, List<String>> adressMap = new ConcurrentHashMap<>(); //链接zk
public ZooKeeper connect() throws Exception {
return ZookeeperUtils.connect();
} /**
* 这里主要是创建代理,利用代理接口实现来达到调用的目的
* @param interfaceName
* @return
* @throws ClassNotFoundException
*/
public Object createProxy(final String interfaceName) throws ClassNotFoundException {
//使用线程实例化,主要考虑处理性
final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(interfaceName);
//创建代理
return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
//用重连计数
private int num = 5; @Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
//发送请求需要的请求参数
RpcRequest rpcRequest = new RpcRequest();
//接口名称
rpcRequest.setInterfaceName(interfaceName);
//调用方法名
rpcRequest.setMethod(method.getName());
//对应参数类型
rpcRequest.setParameterTypes(method.getParameterTypes());
//对应参数
rpcRequest.setParams(params);
//返回响应结果
return sendData(rpcRequest);
} //tcp方式调用
private Object sendData(RpcRequest rpcRequest) throws Exception {
//当访问地址存在时
if (adressMap.containsKey(interfaceName)) {
List<String> adresses = adressMap.get(interfaceName);
if (adresses != null && !adresses.isEmpty()) {
//如果存在多个地址,使用可以调通的一个
for (String adress:adresses) {
//这个是注册zk的时候设定的数据
String[] strs = adress.split(":");
//这里简易版的实现,所以直接使用的socket.
//实际上可以采用netty框架编写,保存channel访问就可以了,也可以对数据进行加解密
Socket socket = new Socket();
try {
//连接可以访问zk,如果连接失败直接抛出异常,循环下一个地址
socket.connect(new InetSocketAddress(strs[0], Integer.valueOf(strs[1])));
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(rpcRequest);
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//这里是响应数据,也就是执行过后的数据。远程执行结果
RpcResponse rpcResponse = (RpcResponse) objectInputStream.readObject();
return rpcResponse.getData();
} catch (IOException e) {
e.printStackTrace();
}
}
//设置重连机制,跳出循环
if (num == 0) {
throw new RuntimeException("server connect fail");
}
num--;
//如果多个地址还是不能访问,则从zk上面更新地址
getAddress(interfaceName);
//如果多个地址还是不能访问,则重新访问
sendData(rpcRequest);
}
throw new RuntimeException("not found service");
}else {
//如果没有地址存储时的访问,主要是请求链接问题
if (num == 0) {
throw new RuntimeException("not found server");
}
num--;
getAddress(interfaceName);
return sendData(rpcRequest);
}
}
});
} //从zk获取最新的地址
private void getAddress(String interfaceName) throws Exception {
//链接zk
ZooKeeper zooKeeper = connect();
//设定的地址目录
String interfacePath = "/registry/" + interfaceName;
//获取地址目录
List<String> addresses = zooKeeper.getChildren(interfacePath, false);
if (addresses != null && !addresses.isEmpty()) {
List<String> datas = new ArrayList<>();
for (String address:addresses) {
//获取数据,也就是配置对应的访问地址
byte[] bytes = zooKeeper.getData(interfacePath + "/" + address, false, null);
if (bytes.length > 0) {
//放入数组
datas.add(new String(bytes));
}
}
//加入缓存
adressMap.put(interfaceName, datas);
}
} public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
Client client = new Client();
//这一步是模拟spring容器放入bean的过程,实际用spring容器可自定义标签实现
RpcBeanFactory.putBean("testService", client.createProxy(ITestService.class.getName()));
//获取bean的过程,实际上实现是代理接口的实现
ITestService testService = (ITestService) RpcBeanFactory.getBean("testService");
//调用方法,也就是调用代理的过程
String test = testService.test("test");
System.out.println(test);
}
}

  五、代码部分就这么多,看一下测试结果

  服务端:

  

  客户端:

  

  zookeeper的展示部分

  

  六、源码分享

  rpc-zk代码:https://github.com/lilin409546297/rpc-zk

  zkui代码:https://github.com/lilin409546297/zkui(这个是别人做的,我放在github的)

  zookeeper下载地址:http://apache.org/dist/zookeeper/

rpc简易实现-zookeeper的更多相关文章

  1. 基于Netty的RPC简易实现

    代码地址如下:http://www.demodashi.com/demo/13448.html 可以给你提供思路 也可以让你学到Netty相关的知识 当然,这只是一种实现方式 需求 看下图,其实这个项 ...

  2. 手写RPC框架(netty+zookeeper)

    RPC是什么?远程过程调用,过程就是业务处理.计算任务,像调用本地方法一样调用远程的过程. RMI和RPC的区别是什么?RMI是远程方法调用,是oop领域中RPC的一种实现,我们熟悉的restfull ...

  3. 老王讲自制RPC框架.(三.ZOOKEEPER)

    (#)定义Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务.状态同步服务.集群管理.分布式应用配 ...

  4. RPC简易学习

    0.RPC简介 RPC,   英文全称:Remote Process Call.   中文全称:远程过程调用. 客户端通过网络请求调用远程服务端对外暴露服务.常用的两种RPC协议:TCP.HTTP. ...

  5. 手撕RPC框架

    手撕RPC 使用Netty+Zookeeper+Spring实现简易的RPC框架.阅读本文需要有一些Netty使用基础. 服务信息在网络传输,需要讲服务类进行序列化,服务端使用Spring作为容器.服 ...

  6. RPC远程过程调用学习之路(一):用最原始代码还原PRC框架

    RPC: Remote Procedure Call 远程过程调用,即业务的具体实现不是在自己系统中,需要从其他系统中进行调用实现,所以在系统间进行数据交互时经常使用. rpc的实现方式有很多,可以通 ...

  7. zookeeper在dubbo中干什么

    本文旨在表述出自己对于zookeeper在dubbo的作用的初步理解 在对dubbo进行了初步的探索后,对于zookeeper在其中的作用不甚了解,因为本身对zookeeper就没有一个特别具体的概念 ...

  8. zookeeper介绍以及安装配置

    Zookeeper启动时默认将Zookeeper.out输出到当前目录,不友好.改变位置有两种方法: 1:在当前用户下~/.bash_profile或在/etc/profile,添加ZOO_LOG_D ...

  9. RabbitMQ、RPC、SaltStack "贡"具的使用

    消息队列 使用队列的场景 在程序系统中,例如外卖系统,订单系统,库存系统,优先级较高 发红包,发邮件,发短信,app消息推送等任务优先级很低,很适合交给消息队列去处理,以便于程序系统更快的处理其他请求 ...

随机推荐

  1. ZOJ 3541 The Last Puzzle(经典区间dp)

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3541 题意:有一排开关,有个开关有两个值t和d,t是按下开关后在t秒后会自 ...

  2. TimerPickerDialog 中 onTimeSet 执行两次的问题

    开发android小闹钟的程序时,在添加闹钟时闹钟列表中总是出现两个相同的闹钟. btnAddAlarm.setOnClickListener(new View.OnClickListener() { ...

  3. hdu 2444 The Accomodation of Students 判断二分图+二分匹配

    The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ( ...

  4. Singleton(单例)

    意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 适用性: 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时. 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代 ...

  5. Java Spring-事务管理

    2017-11-12 16:31:59 Spring的事务管理分为两种: 编程式的事务管理:手动编写代码 声明式的事务管理:只需要配置就可以 一.最初的环境搭建 public interface Ac ...

  6. 更改CentOS7登录画面的分辨率

    设置成用VNC Viewer去连接虚拟机的CentOS7 , 可是分辨率都得等登录了才能生效. 登录画面显示时,分辨率老大了. 找到了下面的文章 , 把 home/<user>/.conf ...

  7. 2-10~2-11 配置iptables防火墙增强服务 selinux简单讲解

    学习一个服务的过程: 1.此服务器的概述:名字,功能,特点,端口号 2.安装 3.配置文件的位置 4.服务启动关闭脚本,查看端口 5.此服务的使用方法 6.修改配置文件,实战举例 7.排错(从下到上, ...

  8. 牛客网——E进阶吧阶乘

    链接:https://www.nowcoder.net/acm/contest/75/E来源:牛客网 时间限制:C/C++ 3秒,其他语言6秒 空间限制:C/C++ 32768K,其他语言65536K ...

  9. 十款效果惊艳的Html案例(一)

    http://www.html5tricks.com/10-html5-image-effect.html

  10. bzoj1068

    题意: 给你一个未压缩串,要求你把它压缩 问你压缩后最小长度 题解: 区间dp 怎么少就怎么来 代码: #include<bits/stdc++.h> using namespace st ...