1.什麼是RPC
RPC全稱Remote Procedure Call,中文譯為遠端過程呼叫,簡單理解就是 一種解決方案。
業務場景:
舉一個大部分phper都接觸過的商城開發,一般商城都有以下幾個模組
- 商品模組
- 訂單模組
- 會員模組
- XX模組
在常見架構中的體現是:
那麼在RPC架構中每個模組就是一個服務提供者,架構體現:
在這套架構中業務機的職責就是把一個請求 ,拆分成N個小請求,分發到各個服務裡面,再整合各個服務的結果,返回給使用者。
例如在某次下單請求中,那麼大概 傳送的邏輯如下:
1. 業務機接受請求
2. 業務機提取使用者引數,請求使用者服務,獲取使用者餘額等資訊,等待結果
3. 業務機提取商品引數,請求商品服務,獲取商品剩餘庫存和價格等資訊,等待結果。
4. 業務機融合使用者服務、商品服務的返回結果,進行下一步呼叫(假設滿足購買條件)
5. 業務機呼叫使用者服務進行扣款,呼叫商品服務進行庫存扣減,呼叫訂單服務進行下單(事務邏輯和撤回可以用請求id保證,或者自己實現其他邏輯排程)
6. 業務機根據處理響應使用者
而在以上發生的行為,就稱為遠端過程呼叫。而呼叫過程實現的通訊協議可以有很多,比如常見的HTTP、TCP協議。
服務熔斷
某個服務故障或者異常時直接熔斷整個服務,而不是一直等到此服務超時
服務降級
當某個服務熔斷之後,伺服器將不再被呼叫,此時客戶端可以自己準備一個本地的fallback回掉,返回一個預設值 ,這樣做,雖然服務水平下降,但好歹,比直接掛掉要強。 服務降級處理是在客戶端實現完成的,與服務端沒有關係
服務限流
例如某個伺服器最多同時僅能處理100個請求, 或者是cpu負載達到百分之80的時候, 為了保護服務的穩定性,則不在希望繼續收到 新的連線。那麼此時就要求客戶端不再對其發起請求,例如 你可以以任何的形式來監控你的服務,當觸發某個條件時(CPU負載80%)下線此服務,業務機動態獲取服務節點時就可以知道此服務已限流則響應使用者[網路繁忙,請稍後再試] 或者此服務有多臺機提供則其他機可繼續提供服務,等被下線的機子恢復後又上線
2.Php Tcp通訊
原始碼
開發環境要求
- 保證 PHP 版本大於等於 7.2
- 保證 Swoole 擴充版本大於等於 4.3.5
- 使用 Linux / FreeBSD / MacOS 這三類作業系統
作者開發環境
- PHP 7.2
- Swoole 4.3.5
- CentOS 7.2
建立一個最基本的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();
<?php
//建立連線
$fp = stream_socket_client('tcp://127.0.0.1:20001');
//傳送資料
fwrite($fp, 'Test');
//主動獲取響應
$data = fread($fp, 65533);
echo "服務端響應資料:{$data}\n";
//斷開連線
fclose($fp);
客戶端
服務端
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 協議》,轉載必須註明作者和本文連結