dubbo 中层次的学习—简易rpc实现

很多人和我一样,在平时的工作中对于dubbo只是停留于使用阶段。甚至很多时候,必须要新开放一个rpc接口,或者新引入一个rpc接口都是要抄之前的配置(所有参数照抄,只是把接口名改一下,然后把id改一下)。其实冷静下来想想,我们正在慢慢朝着刚毕业时最鄙视的人(浑浑噩噩的码农)转变了。

我最近跟公司的另外一个小伙伴一起在吃dubbo。他吃完了之后都做了笔记,而我可能看的源码比较少,吃起来很累,而且可能也没有那么深刻。不过毕竟这是我第一个吃的比较透的rpc框架,也希望能够留下一些文字,写一下我吃的整个过程。没准能给跟我一样的小菜鸡们带来一点思路,哈哈哈哈。加油!

rpc的最基础实现

大家都知道dubbo是个rpc框架,那么rpc框架最基础的实现是什么呢?在这里大家可以先思考一下,如何搭建一个最最简单的rpc框架?

image

我们使用过dubbo的同学应该知道,如果A要调用B的方法,那么必然是依赖了B的包(至少是api包,否则我怎么知道要调用B的哪个方法?_其实也可以不依赖,这个在后面讲_)。所以最最简单的rpc框架应该是A告诉B,我要执行你的哪个哪个方法,然后你执行完之后把结果再告诉我。那么这个方法执行的基础信息是什么呢?

方法执行的基础信息

如果从正向调用方法的逻辑去思考,可能说不清楚到底执行一个方法需要哪些东西。于是我们可以通过反射调用的方式来思考这个问题。


  @Test
  public void test() {
  try {
  Class<?> greetingService = Class.forName("org.apache.dubbo.common.extension.ExtensionTest$GreetingService");
  Class<?>[] parameterTypes = new Class[]{String.class, String.class};
  Method sayHello = greetingService.getMethod("sayHello", parameterTypes);
  Object[] parameterObjects = new Object[]{"tom", "18"};
  Object result = sayHello.invoke(greetingService.newInstance(), parameterObjects);
  System.out.println(result);
  } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
  e.printStackTrace();
  }
  }
 ​
  static class GreetingService {
 ​
  public String sayHello(String name) {
  return "hello " + name;
  }
 ​
  public String sayHello(String name, String age) {
  return "hello " + name + ":" + age;
  }
  }
 }

通过类名方法名参数类型数组(区别方法重载),我们可以确定唯一的一个方法。而当我们需要调用这个方法的时候,就需要把具体的参数传进去。

我上面写的代码显示了如何用一个类(消费者)反射调用另一个类(提供者)的方法。虽然用的是单元测试的代码,相信大家也能够理解。不理解的,我觉得。。。这篇文章不适合你。。哈哈哈哈哈

我们回到rpc的环节。这时候我们再继续思考,最简单的rpc框架就应该是对于上面代码的扩展。而上面是单机版,我们只要把它做成分布式的,岂不是就成了?没错!我们顺着这个思路,很自然而然的就能得到我们下面一步要做的事情——把消费者和提供者分离开,两边通过网络传输的方式进行交流。

socket加入

说实话我这里不知道应不应该大段贴代码,贴太多代码的话确实会影响观感。。。因为socket这一块并不是本文要重点分析的,所以我考虑以后决定只贴一些关键代码。本文最后会把我的git地址贴上,大家有兴趣的话可以直接把代码clone下来看。

消费者
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  Socket socket = new Socket(host, port);
  OutputStream outputStream = socket.getOutputStream();
  ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
 ​
  Class<?>[] paramTypes = new Class[args.length];
  for (int i = 0; i < args.length; i++) {
  paramTypes[i] = args[i].getClass();
  }
  //注意proxy已经是代理类了 所以第一个参数要用method.getDeclaringClass().getName()
  ServiceMetadata serviceMetadata = new ServiceMetadata(method.getDeclaringClass().getName(), method.getName(), paramTypes, args);
  objectOutputStream.writeObject(serviceMetadata);
  return getResultFromRemote(socket);
  }
 ​
 ​
  /**
  * 从远程服务获取方法调用结果
  *
  * @param socket 通信socket
  * @return 调用结果
  * @throws IOException
  * @throws ClassNotFoundException
  */
  private Object getResultFromRemote(Socket socket) throws IOException, ClassNotFoundException {
  InputStream inputStream = socket.getInputStream();
  ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  return objectInputStream.readObject();
  }
服务提供者

 

 /**
  * 开始监听
  */
  public void doListen() throws IOException, ClassNotFoundException {
  Integer port = applicationRpcPort == null ? Integer.valueOf(20880) : applicationRpcPort;
  ServerSocket serverSocket = new ServerSocket(port);
  keepListening = new AtomicBoolean(true);
  while (keepListening.get()) {
  Socket accept = serverSocket.accept();
  System.out.println("接收到消息");
  InputStream inputStream = accept.getInputStream();
  ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  Object readObject = objectInputStream.readObject();
  if (readObject instanceof ServiceMetadata) {
  //当监听到一个消费者请求 放到线程池里面继续操作
  threadPoolExecutor.submit(() -> {
  Object result = RpcServiceInvoker.doInvoke((ServiceMetadata) readObject);
  try {
  OutputStream outputStream = accept.getOutputStream();
  ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
  objectOutputStream.writeObject(result);
  } catch (IOException e) {
  e.printStackTrace();
  }
  });
  }
  }
  threadPoolExecutor.shutdown();
  }
  

讲到这里,我这边会建议大家开始动手了! 真的很重要,如果不动手光看的话,感觉是不一样的。大家应该能写出一个最简单的rpc框架了。

框架的启动

我们在上一节写好的框架,只能通过main()或者单元测试调用。我们的下一步就是需要把我们的框架嵌入到web应用中,做到服务启动,我们的项目就随之启动(跟dubbo一样)。

引入spring

考虑到我们绝大多数的后端服务都会集成spring这个老大哥,而且引入spring之后也会方便我们直接通过web请求验证我们的结果。所以我们可以通过引入spring-boot-starter-web把consumer做成一个controller,然后把provider就藏在另一个tomcat项目里面。

接下去我们要做的事情,就是把从provider端引入的接口通过代理的方式,走远程调用而不走接口直接调用(直接调用会报错的,因为在consumer端只能拿到接口,拿不到实现)。

consumer端步骤如下:

  1. 通过自定义注解的方式找到哪些是需要rpc调用的请求,通过注解的好处是,你可以通过反射得到被注解的所有信息(也就是我们上面提到的服务调用基础信息);
  2. 把引入的接口做代理(java的原生代理,InvocationHandler),然后把代理对象设置到原对象中;
  3. 在自己实现的代理类里面,写上上面的socket调用逻辑。

provider端步骤如下:

  1. 在项目启动的时候,把我们的监听打开就行了。

什么?你不知道项目启动的时候调用自己的程序怎么写? spring的扩展千万种,总有一种适合你~~

总结

写到这里,我们的rpc框架雏形已经出来了。而且这个也是真实可以使用的框架。虽然在本文里面没有讲关于dubbo的东西,但是相信如果大家能完整看下来然后写下来的话,一定是会有启发的。(如果你是大神,当我没说过哈~)

最后附上我这个项目的地址https://gitee.com/hanochMa/rpc-study.git 。大家记得把分支切到v1.0。

在v1.0里面的代码和本文所分析的代码,不同点在于v1.0里面的代码做了很多对象的封装,所以里面的类会比想象中的多那么一点点。


咻咻咻
1 声望0 粉丝