hyperf從零開始構建微服務(二)——構建服務消費者

白狼棧 發表於 2021-09-15
微服務

閱讀目錄

我們說過,服務提供者可以提供各種服務,它可以和資料庫進行互動;服務消費者是純消費的服務,只需要遠端訪問服務提供者即可。

下面我們按步驟構建消費者模組。

原始碼已上傳至github,https://github.com/bailangzhan/hyperf-rpc

1、構建服務消費者

除了對時區進行設定,其他的元件暫時都不安裝,選擇“n”即可。

composer create-project hyperf/hyperf-skeleton shop_consumer_user 
Creating a "hyperf/hyperf-skeleton" project at "./shop_consumer_user"
Installing hyperf/hyperf-skeleton (v2.2.1)
  - Installing hyperf/hyperf-skeleton (v2.2.1): Extracting archive
Created project in /data/web/test/hyperf-rpc/shop_consumer_user
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
> Installer\Script::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies
  What time zone do you want to setup ?
  [n] Default time zone for php.ini
Make your selection or type a time zone name, like Asia/Shanghai (n):
Asia/Shanghai
  Do you want to use Database (MySQL Client) ?
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (yes): n
  Do you want to use Redis Client ?
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (yes): n
  Which RPC protocol do you want to use ?
  [1] JSON RPC with Service Governance
  [2] JSON RPC
  [3] gRPC
  [n] None of the above
  Make your selection or type a composer package name and version (n): n
  Which config center do you want to use ?
  [1] Apollo
  [2] Aliyun ACM
  [3] ETCD
  [4] Nacos
  [n] None of the above
  Make your selection or type a composer package name and version (n): n
  Do you want to use hyperf/constants component ?
  [y] yes
  [n] None of the above
  Make your selection (n): n
  Do you want to use hyperf/async-queue component ? (A simple redis queue component)
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (n): n
  Do you want to use hyperf/amqp component ?
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (n): n
  Do you want to use hyperf/model-cache component ?
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (n): n
  Do you want to use hyperf/elasticsearch component ?
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (n): n
  Do you want to use hyperf/tracer component ? (An open tracing protocol component, adapte with Zipkin etc.)
  [y] yes
  [n] None of the above
  Make your selection or type a composer package name and version (n): n

2、安裝json rpc依賴

cd shop_consumer_user
composer require hyperf/json-rpc

3、安裝 JSON RPC 客戶端

shop_consumer_user 不需要對外提供服務,所以我們只安裝客戶端,不需要安裝hyperf/rpc-server元件

composer require hyperf/rpc-client

4、server配置

server的配置這裡用預設的就好了,9501埠提供http服務,不需要改動

'servers' => [
    [
        'name' => 'http',
        'type' => Server::SERVER_HTTP,
        'host' => '0.0.0.0',
        'port' => 9501,
        'sock_type' => SWOOLE_SOCK_TCP,
        'callbacks' => [
            Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
        ],
    ],
],

5、編寫業務程式碼

5-1、編寫服務消費者類

app下新建JsonRpc目錄,編寫UserService.php和UserServiceInterface.php檔案

【UserServiceInterface.php】

<?php
namespace App\JsonRpc;
interface UserServiceInterface
{
    public function createUser(string $name, int $gender);
    public function getUserInfo(int $id);
}
<span class="redactor-invisible-space">

</span>【UserService.php】

<?php
namespace App\JsonRpc;
use Hyperf\RpcClient\AbstractServiceClient;
class UserService extends AbstractServiceClient implements UserServiceInterface
{
    /**
     * 定義對應服務提供者的服務名稱
     * @var string
     */
    protected $serviceName = 'UserService';
    /**
     * 定義對應服務提供者的服務協議
     * @var string
     */
    protected $protocol = 'jsonrpc-http';
    /**
     * @param string $name
     * @param int $gender
     * @return mixed
     */
    public function createUser(string $name, int $gender)
    {
        return $this->__request(__FUNCTION__, compact('name', 'gender'));
    }
    /**
     * @param int $id
     * @return mixed
     */
    public function getUserInfo(int $id)
    {
        return $this->__request(__FUNCTION__, compact('id'));
    }
}

hyperf 官方的hyperf/rpc-client元件已經幫我們實現了rpc遠端呼叫的實現,所以我們只需要再配置一下服務消費者,告訴hyperf從哪個節點哪個埠呼叫即可。

5-2、consumer配置

config/autoload/services.php內定義consumers如下:(沒有services.php檔案的可以自行建立)

<?php
return [
    'consumers' => [
        [
            // 對應消費者類的 $serviceName
            'name' => 'UserService',
            // 直接對指定的節點進行消費,通過下面的 nodes 引數來配置服務提供者的節點資訊
            'nodes' => [
                ['host' => '127.0.0.1', 'port' => 9600],
            ],
        ]
    ],
];

5-3、配置 UserServiceInterface

為了可以方便的注入 UserServiceInterface,我們在 config/autoload/dependencies.php 內定義 UserServiceInterface 和 UserService 的關係如下:

App\JsonRpc\UserServiceInterface::class => App\JsonRpc\UserService::class,

5-4、編寫UserController,實現獲取使用者和建立使用者的介面呼叫

【app\Controller\UserController.php】

<?php
declare(strict_types=1);
namespace App\Controller;
use App\JsonRpc\UserServiceInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
/**
 * Class UserController
 * @package App\Controller
 * @AutoController()
 */
class UserController extends AbstractController
{
    /**
     * @Inject()
     * @var UserServiceInterface
     */
    private $userServiceClient;
    public function createUser()
    {
        $name = (string) $this->request->input('name', '');
        $gender = (int) $this->request->input('gender', 0);
        return $this->userServiceClient->createUser($name, $gender);
    }
    public function getUserInfo()
    {
        $id = (int) $this->request->input('id');
        return $this->userServiceClient->getUserInfo($id);
    }
}

6、postman訪問測試

啟動shop_consumer_user專案的同時,務必要保證 shop_provider_user 也啟動了,不然請求會失敗。

hyperf從零開始構建微服務(二)——構建服務消費者

hyperf從零開始構建微服務(二)——構建服務消費者

7、自動配置服務消費者

你可能已經注意到 app\JsonRpc\UserService 類的方法並沒有實際意義,只是構建引數發起請求並返回響應結果,千篇一律的操作著實增加了複雜度。hyperf支援自動配置服務消費者代理類(生產者暫不支援自動配置)。

自動配置非常簡單,只需要在 consumer 配置項增加service配置即可,如下:

return [
    'consumers' => [
        [
            // 對應消費者類的 $serviceName
            'name' => 'UserService',
            // 服務介面名,可選,預設值等於 name 配置的值,如果 name 直接定義為介面類則可忽略此行配置,
            // 如 name 為字串則需要配置 service 對應到介面類
            'service' => \App\JsonRpc\UserServiceInterface::class,
            // 直接對指定的節點進行消費,通過下面的 nodes 引數來配置服務提供者的節點資訊
            'nodes' => [
                ['host' => '127.0.0.1', 'port' => 9600],
            ],
        ]
    ],
];

現在我們做兩件事,測試consumer走的是自動配置還是手動建立的UserService

  1. 把 config/autoload/dependencies.php 內定義 UserServiceInterface 和 UserService 的關係遮蔽
  2. 在 App\JsonRpc\UserService::getUserInfo() 方法內列印點資料測試
GET請求 http://127.0.0.1:9501/user/getUserInfo?id=2
結果發現控制檯並沒有任何輸出,走的是自動配置的consumer

反過來

  1. 我們再把 config/autoload/dependencies.php 內定義 UserServiceInterface 和 UserService 的關係放開
  2. 把 config/autoload/services.php 檔案內 consumers 的配置項 service 遮蔽
GET請求 http://127.0.0.1:9501/user/getUserInfo?id=2
string(36) "App\JsonRpc\UserService::getUserInfo"
發現控制檯輸出了我們在 App\JsonRpc\UserService::getUserInfo() 方法內列印的資料,
走的是手動建立的consumer

在沒有特殊情況下,後續consumer我們僅做配置,不在手動建立,因為沒有建立的必要。

8、配置優化

我們注意到 config/autoload/services.php 檔案內 consumers 的配置,一個服務是一個配置,服務消費者需要消費的服務可能很多,所以我們很有必要優化下這裡的寫法,下面是參考官網的寫法:

// 服務定義
$consumerServices = [
    'UserService' => \App\JsonRpc\UserServiceInterface::class,
];
return [
    'consumers' => value(function () use ($consumerServices) {
        $consumers = [];
        foreach ($consumerServices as $name => $interface) {
            $consumers[] = [
                'name' => $name,
                'service' => $interface,
                'nodes' => [
                    ['host' => '127.0.0.1', 'port' => 9600],
                ],
            ];
        }
        return $consumers;
    }),
];

這樣一來,我們每次只需要在陣列 $consumerServices 內新增需要新的服務即可。

最後,我們來看一個比較大的問題。

consumer拿到的結果,又是字串又是物件,還動不動直接 Internal Server Error. 資料格式的不統一非常不利於前端小夥伴解析。

統一結果處理

為了規範,我們制定了一個簡單的標準,統一返回帶有code,message,data的資料格式,有興趣的小夥伴可以先研究下怎麼解決這個問題,我們下一節繼續。