深入了解RPC遠端方法調用(一)
1. 從多态說起
面向對象有三大特征:繼承、封裝、多态,我們先來說說多态。
多态就是指程式中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在程式設計時并不确定,而是在程式運作期間才确定,即一個引用變量倒底會指向哪個類的執行個體對象,該引用變量發出的方法調用到底是哪個類中實作的方法,必須在由程式運作期間才能決定。
先來看傳統的方法調用,在Java 中 All in object. 第一步是 new 一個對象,然後調用這個對象的成員方法。
執行個體1:
HelloService service = new HelloService();
service.sayHello();
Java 中,接口是最能展現多态的了,再來看一下多态的例子,我們把 HelloService 定義為接口,有兩個實作類,我們在編碼過程中向上轉型用 HelloService 接口聲明變量,而這兩個具體實作類必定擁有 sayHello() 這一行為,是以可以直接用 HelloService 接口的執行個體進行方法調用,而不必關心他的具體實作:
執行個體2:
public interface HelloService{
void sayHello();
}
public class BobHelloServiceImpl implements HelloService {
@Override
public void sayHello(){
System.out.println("Hello Bob!");
}
}
public class JackHelloServiceImpl implements HelloService {
@Override
public void sayHello(){
System.out.println("Hello Jack!");
}
}
@Test
public void test(){
HelloService service = null;
service = new BobHelloServiceImpl();
service.sayHello();//Hello Bob!
service = new JackHelloServiceImpl();
service.sayHello();//Hello Jack!
}
接口的實作類擁有該接口的所有行為(方法),利用接口可将聲明與實作充分解耦,至于 Spring 就更進一步了,直接在 ApplicationContext.xml 中定義 Bean 指定 HelloService 的實作,做到了對代碼的零侵入。這也是面向對象6個原則之一:面向接口程式設計而不是面向實作程式設計。
2. 自己動手寫RPC
有了面向接口程式設計的基本思路接下來對了解遠端調用就簡單多了,遠端方法調用分這麼幾個角色:
- 接口API:我們要定義的通信接口。
- 服務提供者:提供接口API的具體實作。
- 服務消費者:接口調用方,也就是作為用戶端的角色。
來改造一下 1. 中的例子,我們分三個子項目,分别是 rpc-provider、rpc-consumer、rpc-api 。rpc-provider 和 rpc-consumer 都依賴于 rpc-api 。三者的依賴關系如下圖:
執行個體3:
rpc-api 的代碼:
public interface HelloService{
String sayHello(String name);
}
rpc-provider,服務提供者:
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name){
return "Hello " + name;
}
}
public static void main(String[] args){
String url = "http://localhost:8080/rpc/";
RpcServer server = new RpcServer(url,HelloService.class,HelloServiceImpl.class);
server.start();
}
在執行個體2中 @Test 所在的方法就是 consumer ,遠端方法調用跟本地方法調用的差別就是 consumer 擷取 HelloService 接口的具體實作的方式變了,假設我們的遠端調用架構封裝為一個 InvokeProxy 類,使用代理模式來實作 RPC,這裡隻提供僞代碼說明原理,我隐藏了具體實作,實際上很多第三方架構已經為我們做好了這一點。
rpc-consumer,服務消費端:
@Test
public void test(){
InvokeProxy invokeProxy = new InvokeProxy("http://localhost:8080/rpc/");
String result = (String)InvokeProxy.invoke(HelloService.class,"sayHello",new Object[]{"xiaoming"});
System.out.println(result);//Hello xiaoming
}
InvokeProxy 類的 invoke 方法接收三個參數,一為接口所在的 class ,二為要調用的方法名字元串,三為方法所需接收的參數。
來探索一下整個流程發生了什麼:
- 啟動 rpc-provider 項目。RpcServer 作為遠端調用的服務釋出架構以 HTTP 協定将 HelloServiceImpl 的所有方法釋出到
這個跟路徑上,如 sayHello 方法的通路路徑就為http://localhost:8080/rpc/
。http://localhost:8080/rpc/sayHello
- rpc-consumer 啟動後根據指定的接口和方法名參數等将序列化好的請求封包發送給 rpc-provider 。
- rpc-provider 收到請求後将資訊反序列化為 Java 對象在 rpc-provider 端進行本地方法調用産生傳回值,将傳回值響應給 rpc-consumer 的請求。
這個過程是一個簡單的基于 HTTP 的 RPC 調用服務,事實上,這也是時下流行的 RPC 架構 Hessian 的原理,Hessian 是一個基于 HTTP 的高性能 RPC 架構。當然,你也可以實作一個基于 TCP 的 RPC 架構,原理都是類似的。
如果你關注微服務或者遠端調用,那麼對 Dubbo、RMI、REST 這幾個名詞應該不會陌生,接下來的文章我們将繼續探索。