應用架構之RPC架構

RPC框架簡介

  • 什麼叫RPC?
    RPC(Remote Procedure Call Protocol)——遠程過程調用協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,爲通信程序之間攜帶信息數據。在OSI網絡通信模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。
  • 爲什麼會出現RPC?
    應用之間的交互不可避免,將核心業務抽取出來,作爲獨立的服務。同時將通用的API抽取出來,供其他系統調用者使用。應用拆分之後會按照模塊獨立部署,接口調用本地接口變成了跨進程的遠程調用,這個時候就出現了RPC框架。
  • 說白了RPC是一種進程間的通信方式,允許像調用本地方法一樣調用遠程服務

RPC框架原理

RPC框架的目標是是遠程服務調用更加簡單,透明。它屏蔽了底層的傳輸方式(TCP或者UDP),序列化方式(XML/JSON/二進制)和通信細節。其實框架的作用就是屏蔽了底層的操作細節,讓我們程序員更加方便高效的編程。但是瞭解底層的實現對我們熟練使用框架是有很大的幫助的。下圖簡述RPC框架的原理:

這裏寫圖片描述

運行時,一次客戶機對服務器的RPC調用,其內部操作大致有如下十步:
1.調用客戶端句柄;執行傳送參數
2.調用本地系統內核發送網絡消息
3.消息傳送到遠程主機
4.服務器句柄得到消息並取得參數
5.執行遠程過程
6.執行的過程將結果返回服務器句柄
7.服務器句柄返回結果,調用遠程系統內核
8.消息傳回本地主機
9.客戶句柄由內核接收消息
10.客戶接收句柄返回的數據

簡單的PRC框架的實現

下面通過Java原生的序列化,Socket通信,動態代理和反射機制實現最簡單的RPC框架。

  • 組成

    • 服務提供者,運行在服務端,負責提供服務接口定義和服務實現類。
    • 服務發佈者,運行在RPC服務端,負責將本地服務發佈成遠程服務,供消費者使用。
    • 本地服務代理,運行在RPC客戶端通過代理調用遠程的服務提供者,然後將結果進行封裝返還給本地消費者。
  • 實現

    • 服務端接口定義

      public interface EchoService {
          String echo( String ping );
      }
    • 服務端接口實現類

      public class EchoServiceImpl implements EchoService {
      
          @Override
          public String echo(String ping) {
              return ping != null ? ping + " -->I am ok." : " I am ok.";
          }
      
      }
    • RPC服務端服務發佈者代碼

      //ObjectInputStream 對象輸入流,可以將序列化過的對象反序列化到內存中,對應的還有個ObjectOutputStream 可以將一個對象序列化到本地!
      public class RpcExporter {
      
          static Executors executors = (Executors) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
      
          public static void exporter( String hostName, int port ) throws Exception {
              ServerSocket server = new ServerSocket();
              server.bind( new InetSocketAddress( hostName, port ) );
          }
      
          private static class ExporterTask implements Runnable {
      
              Socket client = null;
      
              public ExporterTask ( Socket client ) {
                  this.client = client;
              }
      
              @Override
              public void run() {
                  ObjectInputStream input = null;
                  ObjectOutputStream output = null;
      
                  try {
      
                      input = new ObjectInputStream( client.getInputStream() );
                      String interfaceName = input.readUTF();
                      Class<?> service = Class.forName( interfaceName );
                      String methodName = input.readUTF();
                      Class< ? > parameterTypes = (Class<?>) input.readObject();
                      Object[] arguments = (Object[]) input.readObject();
                      Method method = service.getMethod(methodName, parameterTypes);
                      Object result = method.invoke( service.newInstance() , arguments );
                      output = new ObjectOutputStream( client.getOutputStream() );
                      output.writeObject( result );
      
                  } catch (Exception e) {
                      e.printStackTrace();
                  } finally {
                      if( output != null ) {
                          try {
                              output.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                      if( input != null ) {
                          try {
                              input.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                      if( client != null ) {
                          try {
                              client.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }
      }
      
      • 服務發佈者的主要職責:
        • 做爲服務器,監聽客戶端的TCP連接,接收到新的客戶端連接後,將其封裝成Task,由線程池執行。
        • 將客戶端發送的碼反序列化成對象,反射調用服務實現者,獲得執行結果。
        • 將執行結果對象反序列化,通過Socket發送給客戶端。
        • 遠程調用完成之後,釋放Socket等連接資源,防止句柄泄漏。
    • RPC客戶端本地服務代理代碼

      public class RpcImporter<S> {
          public S importer ( final Class<?> serviceClass ,final InetSocketAddress addr ) {
              return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[] { serviceClass.getInterfaces()[0] },
                  new InvocationHandler() {
      
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              Socket socket = null;
                              ObjectOutputStream output = null;
                              ObjectInputStream input = null;
      
                              try {
                                  socket = new Socket();
                                  socket.connect(addr);
                                  output = new ObjectOutputStream(socket.getOutputStream());
                                  output.writeUTF(serviceClass.getName());
                                  output.writeUTF(method.getName());
                                  output.writeObject(method.getParameterTypes());
                                  output.writeObject(args);
                                  input = new ObjectInputStream(socket.getInputStream());//同步阻塞等待
                              } finally {
                                  if( socket != null ) {
                                      socket = null;
                                  }
                                  if( output != null ) {
                                      output = null;
                                  }
                                  if( input != null ) {
                                      input = null;
                                  }
                              }
                              return input.readObject();
                          }
                      });
          }
      }
      
      • 本地服務代理的職責
        • 將本地接口調用轉化成JDK的動態代理,在代理類中實現接口的遠程調用。
        • 創建Socket客戶端,根據指定地址連接遠程服務提供者
        • 將遠程服務調用所需的接口和方法名等編碼後發送給服務提供者。
        • 同步阻塞等待服務器返回應答,獲取應答之後返回。
    • 測試代碼

      public class RpcTest {
          public static void main(String[] args) {
              //用於接受PRC客戶端的請求
              new Thread(new Runnable() {
      
                  @Override
                  public void run() {
                      try {
                          RpcExporter.exporter("127.0.0.1", 8080);
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
              RpcImporter<EchoService> importer = new RpcImporter< EchoService >();
              EchoService echo = importer.importer( EchoServiceImpl.class, new InetSocketAddress("127.0.0.1", 8080) );
              System.out.println( echo.echo("Are you OK?") );
          }
      }

RPC的不足

純粹的RPC框架服務與治理能力都不健全,當應用大規模服務化之後會面臨許多服務治理方面的挑戰,要解決這些問題,必須通過服務框架 + 服務治理來完成,單憑RPC框架無法解決服務治理問題。
什麼是服務治理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章