一、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

  1.   <dependency>
  2. <groupId>org.apache.zookeeper</groupId>
  3. <artifactId>Zookeeper</artifactId>
  4. <version>3.4.0</version>
  5. </dependency>

  3)zookeeper的连接工具

  1. import org.apache.zookeeper.*;
  2.  
  3. import java.io.IOException;
  4. import java.util.concurrent.CountDownLatch;
  5.  
  6. public class ZookeeperUtils {
  7.  
  8. private static ZooKeeper zooKeeper = null;
  9.  
  10. public static ZooKeeper connect() throws IOException, InterruptedException {
  11. CountDownLatch latch = new CountDownLatch(1);
  12. //连接zk
  13. zooKeeper = new ZooKeeper("localhost:2182", 60000, watchedEvent -> {
  14. if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
  15. latch.countDown();
  16. }
  17. });
  18. //无连接阻塞
  19. latch.await();
  20. return zooKeeper;
  21. }
  22.  
  23. }

  4)数据传输的实体

  1. import java.io.Serializable;
  2.  
  3. /**
  4. * 请求需要的数据
  5. */
  6. public class RpcRequest implements Serializable{
  7. //接口名称,用于反射
  8. private String interfaceName;
  9. //调用方法
  10. private String method;
  11. //参数类型
  12. private Class<?>[] parameterTypes;
  13. //参数
  14. private Object[] params;
  15.  
  16. public String getInterfaceName() {
  17. return interfaceName;
  18. }
  19.  
  20. public void setInterfaceName(String interfaceName) {
  21. this.interfaceName = interfaceName;
  22. }
  23.  
  24. public String getMethod() {
  25. return method;
  26. }
  27.  
  28. public void setMethod(String method) {
  29. this.method = method;
  30. }
  31.  
  32. public Class<?>[] getParameterTypes() {
  33. return parameterTypes;
  34. }
  35.  
  36. public void setParameterTypes(Class<?>[] parameterTypes) {
  37. this.parameterTypes = parameterTypes;
  38. }
  39.  
  40. public Object[] getParams() {
  41. return params;
  42. }
  43.  
  44. public void setParams(Object[] params) {
  45. this.params = params;
  46. }
  47. }
  1. import java.io.Serializable;
  2.  
  3. /**
  4. * 相应对象
  5. */
  6. public class RpcResponse implements Serializable {
  7.  
  8. //返回状态,当然这里可以,加入对应的异常等(这里简化)
  9. private String status;
  10. //返回的数据
  11. private Object data;
  12.  
  13. public String getStatus() {
  14. return status;
  15. }
  16.  
  17. public void setStatus(String status) {
  18. this.status = status;
  19. }
  20.  
  21. public Object getData() {
  22. return data;
  23. }
  24.  
  25. public void setData(Object data) {
  26. this.data = data;
  27. }
  28. }

  5)测试的服务以及实现

  1. import com.pinnet.zookeeper.service.ITestService;
  2.  
  3. public class TestServiceImpl implements ITestService{
  4.  
  5. public String test(String name) {
  6. System.out.println(name);
  7. return "return" + name;
  8. }
  9. }

  6)提供方

  1. import com.pinnet.zookeeper.bean.RpcBeanFactory;
  2. import com.pinnet.zookeeper.data.RpcRequest;
  3. import com.pinnet.zookeeper.data.RpcResponse;
  4. import com.pinnet.zookeeper.service.ITestService;
  5. import com.pinnet.zookeeper.service.impl.TestServiceImpl;
  6. import com.pinnet.zookeeper.zookeeper.ZookeeperUtils;
  7. import org.apache.zookeeper.*;
  8.  
  9. import java.io.IOException;
  10. import java.io.ObjectInputStream;
  11. import java.io.ObjectOutputStream;
  12. import java.lang.reflect.InvocationTargetException;
  13. import java.lang.reflect.Method;
  14. import java.net.ServerSocket;
  15. import java.net.Socket;
  16.  
  17. /**
  18. * rpc服务端
  19. */
  20. public class Server {
  21.  
  22. //服务器设定的目录
  23. private String registryPath = "/registry";
  24. //接口,这里方便测试用
  25. private String serviceName = ITestService.class.getName();
  26. //地址目录
  27. private static String addressName = "address";
  28. //本地地址
  29. private static String ip = "localhost";
  30. //监听接口
  31. public static Integer port = 8000;
  32.  
  33. //链接zk
  34. public ZooKeeper connect() throws Exception {
  35. ZooKeeper zooKeeper = ZookeeperUtils.connect();
  36. return zooKeeper;
  37. }
  38.  
  39. //创建节点,也就是访问的,目录
  40. public void createNode(ZooKeeper zooKeeper) throws Exception {
  41. if (zooKeeper.exists(registryPath, false) == null) {
  42. //创建永久目录,接口服务,可以创建永久目录
  43. zooKeeper.create(registryPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  44. }
  45.  
  46. String servicePath = registryPath + "/" +serviceName;
  47. if (zooKeeper.exists(servicePath, false) == null) {
  48. //接口目录
  49. zooKeeper.create(servicePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  50. }
  51.  
  52. String addressPath = servicePath + "/" +addressName;
  53. //地址目录,这里ip就是本地的地址,用于tcp链接使用
  54. //这里创建的是临时目录,当zk服务断连过后,自动删除临时节点
  55. zooKeeper.create(addressPath, (ip + ":"+ port).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  56. }
  57.  
  58. //监听过程
  59. private void accept() throws Exception {
  60. //当然这里也可以使用netty来进行监听和其他过程
  61. //这里简化
  62. ServerSocket serverSocket = new ServerSocket(port);
  63. while (true) {
  64. System.out.println("监听中。。。。。。。");
  65. Socket socket = serverSocket.accept();
  66. resultData(socket);
  67. }
  68. }
  69.  
  70. //执行并返回数据
  71. private void resultData(Socket socket) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  72. ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
  73. //读取请求的参数
  74. RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
  75. //这里从容器中获取bean,当然这里的bean可以自己缓存,独立spring容器之外
  76. Object bean = RpcBeanFactory.getBean(rpcRequest.getInterfaceName());
  77. //方法调用
  78. Method method = bean.getClass().getMethod(rpcRequest.getMethod(), rpcRequest.getParameterTypes());
  79. Object data = method.invoke(bean, rpcRequest.getParams());
  80. //返回数据
  81. ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
  82. RpcResponse rpcResponse = new RpcResponse();
  83. rpcResponse.setStatus("success");
  84. rpcResponse.setData(data);
  85. objectOutputStream.writeObject(rpcResponse);
  86. }
  87.  
  88. public static void main(String[] args) throws Exception {
  89. //模拟spring容器的加载过程
  90. RpcBeanFactory.putBean(ITestService.class.getName(), new TestServiceImpl());
  91. Server server = new Server();
  92. ZooKeeper zooKeeper = server.connect();
  93. //创建节点,用于地址访问
  94. server.createNode(zooKeeper);
  95. //监听,当然多线程更加理想,这里只显示效果
  96. server.accept();
  97. }
  98.  
  99. }

  7)调用方

  1. import com.pinnet.zookeeper.bean.RpcBeanFactory;
  2. import com.pinnet.zookeeper.data.RpcRequest;
  3. import com.pinnet.zookeeper.data.RpcResponse;
  4. import com.pinnet.zookeeper.service.ITestService;
  5. import com.pinnet.zookeeper.zookeeper.ZookeeperUtils;
  6. import org.apache.zookeeper.ZooKeeper;
  7.  
  8. import java.io.IOException;
  9. import java.io.ObjectInputStream;
  10. import java.io.ObjectOutputStream;
  11. import java.lang.reflect.InvocationHandler;
  12. import java.lang.reflect.Method;
  13. import java.lang.reflect.Proxy;
  14. import java.net.InetSocketAddress;
  15. import java.net.Socket;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.concurrent.ConcurrentHashMap;
  20.  
  21. /**
  22. * rpc客户端
  23. */
  24. public class Client {
  25.  
  26. //缓存地址
  27. private Map<String, List<String>> adressMap = new ConcurrentHashMap<>();
  28.  
  29. //链接zk
  30. public ZooKeeper connect() throws Exception {
  31. return ZookeeperUtils.connect();
  32. }
  33.  
  34. /**
  35. * 这里主要是创建代理,利用代理接口实现来达到调用的目的
  36. * @param interfaceName
  37. * @return
  38. * @throws ClassNotFoundException
  39. */
  40. public Object createProxy(final String interfaceName) throws ClassNotFoundException {
  41. //使用线程实例化,主要考虑处理性
  42. final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(interfaceName);
  43. //创建代理
  44. return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
  45. //用重连计数
  46. private int num = 5;
  47.  
  48. @Override
  49. public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
  50. //发送请求需要的请求参数
  51. RpcRequest rpcRequest = new RpcRequest();
  52. //接口名称
  53. rpcRequest.setInterfaceName(interfaceName);
  54. //调用方法名
  55. rpcRequest.setMethod(method.getName());
  56. //对应参数类型
  57. rpcRequest.setParameterTypes(method.getParameterTypes());
  58. //对应参数
  59. rpcRequest.setParams(params);
  60. //返回响应结果
  61. return sendData(rpcRequest);
  62. }
  63.  
  64. //tcp方式调用
  65. private Object sendData(RpcRequest rpcRequest) throws Exception {
  66. //当访问地址存在时
  67. if (adressMap.containsKey(interfaceName)) {
  68. List<String> adresses = adressMap.get(interfaceName);
  69. if (adresses != null && !adresses.isEmpty()) {
  70. //如果存在多个地址,使用可以调通的一个
  71. for (String adress:adresses) {
  72. //这个是注册zk的时候设定的数据
  73. String[] strs = adress.split(":");
  74. //这里简易版的实现,所以直接使用的socket.
  75. //实际上可以采用netty框架编写,保存channel访问就可以了,也可以对数据进行加解密
  76. Socket socket = new Socket();
  77. try {
  78. //连接可以访问zk,如果连接失败直接抛出异常,循环下一个地址
  79. socket.connect(new InetSocketAddress(strs[0], Integer.valueOf(strs[1])));
  80. ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
  81. objectOutputStream.writeObject(rpcRequest);
  82. ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
  83. //这里是响应数据,也就是执行过后的数据。远程执行结果
  84. RpcResponse rpcResponse = (RpcResponse) objectInputStream.readObject();
  85. return rpcResponse.getData();
  86. } catch (IOException e) {
  87. e.printStackTrace();
  88. }
  89. }
  90. //设置重连机制,跳出循环
  91. if (num == 0) {
  92. throw new RuntimeException("server connect fail");
  93. }
  94. num--;
  95. //如果多个地址还是不能访问,则从zk上面更新地址
  96. getAddress(interfaceName);
  97. //如果多个地址还是不能访问,则重新访问
  98. sendData(rpcRequest);
  99. }
  100. throw new RuntimeException("not found service");
  101. }else {
  102. //如果没有地址存储时的访问,主要是请求链接问题
  103. if (num == 0) {
  104. throw new RuntimeException("not found server");
  105. }
  106. num--;
  107. getAddress(interfaceName);
  108. return sendData(rpcRequest);
  109. }
  110. }
  111. });
  112. }
  113.  
  114. //从zk获取最新的地址
  115. private void getAddress(String interfaceName) throws Exception {
  116. //链接zk
  117. ZooKeeper zooKeeper = connect();
  118. //设定的地址目录
  119. String interfacePath = "/registry/" + interfaceName;
  120. //获取地址目录
  121. List<String> addresses = zooKeeper.getChildren(interfacePath, false);
  122. if (addresses != null && !addresses.isEmpty()) {
  123. List<String> datas = new ArrayList<>();
  124. for (String address:addresses) {
  125. //获取数据,也就是配置对应的访问地址
  126. byte[] bytes = zooKeeper.getData(interfacePath + "/" + address, false, null);
  127. if (bytes.length > 0) {
  128. //放入数组
  129. datas.add(new String(bytes));
  130. }
  131. }
  132. //加入缓存
  133. adressMap.put(interfaceName, datas);
  134. }
  135. }
  136.  
  137. public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
  138. Client client = new Client();
  139. //这一步是模拟spring容器放入bean的过程,实际用spring容器可自定义标签实现
  140. RpcBeanFactory.putBean("testService", client.createProxy(ITestService.class.getName()));
  141. //获取bean的过程,实际上实现是代理接口的实现
  142. ITestService testService = (ITestService) RpcBeanFactory.getBean("testService");
  143. //调用方法,也就是调用代理的过程
  144. String test = testService.test("test");
  145. System.out.println(test);
  146. }
  147. }

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

  服务端:

  

  客户端:

  

  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. LA 4253 箭术(二分枚举)

    https://vjudge.net/problem/UVALive-4253 题意: 有n个平行于x轴的线段,每条线段代表一个靶子.判断是否可以站在x轴上[0,W]区间内的某个位置射箭. 思路:二分 ...

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

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

  3. poj3734矩阵快速幂

    挑战上面的题目,感觉脑洞很大 分别找红蓝个数全为偶,全为奇,一奇一偶的个数ai,bi,ci 转移矩阵是| 2 1 0 |,是一个对称矩阵(会不会有什么联系.) | 2 2 2 | | 0 1 2 | ...

  4. Xcode8出现问题总结

    上点干货,目前得知的一些bug解决汇总:iOS10相册相机闪退bughttp://www.jianshu.com/p/5085430b029fiOS 10 因苹果健康导致闪退 crashhttp:// ...

  5. iis6手工创建网站后无法运行php脚本

    给人搬了十几个网站,老站用西部数码建站助手创建的,现在过期了无法继续创建,只能在Internet 信息服务(IIS)管理器创建网站,创建下来都没问题,但是就是无法打开网站. 测试打开txt文档.静态页 ...

  6. 在Google Maps中导出KML文件

    教你一招:在Google Maps中导出KML文件 2011-07-11 15:24:49 4819 人阅读 作者:上方文Q 编辑:上方文Q [复制链接] [爆料] Google Earth的一大乐趣 ...

  7. 使用Array类处理基本数组对象

    java里面的Arrays类有个asList方法,参数是1或多个Object对象,如果传入一个Object数组,则可以将该数组转化为List,但如果传入的是一个基本类型的数据(int,long,sho ...

  8. 【LeetCode 1_数组_哈希表】Two Sum

    解法一:O(N) vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int, i ...

  9. 关于apply、call和bind的总结

    基础知识不是你看了一遍书或者两篇文章就能掌握的. 之前看书看文章时,感觉自己看懂了就掌握了.呵呵!too young!too naive! 以前的坑还是要一铲一铲的填上的. 高程上面关于apply和c ...

  10. L173

    Technical problems temporarily blocked some US and European users having access to their accounts an ...