PHP實現RPC(簡版)

概述

RPC

這個東西是什麼? 第一次聽說他, 還要在它的前邊加個G, 當時我以為GRPC是一項技術, 後來才知道, 並不是這樣. GRPC只是RPC的谷歌實現.

谷歌搜了一下, RPC就是一種: 遠程函數調用, 看到這裡, 我已經等不及了, 不往下看了, 先自己實現一個. 如果只給你這樣一個概念, 如何實現調用遠程函數的功能呢?

自己實現

自己嘗試實現一個粗糙的PHP版本. (不想看可以跳過的)

思路

遠程調用, 只需要解決下面問題:

  1. 通信問題
  2. 定義傳輸的數據格式
  3. 如何封裝後可以達到像調用本地函數一樣的效果

先來解決通信問題, 直接粗暴的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>

結果:

PHP實現RPC(簡版)

嗯, 還闊以. 當然, 問題還是有很多的, 比如不能實現保存對象的修改狀態等等.

其實對象可以通過序列化和反序列化來傳輸, 額, Java中, 不知道PHP有沒有這種技術.

當然, 一個RPC中必然大量使用反射、序列化、動態加載、代理、網絡請求等等, 這只是一個超級超級粗糙的示例.

繼續

nice, 自己做完了, 對RPC是個什麼東西有了一個基本的概念.

WHAT

RPC是什麼? 簡單說, 就是遠程函數調用. 字面意思, 很好理解.

WHY

看到一個技術, 一定會問的一個問題就是: 為什麼? 一個技術基本不會平白無故出現, 都是為了解決某些問題, 那麼RPC解決了什麼問題呢? 字面含義: 遠程函數調用

為什麼要進行遠程函數調用, 把函數拿過來本地調用不就好了? 還不用走網絡IO, 速度更快一些. 很好, 現在假設, 你真的這樣做了, 當項目變得龐大, 你想要進行拆分, 拆分後的有: 項目A, 項目B..., 這時, 你發現這些拆分的項目部分邏輯是重疊的, 比如用戶信息相關, 怎麼辦? 如果不抽出來, 以後的維護成本會變得很高, 一處改處處改. 如果抽出來, 跨項目如何進行調用? 哎, 走過路過不要錯過, RPC推薦給你.

HOW

那麼如何實現RPC呢?

在剛才使用PHP簡單實現中, 已經發現了. 需要解決的問題如下:

  1. 網絡通信
  2. 信息格式
  3. 對象狀態保存

1.網絡通信

說到底, 網絡通信不過兩種: tcp udp.

有沒有使用udp實現的RPC呢? 貌似也有.

使用tcp協議實現的RPC也有, 當然, 不光傳輸層協議, 也有直接通過應用層協議: http、websocket等等建立連接的. 當然, 如果需要頻繁調用, 可以不斷開tcp連接, 在一段時間內一直保持連接, 避免頻繁握手.

2.信息格式

信息格式就有很多選擇了, jsonxml等等, 也可以自己定製, 只要發送端和接收端統一信息格式就行了.

3.對象狀態保存

對於一個類的調用, 通常都會有類狀態修改的操作, 比如調用setName方法, 如何保存對象的信息呢? 當然, 可以服務端將對象在內存中的信息直接序列化發回去, 當客戶端下次調用時攜帶序列化信息, 服務端接收後反序列化還原對象繼續操作.

過程

個人理解的RPC調用過程:

  1. 客戶端創建RPC對象
  2. 客戶端調用方法
  3. RPC解析方法並將對象及參數做序列化
  4. RPC通過網絡連接發送方法調用
  5. 服務端接收到方法調用, 解析對象及參數反序列化
  6. 服務端執行方法並將結果序列化返回
  7. 客戶端接收到結果並進行解析, 返回給本地調用者
  8. 拿到最終結果

RPC適用於內部網絡不同項目之間的通信, 如果是對外暴露的, 個人感覺還是通過接口的形式吧.

使用RPC顯然會喪失一部分性能, 畢竟調用要走網絡IO, 儘管是內網, 仍然要比本地調用慢上一些, 但帶來了更好的可擴展性和可維護性, 感覺還是不錯的.

之後如果用到的話, 拉個框架看看源碼.

個人理解, 以上...


分享到:


相關文章: