RPC全稱Remote Procedure Call,中文譯爲遠程過程調用,簡單理解就是 一種解決方案。php
業務場景:
舉一個大部分phper都接觸過的商城開發,通常商城都有如下幾個模塊git
在常見架構中的體現是:github
那麼在RPC架構中每一個模塊就是一個服務提供者,架構體現:服務器
在這套架構中業務機的職責就是把一個請求 ,拆分紅N個小請求,分發到各個服務裏面,再整合各個服務的結果,返回給用戶。
網絡
例如在某次下單請求中,那麼大概 發送的邏輯以下:架構
1. 業務機接受請求
2. 業務機提取用戶參數,請求用戶服務,獲取用戶餘額等信息,等待結果
3. 業務機提取商品參數,請求商品服務,獲取商品剩餘庫存和價格等信息,等待結果。
4. 業務機融合用戶服務、商品服務的返回結果,進行下一步調用(假設知足購買條件)
5. 業務機調用用戶服務進行扣款,調用商品服務進行庫存扣減,調用訂單服務進行下單(事務邏輯和撤回能夠用請求id保證,或者本身實現其餘邏輯調度)
6. 業務機根據處理響應用戶socket
而在以上發生的行爲,就稱爲遠程過程調用。而調用過程實現的通信協議能夠有不少,好比常見的HTTP、TCP協議。tcp
某個服務故障或者異常時直接熔斷整個服務,而不是一直等到此服務超時spa
當某個服務熔斷以後,服務器將再也不被調用,此時客戶端能夠本身準備一個本地的fallback回掉,返回一個缺省值 ,這樣作,雖然服務水平降低,但好歹,比直接掛掉要強。 服務降級處理是在客戶端實現完成的,與服務端沒有關係操作系統
例如某個服務器最多同時僅能處理100個請求, 或者是cpu負載達到百分之80的時候, 爲了保護服務的穩定性,則不在但願繼續收到 新的鏈接。那麼此時就要求客戶端再也不對其發起請求,例如 你能夠以任何的形式來監控你的服務,當觸發某個條件時(CPU負載80%)下線此服務,業務機動態獲取服務節點時就能夠知道此服務已限流則響應用戶[網絡繁忙,請稍後再試] 或者此服務有多臺機提供則其餘機可繼續提供服務,等被下線的機子恢復後又上線
https://github.com/ar414-com/...
<?php //建立Server對象,監聽 0.0.0.0:20001端口 $serv = new Swoole\Server("0.0.0.0", 20001); $serv->on('Start', function ($serv) { echo "服務已啓動,主進程PID:{$serv->master_pid}\n"; }); //監聽鏈接進入事件 $serv->on('Connect', function ($serv, $fd) { echo "Client: Connect.\n";}); //監聽數據接收事件 $serv->on('Receive', function ($serv, $fd, $from_id, $data) { echo "接收客戶端數據:{$data}\n"; $serv->send($fd, "Server: ".$data); }); //監聽鏈接關閉事件 $serv->on('Close', function ($serv, $fd) { echo "Client: Close.\n";}); //啓動服務器 $serv->start();
<?php //創建鏈接 $fp = stream_socket_client('tcp://127.0.0.1:20001'); //發送數據 fwrite($fp, 'Test'); //主動獲取響應 $data = fread($fp, 65533); echo "服務端響應數據:{$data}\n"; //斷開鏈接 fclose($fp);
客戶端請求Rpc服務(如下並不是完整代碼)
//商品列表 $data = [ 'service' => 'Goods', //服務名稱 'action' => 'getList', //具體方法 'arg' => ['page' => 1] //請求參數 ]; //用戶信息 $data = [ 'service' => 'User', //服務名稱 'action' => 'getUserInfoForToken', //具體方法 'arg' => ['token' => '6aa62603ef82b70597a90d93af04b542'] //請求參數 ]; //打包數據 $dataStr = serialize($data); $dataStr = pack('N', strlen($str)).$str;
請求API網關 API網關自動根據Service參數查詢出對應服務IP、PORT並進行調用返回
本示例爲了方便將Rpc服務配置寫入.env文件 例:
//.env RPC_GOODS_HOST=10.0.0.1 RPC_GOODS_PORT=8899 RPC_USER_HOST=10.0.0.2 RPC_USER_PORT=8899
服務端處理請求( 完整代碼)
//接受請求數據並解包 $data = substr($request,'4'); $data = unserialize($data); //TODO 檢測必須參數 service action //檢測服務是否存在 //$controllerNameSpace是你的控制器命名空間 $service = ucfirst($data['service']); $class = "{$controllerNameSpace}\\{$service}"; if(!class_exists($class)) { //TODO 服務不存在 //設置響應狀態錯誤碼(需自行封裝) $response->setStatus(Response::STATUS_SERVICE_SERVICE_NOT_FOUND); //響應客戶端(需自行封裝) goto response;} //檢測方法是否存在 $class = new \ReflectionClass($class); $action = $data['action']; if(!$class->hasMethod($action)) { //action不存在 //從新組裝參數 //若是方法則調用魔術方法 好比調用一些PDO方法,若是無則調用時返回方法不存在 $request->proxyActionAssemblyArg(); $method = $class->getMethod('__call');} else { $method = $class->getMethod($action);} //調用 $instance = $class->newInstance($request,$response); $ret = $method->invokeArgs($instance,$request->getArg()); $response->setMessage($ret); //響應客戶端(需自行封裝) goto response; //做者的響應封裝(僅供參考): response:{ if ($server->exist($fd)) { $message = $response->getMessage(); $responseData = [ 'status' => $response->getStatus(), 'data' => $message ]; $responseData = serialize($responseData); $responseData = Request::pack($responseData); $server->send($fd,$responseData); //判斷客戶端是否須要長鏈接 if(!$request->getIsKeep()) { $server->close($fd); } } }