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框架無法解決服務治理問題。
什麼是服務治理