PHP实现RPC(简版)
概述
RPC這個東西是什么? 第一次聽說他, 還要在它的前邊加個G, 當時我以為GRPC是一項技術, 后來才知道, 并不是這樣. GRPC只是RPC的谷歌實現(xiàn).
谷歌搜了一下, RPC就是一種: 遠程函數(shù)調(diào)用, 看到這里, 我已經(jīng)等不及了, 不往下看了, 先自己實現(xiàn)一個. 如果只給你這樣一個概念, 如何實現(xiàn)調(diào)用遠程函數(shù)的功能呢?
自己實現(xiàn)
自己嘗試實現(xiàn)一個粗糙的PHP版本. (不想看可以跳過的)
思路
遠程調(diào)用, 只需要解決下面問題:
先來解決通信問題, 直接粗暴的tcp socket
傳輸?shù)臄?shù)據(jù)格式, 直接用json進行傳輸
調(diào)用本地函數(shù)?? 這就要借助一下PHP的魔術函數(shù)了, __call() 這個函數(shù)是一個類調(diào)用不存在的方法時會跑到這里來, 所以, 我們返回一個類, 在call方法中進行遠程調(diào)用, 這樣, 在本地看來就只是在調(diào)用一個方法.
開始實現(xiàn)
PHP中進行socket連接十分簡單, 直接調(diào)用系統(tǒng)函數(shù). 通信問題解決了, 剩下的就是傳輸數(shù)據(jù)了, so easy
經(jīng)過一番摸索, 看下結果
服務器內(nèi)容:
<?php class RpcServer{private $port = 0; // 監(jiān)聽端口號private $host = ''; // IPpublic function __construct($host, $port){$this->host = $host;$this->port = $port;}/*** 運行, 監(jiān)聽端口并處理*/public function run(){// 創(chuàng)建socket$server = stream_socket_server("tcp://{$this->host}:{$this->port}");if(empty($server)) throw new Exception('創(chuàng)建套接字失敗');// 監(jiān)聽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);// 創(chuàng)建對象并調(diào)用方法$class = $array['class'] ?? '';$method = $array['method'] ?? '';$params = $array['params'] ?? [];$instance = new $class();$result = $instance->$method(...$params);fwrite($client, json_encode($result));} } // 測試調(diào)用類 class Test{public function tt(){return 'return_tt';}public function add($a, $b){return $a + $b;} }(new RpcServer('127.0.0.1', 8888))->run();調(diào)用方:
<?php 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){// 創(chuàng)建客戶端$client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}");if(empty($client)) return null;// 發(fā)送數(shù)據(jù)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);結果:
嗯, 還闊以. 當然, 問題還是有很多的, 比如不能實現(xiàn)保存對象的修改狀態(tài)等等.
其實對象可以通過序列化和反序列化來傳輸, 額, Java中, 不知道PHP有沒有這種技術.
當然, 一個RPC中必然大量使用反射、序列化、動態(tài)加載、代理、網(wǎng)絡請求等等, 這只是一個超級超級粗糙的示例.
繼續(xù)
nice, 自己做完了, 對RPC是個什么東西有了一個基本的概念.
WHAT
RPC是什么? 簡單說, 就是遠程函數(shù)調(diào)用. 字面意思, 很好理解.
WHY
看到一個技術, 一定會問的一個問題就是: 為什么? 一個技術基本不會平白無故出現(xiàn), 都是為了解決某些問題, 那么RPC解決了什么問題呢? 字面含義: 遠程函數(shù)調(diào)用
為什么要進行遠程函數(shù)調(diào)用, 把函數(shù)拿過來本地調(diào)用不就好了? 還不用走網(wǎng)絡IO, 速度更快一些. 很好, 現(xiàn)在假設, 你真的這樣做了, 當項目變得龐大, 你想要進行拆分, 拆分后的有: 項目A, 項目B…, 這時, 你發(fā)現(xiàn)這些拆分的項目部分邏輯是重疊的, 比如用戶信息相關, 怎么辦? 如果不抽出來, 以后的維護成本會變得很高, 一處改處處改. 如果抽出來, 跨項目如何進行調(diào)用? 哎, 走過路過不要錯過, RPC推薦給你.
HOW
那么如何實現(xiàn)RPC呢?
在剛才使用PHP簡單實現(xiàn)中, 已經(jīng)發(fā)現(xiàn)了. 需要解決的問題如下:
1.網(wǎng)絡通信
說到底, 網(wǎng)絡通信不過兩種: tcp udp.
有沒有使用udp實現(xiàn)的RPC呢? 貌似也有.
使用tcp協(xié)議實現(xiàn)的RPC也有, 當然, 不光傳輸層協(xié)議, 也有直接通過應用層協(xié)議: http、websocket等等建立連接的. 當然, 如果需要頻繁調(diào)用, 可以不斷開tcp連接, 在一段時間內(nèi)一直保持連接, 避免頻繁握手.
2.信息格式
信息格式就有很多選擇了, json、xml等等, 也可以自己定制, 只要發(fā)送端和接收端統(tǒng)一信息格式就行了.
3.對象狀態(tài)保存
對于一個類的調(diào)用, 通常都會有類狀態(tài)修改的操作, 比如調(diào)用setName方法, 如何保存對象的信息呢? 當然, 可以服務端將對象在內(nèi)存中的信息直接序列化發(fā)回去, 當客戶端下次調(diào)用時攜帶序列化信息, 服務端接收后反序列化還原對象繼續(xù)操作.
過程
個人理解的RPC調(diào)用過程:
RPC適用于內(nèi)部網(wǎng)絡不同項目之間的通信, 如果是對外暴露的, 個人感覺還是通過接口的形式吧.
使用RPC顯然會喪失一部分性能, 畢竟調(diào)用要走網(wǎng)絡IO, 盡管是內(nèi)網(wǎng), 仍然要比本地調(diào)用慢上一些, 但帶來了更好的可擴展性和可維護性, 感覺還是不錯的.
之后如果用到的話, 拉個框架看看源碼.
個人理解, 以上…
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
以上是生活随笔為你收集整理的PHP实现RPC(简版)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP usort 函数底层排序
- 下一篇: php7改进,关注一下:PHP 7.3.