概述
RPC 這個東西是什麼? 第一次聽說他, 還要在它的前邊加個G, 當時我以為GRPC是一項技術, 後來才知道, 並不是這樣. GRPC只是RPC的谷歌實現.
谷歌搜了一下, RPC就是一種: 遠程函數調用, 看到這裡, 我已經等不及了, 不往下看了, 先自己實現一個. 如果只給你這樣一個概念, 如何實現調用遠程函數的功能呢?
自己實現
自己嘗試實現一個粗糙的PHP版本. (不想看可以跳過的)
思路
遠程調用, 只需要解決下面問題:
- 通信問題
- 定義傳輸的數據格式
- 如何封裝後可以達到像調用本地函數一樣的效果
先來解決通信問題, 直接粗暴的tcp socket
傳輸的數據格式, 直接用json進行傳輸
調用本地函數?? 這就要藉助一下PHP的魔術函數了, __call() 這個函數是一個類調用不存在的方法時會跑到這裡來, 所以, 我們返回一個類, 在call方法中進行遠程調用, 這樣, 在本地看來就只是在調用一個方法.
開始實現
PHP中進行socket連接十分簡單, 直接調用系統函數. 通信問題解決了, 剩下的就是傳輸數據了, so easy
經過一番摸索, 看下結果
服務器內容:
<code>class RpcServer{
private $port = 0; // 監聽端口號
private $host = ''; // IP
public function __construct($host, $port){
$this->host = $host;
$this->port = $port;
}
/**
* 運行, 監聽端口並處理
*/
public function run(){
// 創建socket
$server = stream_socket_server("tcp://{$this->host}:{$this->port}");
if(empty($server)) throw new Exception('創建套接字失敗');
// 監聽
while (true){
$client = stream_socket_accept($server);
if(empty($client)) continue;
// 處理請求
$this->disposeClient($client);
fclose($client);
}
}
private function disposeClient($client){
$buf = fread($client, 4096);
$array = json_decode($buf, true);
// 創建對象並調用方法
$class = $array['class'] ?? '';
$method = $array['method'] ?? '';
$params = $array['params'] ?? [];
$instance = new $class();
$result = $instance->$method(...$params);
fwrite($client, json_encode($result));
}
}
// 測試調用類
class Test{
public function tt(){
return 'return_tt';
}
public function add($a, $b){
return $a + $b;
}
}
(new RpcServer('127.0.0.1', 8888))
->run();/<code>
調用方:
<code>class RpcClient{
private $urlInfo = null;
private $className = '';
private function __construct($url, $className){
$this->urlInfo = parse_url($url);
$this->className = $className;
}
public static function getInstance($className){
return new RpcClient('127.0.0.1:8888', $className);
}
public function __call($name, $arguments){
// 創建客戶端
$client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}");
if(empty($client)) return null;
// 發送數據
fwrite($client, json_encode([
'class' => $this->className,
'method' => $name,
'params' => $arguments,
]));
// 接收返回
$data = fread($client, 4096);
// 關閉客戶端
fclose($client);
return json_decode($data, true);
}
}
$test = RpcClient::getInstance('Test');
echo $test->tt(), PHP_EOL;
echo $test->add(4, 6);/<code>
結果:
嗯, 還闊以. 當然, 問題還是有很多的, 比如不能實現保存對象的修改狀態等等.
其實對象可以通過序列化和反序列化來傳輸, 額, Java中, 不知道PHP有沒有這種技術.
當然, 一個RPC中必然大量使用反射、序列化、動態加載、代理、網絡請求等等, 這只是一個超級超級粗糙的示例.
繼續
nice, 自己做完了, 對RPC是個什麼東西有了一個基本的概念.
WHAT
RPC是什麼? 簡單說, 就是遠程函數調用. 字面意思, 很好理解.
WHY
看到一個技術, 一定會問的一個問題就是: 為什麼? 一個技術基本不會平白無故出現, 都是為了解決某些問題, 那麼RPC解決了什麼問題呢? 字面含義: 遠程函數調用
為什麼要進行遠程函數調用, 把函數拿過來本地調用不就好了? 還不用走網絡IO, 速度更快一些. 很好, 現在假設, 你真的這樣做了, 當項目變得龐大, 你想要進行拆分, 拆分後的有: 項目A, 項目B..., 這時, 你發現這些拆分的項目部分邏輯是重疊的, 比如用戶信息相關, 怎麼辦? 如果不抽出來, 以後的維護成本會變得很高, 一處改處處改. 如果抽出來, 跨項目如何進行調用? 哎, 走過路過不要錯過, RPC推薦給你.
HOW
那麼如何實現RPC呢?
在剛才使用PHP簡單實現中, 已經發現了. 需要解決的問題如下:
- 網絡通信
- 信息格式
- 對象狀態保存
1.網絡通信
說到底, 網絡通信不過兩種: tcp udp.
有沒有使用udp實現的RPC呢? 貌似也有.
使用tcp協議實現的RPC也有, 當然, 不光傳輸層協議, 也有直接通過應用層協議: http、websocket等等建立連接的. 當然, 如果需要頻繁調用, 可以不斷開tcp連接, 在一段時間內一直保持連接, 避免頻繁握手.
2.信息格式
信息格式就有很多選擇了, json、xml等等, 也可以自己定製, 只要發送端和接收端統一信息格式就行了.
3.對象狀態保存
對於一個類的調用, 通常都會有類狀態修改的操作, 比如調用setName方法, 如何保存對象的信息呢? 當然, 可以服務端將對象在內存中的信息直接序列化發回去, 當客戶端下次調用時攜帶序列化信息, 服務端接收後反序列化還原對象繼續操作.
過程
個人理解的RPC調用過程:
- 客戶端創建RPC對象
- 客戶端調用方法
- RPC解析方法並將對象及參數做序列化
- RPC通過網絡連接發送方法調用
- 服務端接收到方法調用, 解析對象及參數反序列化
- 服務端執行方法並將結果序列化返回
- 客戶端接收到結果並進行解析, 返回給本地調用者
- 拿到最終結果
RPC適用於內部網絡不同項目之間的通信, 如果是對外暴露的, 個人感覺還是通過接口的形式吧.
使用RPC顯然會喪失一部分性能, 畢竟調用要走網絡IO, 儘管是內網, 仍然要比本地調用慢上一些, 但帶來了更好的可擴展性和可維護性, 感覺還是不錯的.
之後如果用到的話, 拉個框架看看源碼.
個人理解, 以上...
閱讀更多 菸草的香味 的文章