趁熱打鐵,我們今天來手寫一個RPC框架……
前言
昨天大概理了下思路,覺得目前最合適的事,就是手寫一個rpc
框架,因為只有創造、創作,才讓我覺得生活更有激情,而且做具體的事,也會讓生活更充實、更真實,會讓你不那麼迷茫。
如果有同樣感受的小夥伴,可以試著找一些具體的事來做一做,這樣你也就不再那麼浮躁了。
好了,廢話少說,我們直接正文。
手寫RPC
在開始正文前,我們先看下什麼是rpc
。
關於RPC
rpc
的英文全稱是Remote Procedure Call
,翻譯過來就是遠端呼叫,顧名思義,rpc
就是一套介面的呼叫方式,一種呼叫協議。
關於rpc
的呼叫原理,這裡放上百度百科的一張圖片,供大家參考:
正如上圖所示,因為是遠端呼叫,所以介面的呼叫方和被呼叫方是通過網路實現呼叫和通訊的,今天我們是基於socket
從rpc
廣義的定義角度來說,restful
也屬於遠端呼叫,只是遵循的通訊協議不一樣。
好了,基礎知識就到這裡,下面我們看下具體如何實現。
具體實現
開始之前,我想先說下思路。在此之前,我們已經手寫過springboot
的一個簡易框架,通過那個框架,我們瞭解了伺服器的工作流程,和呼叫過程,當然更重要的是也為我們今天的實現提供思路。簡單來說,今天的實現是這樣的:
分別建立socket
服務端和客戶端,他們分別代表服務提供者和服務消費者,服務提供者先啟動(服務端),消費者向服務端傳送訪問請求(包括類名、方法名、引數等),服務提供者收到介面訪問請求時,解析請求引數(拿出類名、方法名、引數列表),通過反射呼叫方法,並將執行結果返回,然後呼叫完成。
這裡是簡單的流程圖:
下面我們看下具體實現過程。
服務提供者
就是一個socket
服務端,這裡最核心的問題是,你需要提前確定消費者和提供者要傳輸哪些資料,因為客戶端也是我們自己寫,所以具體資料傳輸就比較靈活。
這裡我通過objectInputStream
和objectOutputStream
進行資料操作,因為我們操作的都是java
的object
,所以用這個就比較方便,當然網上絕大多示例也都是這麼實現的。
你直接用InputStream/OutputStream
也是可以的,重要的是要理解原理。
這裡需要注意的是,服務端讀取的順序,必須和客戶端傳送的一致,否則在反序列化的時候會報錯,會強轉失敗:
- 讀取類名
- 讀取方法名
- 讀取引數
- 讀取引數型別
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket accept = serverSocket.accept();
ObjectInputStream objectInputStream = new ObjectInputStream(accept.getInputStream());
// 讀取類名
String classFullName = objectInputStream.readUTF();
// 讀取方法名
String methodName = objectInputStream.readUTF();
// 讀取方法呼叫入參
Object[] parameters = (Object[])objectInputStream.readObject();
// 讀取方法入參型別
Class<?>[] parameterTypes = (Class<?>[])objectInputStream.readObject();
System.out.println(String.format("收到消費者遠端呼叫請求:類名 = {%s},方法名 = {%s},呼叫入參 = %s,方法入參列表 = %s",
classFullName, methodName, Arrays.toString(parameters), Arrays.toString(parameterTypes)));
Class<?> aClass = Class.forName(classFullName);
Method method = aClass.getMethod(methodName, parameterTypes);
Object invoke = method.invoke(aClass.newInstance(), parameters);
// 回寫返回值
ObjectOutputStream objectOutputStream = new ObjectOutputStream(accept.getOutputStream());
System.out.println("方法呼叫結果:" + invoke);
objectOutputStream.writeObject(invoke);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
介面實現
這裡的介面實現就是我們提供給消費者的服務。
public class HelloSercieImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
}
服務消費者
前面說了,服務消費者傳送訊息的順序必須和服務提供者讀取的順序一致,所以消費者傳送的順序也必須如下:
- 寫類名
- 寫方法名
- 寫引數
- 寫引數型別
public static void main(String[] args) {
try {
String classFullName = "io.github.syske.rpc.service.HelloSercieImpl";
String methodName = "sayHello";
String[] parameters = {"syske"};
Class<?>[] parameterTypes = {String.class};
Socket socket = new Socket("127.0.0.1", 8888);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeUTF(classFullName);
objectOutputStream.writeUTF(methodName);
objectOutputStream.writeObject(parameters);
objectOutputStream.writeObject(parameterTypes);
// 讀取返回值
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object o = objectInputStream.readObject();
System.out.println("消費者遠端呼叫返回結果:" + o);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
這裡的方法名和引數列表主要是為了確認方法的唯一性,便於獲取方法,後期如果引入facade
層,方法名和引數可以直接從介面獲取。
測試
我們來測試一下,先啟動服務提供者,然後啟動服務消費者,這時候會看到服務提供者控制檯輸出如下資訊:
同時,消費者控制檯也返回了遠端呼叫結果:
到這裡,我們簡易的rpc
服務框架就基本完成了。怎麼樣,是不是很簡單呢?
總結
不知道大家發現沒,雖然我們的rpc
框架實現了,但是在呼叫具體方法的時候很不靈活,不僅需要執行實現類,要指定方法名、入參、引數列表,同時還需要配置服務地址和埠,很繁瑣,而且如果我們的遠端介面很多,我們就需要配置很多不同的介面資訊,介面管理起來就很不方便。
所以這時候我們就應該明白了,為什麼rpc
框架需要註冊中心,以及註冊中心的作用?是的,它就是用來註冊管理我們的服務的,讓我們可以更方便地找到服務的提供者以及介面的資訊。
我現在對很多框架了元件有了更深刻的瞭解和認知,任何元件的出現都是為了解決具體的問題,比如zookeeper
、redis
、配置中心等,但這些元件並非必須的,所以我們在使用具體元件的時候,已經要有知其然,知其所以然的思維,不能僅僅停留在會用的層面,而應該去思考為什麼用,為什麼不用,只有在這樣的思維指導下,你才可能在這個行業走的更遠,至少作為一名技術人員,我覺得是這樣的。
技術本身只是器,只是工具,脫離技術的思維才是yyds
,當然行動也是很重要的,我們也需要不斷地實踐和探索。
和web
伺服器一樣,我以前對rpc
的認知也比較淺顯,總覺得這些東西很牛逼,但是當我真正理解了原理,然後自己動手做的時候,我覺得也沒有那麼難,還是那句話,知易行難,還是要自己動手,才能真正瞭解其中意。
好了,今天就到這裡吧!
完整專案開源地址如下,感興趣的小夥伴可以去看下,後續我們會繼續實現相關功能,比如整合註冊中心、實現動態代理等待:
https://github.com/Syske/syske-rpc-server