PhpRpc 從 0 到 0.7

AR414發表於2020-03-06

1.什麼是RPC

RPC全稱Remote Procedure Call,中文譯為遠端過程呼叫,簡單理解就是 一種解決方案。

業務場景:
舉一個大部分phper都接觸過的商城開發,一般商城都有以下幾個模組

  • 商品模組
  • 訂單模組
  • 會員模組
  • XX模組

在常見架構中的體現是:

rpc11

那麼在RPC架構中每個模組就是一個服務提供者,架構體現:

在這套架構中業務機的職責就是把一個請求 ,拆分成N個小請求,分發到各個服務裡面,再整合各個服務的結果,返回給使用者。
rpc12

例如在某次下單請求中,那麼大概 傳送的邏輯如下:

1. 業務機接受請求
2. 業務機提取使用者引數,請求使用者服務,獲取使用者餘額等資訊,等待結果
3. 業務機提取商品引數,請求商品服務,獲取商品剩餘庫存和價格等資訊,等待結果。
4. 業務機融合使用者服務、商品服務的返回結果,進行下一步呼叫(假設滿足購買條件)
5. 業務機呼叫使用者服務進行扣款,呼叫商品服務進行庫存扣減,呼叫訂單服務進行下單(事務邏輯和撤回可以用請求id保證,或者自己實現其他邏輯排程)
6. 業務機根據處理響應使用者

而在以上發生的行為,就稱為遠端過程呼叫。而呼叫過程實現的通訊協議可以有很多,比如常見的HTTP、TCP協議。

服務熔斷

某個服務故障或者異常時直接熔斷整個服務,而不是一直等到此服務超時

服務降級

當某個服務熔斷之後,伺服器將不再被呼叫,此時客戶端可以自己準備一個本地的fallback回掉,返回一個預設值 ,這樣做,雖然服務水平下降,但好歹,比直接掛掉要強。 服務降級處理是在客戶端實現完成的,與服務端沒有關係

服務限流

例如某個伺服器最多同時僅能處理100個請求, 或者是cpu負載達到百分之80的時候, 為了保護服務的穩定性,則不在希望繼續收到 新的連線。那麼此時就要求客戶端不再對其發起請求,例如 你可以以任何的形式來監控你的服務,當觸發某個條件時(CPU負載80%)下線此服務,業務機動態獲取服務節點時就可以知道此服務已限流則響應使用者[網路繁忙,請稍後再試] 或者此服務有多臺機提供則其他機可繼續提供服務,等被下線的機子恢復後又上線

2.Php Tcp通訊

原始碼

https://github.com/ar414-com/RpcDemo

開發環境要求

  • 保證 PHP 版本大於等於 7.2
  • 保證 Swoole 擴充版本大於等於 4.3.5
  • 使用 Linux / FreeBSD / MacOS 這三類作業系統

作者開發環境

  • PHP 7.2
  • Swoole 4.3.5
  • CentOS 7.2

rpc21

建立一個最基本的TCP伺服器

<?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();

rpc22

<?php

//建立連線
$fp = stream_socket_client('tcp://127.0.0.1:20001');

//傳送資料
fwrite($fp, 'Test');

//主動獲取響應
$data = fread($fp, 65533);

echo "服務端響應資料:{$data}\n";

//斷開連線
fclose($fp);

客戶端

rpc23

服務端

rpc24

3.客戶端呼叫與服務端處理(提供思路)

客戶端與伺服器的資料傳輸約定

客戶端請求Rpc服務(以下並非完整程式碼)

  • 場景:例如在一個商場系統中,我們將商品庫和使用者庫兩個服務切分開到不同的伺服器當中
  • 當使用者開啟商場首頁的時候, 我們希望App向某個閘道器發起請求,
  • 該閘道器可以自動的幫我們請求商品列表和使用者資訊等資料
//商品列表
$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);
        }
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章