dubbo 中層次的學習—簡易rpc實現

dubbo 中層次的學習—簡易rpc實現

不少人和我同樣,在平時的工做中對於dubbo只是停留於使用階段。甚至不少時候,必需要新開放一個rpc接口,或者新引入一個rpc接口都是要抄以前的配置(全部參數照抄,只是把接口名改一下,而後把id改一下)。其實冷靜下來想一想,咱們正在慢慢朝着剛畢業時最鄙視的人(渾渾噩噩的碼農)轉變了。java

我最近跟公司的另一個小夥伴一塊兒在吃dubbo。他吃完了以後都作了筆記,而我可能看的源碼比較少,吃起來很累,並且可能也沒有那麼深入。不過畢竟這是我第一個吃的比較透的rpc框架,也但願可以留下一些文字,寫一下我吃的整個過程。沒準能給跟我同樣的小菜雞們帶來一點思路,哈哈哈哈。加油!git

rpc的最基礎實現

你們都知道dubbo是個rpc框架,那麼rpc框架最基礎的實現是什麼呢?在這裏你們能夠先思考一下,如何搭建一個最最簡單的rpc框架?web

image

咱們使用過dubbo的同窗應該知道,若是A要調用B的方法,那麼必然是依賴了B的包(至少是api包,不然我怎麼知道要調用B的哪一個方法?_其實也能夠不依賴,這個在後面講_)。因此最最簡單的rpc框架應該是A告訴B,我要執行你的哪一個哪一個方法,而後你執行完以後把結果再告訴我。那麼這個方法執行的基礎信息是什麼呢?spring

方法執行的基礎信息

若是從正向調用方法的邏輯去思考,可能說不清楚到底執行一個方法須要哪些東西。因而咱們能夠經過反射調用的方式來思考這個問題。apache

  @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;
  }
  }
 }

經過類名方法名參數類型數組(區別方法重載),咱們能夠肯定惟一的一個方法。而當咱們須要調用這個方法的時候,就須要把具體的參數傳進去。後端

我上面寫的代碼顯示瞭如何用一個類(消費者)反射調用另外一個類(提供者)的方法。雖然用的是單元測試的代碼,相信你們也可以理解。不理解的,我以爲。。。這篇文章不適合你。。哈哈哈哈哈api

咱們回到rpc的環節。這時候咱們再繼續思考,最簡單的rpc框架就應該是對於上面代碼的擴展。而上面是單機版,咱們只要把它作成分佈式的,豈不是就成了?沒錯!咱們順着這個思路,很天然而然的就能獲得咱們下面一步要作的事情——把消費者和提供者分離開,兩邊經過網絡傳輸的方式進行交流。數組

socket加入

說實話我這裏不知道應不該該大段貼代碼,貼太多代碼的話確實會影響觀感。。。由於socket這一塊並非本文要重點分析的,因此我考慮之後決定只貼一些關鍵代碼。本文最後會把個人git地址貼上,你們有興趣的話能夠直接把代碼clone下來看。tomcat

消費者
  @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裏面的代碼作了不少對象的封裝,因此裏面的類會比想象中的多那麼一點點。

相關文章
相關標籤/搜索