PHP GRPC 基本教學

Gary Ng
11 min readSep 24, 2022

--

在使用 GRPC 前我們要先稍微了解一下什麼是 GRPC,GRPC 是由 Google 所開發出來基於 Http2.0 的一種架構,主打著跨平台、高效能。

在使用 GRPC 前也須先學習 Protocol Buffer 語法,他是 GRPC 的資料交換格式。

首先先來定義一個 Protocol Buffer

syntax = "proto3";package protobuf.user;message User {   // 包含哪些欄位資訊
uint32 id = 1;
}message UserRequest { // 參數
}
service UserService {
rpc GetUser(UserReqeust) returns(User) {};
}

syntax: 定義 protocol buffer 所使用的版本, 上面範例是設定為使用 protocol buffer 3, 沒有設定的話預設為使用 2 版。

package: 可以避免 service 以及 message 名稱衝突。

message: 定義訊息結過需使用,以上範例為定義一個 User 的訊息結構,而裡面則定義了這個訊息結構所包含的資訊有哪些。

service: 定義 grpc 服務 (講白一點就是定義這個 GRPC 有哪些 api 可以使用)。

rpc … returns : 定義 grpc 服務可以使用的 method 以及回傳值為何,以上面的例子來看的話就是有一個 GetUser 的 method, 而參數為 UserRequest 回傳值為 User。

注意!!! 假如使用 oneof 的話則型別不能是 repeated 以及 map

想要更加深入學習 protocol buffer 的話可以到官網閱讀文件 https://developers.google.com/protocol-buffers/docs/proto3#services

以下稍後直接說明如何在本機進行 GRPC 的開發

注意!!!! PHP GRPC 目前只支援 unidirectional API

首先要先安裝 php grpc extension

安裝 grpc extension

sudo pecl install grpc

安裝完成後可以查看 php.ini 是否有加入了 grpc extension

extension="grpc.so"

安裝 protobuf

sudo pecl install protobuf

一樣安裝完成後可以查看 php.ini 是否已加入 protobuf extension

extension="protobuf.so"

這邊還要再安裝產生 grpc client code 套件以及 grpc server code 套件

ps: 以下範例用 mac 當作教學

首先安裝產生 client code 的 command, 而 mac 可以直接透過 homebrew 安裝

brew install grpc

安裝完成後再 /usr/local/bin 應該可以看到 grpc-php-plugin 這個檔案

ls -al /usr/local/bin | grep grpc

緊接著要安裝產生 server code 的 binary 檔案, 這邊我是直接使用 go 進行安裝(假如沒有 安裝 go 的人可以先去安裝 go)

go get github.com/spiral/php-grpc/cmd/protoc-gen-php-grpc

安裝完成後就會在 go path 的 bin 資料夾下看到該檔案, 我這邊 go path 設定的位置是加入下的 go 資料夾中

ls -al /<username>/go/bin

接著在 laravel Server & Client 專案安裝 grpc 套件

composer 要安裝以下幾個套件

composer require google/protobuf
composer require grpc/grpc

然後假如是要作為 grpc server 的話要再多安裝

composer require spiral/roadrunner-grpc

所需工具都安裝完成後我們緊接著進行簡單的 proto 產生

首先在專案中新增 protobuf/src/user.proto 檔案內容如下

syntax = "proto3";

package mypackage;

service UserService {
rpc getUser(UserRequest) returns(User) {}
}

message UserRequest {
uint32 id=1;
}

enum Gender {
male = 0;
female = 1;
other = 2;
}

message User {
uint32 id = 1;
string name = 2;
string email = 3;
string created_at = 4;
string updated_at = 5;
message UserProfile {
string nickname = 1;
string intro = 3;
Gender gender = 4;
string birthday = 5;
}
}

透過以下指令分別產生 server & client code

產生 server code (要透過 protoc-gen-php-grpc)

protoc --proto_path=protobuf  \  # proto 位置
--php_out=protobuf/build \ # 資料要產生在何處
--grpc_out=./protobuf/build \ # server 的 code 要產生到何處
--plugin=protoc-gen-grpc=/<home>/go/bin/protoc-gen-php-grpc \
./protobuf/src/user.proto # protobuf 檔案

該指令會產生在 protobuf 資料夾下

緊接著產生 client code, 而 client code 的部分要透過 grpc_php_plugin

protoc --proto_path=protobuf --php_out=protobuf/build --grpc_out=./protobuf/build  --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin   ./protobuf/src/greet.proto

下完指令後會產生一個 UserServiceClient.php 的檔案

視情況要在 composer.json 的 autoload 增加類似以下的 的設定, 設定完成後重新執行 composer dump-autoload

透過 composer 安裝的 spiral/roadrunner-grpc 去下載 rr binary

./vendor/bin/rr get-binary

安裝完成後會在專案根目錄下看到 rr binary 檔案

接著在 server grpc 專案根目錄下新增 worker.php

<?php

use
Illuminate\Contracts\Console\Kernel;

require_once __DIR__ . '/vendor/autoload.php';

/** 載入 Laravel 核心 */
$app = require_once __DIR__.'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();

/** 加入 gRPC Server 物件 */
$server = $app->make(\Spiral\GRPC\Server::class);
/** 註冊想要的服務 */
$server->registerService(\Mypackage\UserServiceInterface::class, new \App\Services\UserService());
/** 啟始 worker */
$worker = new Spiral\RoadRunner\Worker(new Spiral\Goridge\StreamRelay(STDIN, STDOUT));
$server->serve($worker);

以及設定 .rr.yaml 設定檔案

version: "2.7"

grpc:
listen: "tcp://0.0.0.0:50051"
proto: "protobuf/src/user.proto"
workers:
command: "php worker.php"
pool:
numWorkers: 4

設定完後接著啟動 grpc server

./rr serve -c ./.rr.yaml -d

grpc server 啟動起來後在 console 應該會看到類似的畫面, 看到類似的畫面及代表成功啟動。

然後這邊假如 server 有改 code 的話你會發現 client 端打 server 還是會是舊的 code, 會需要將 grpc server 再重新啟動 server 才會使用最新的程式碼。為了處理每次都要手動重啟的麻煩,我們這邊將進行額外的設定去進行 hot reload,設定如下

我們在 server 端的 .rr.yaml 設定

# hot reload
reload:
# 間隔幾秒更新
interval: 1s
# 全域的檔案格式
patterns:
- .php
# 哪些服務要 reload
services:
grpc:
# recursive 搜尋檔案格式
recursive: true
# 忽略哪些資料夾
ignore:
- vendor
patterns:
- .php
dirs:
- .

設定好後重新啟動 grpc server, 之後假如 server 端有更改程式碼的話, grpc 都會重新 reload。

想了解夠多 roadrunner plugin 裡面的設定可以到官網進行學習 https://roadrunner.dev/docs/plugins-reload/2.x/en

而 GRPC 有分為四種類型

  1. Unary: 單向,Client 發送一次 Request, Server Response 一次
  2. Server Streaming: Client 發送一次 Request, Server 可以多次回傳 Response
  3. Client Streaming: Client 發送多次 Request, Server Response 一次
  4. Bi Directional Streaming: 雙向多次,也就是 Client 發送多次 Request, Server 也會回傳多次 Response

但是注意!!! 目前 PHP 只有 Unary 可以使用,假如想要使用 Server Streaming, Client Streaming 等可以使用 Golang

參考資料:

  1. https://github.com/spiral/php-grpc
  2. https://hackmd.io/@tML6ejGhR7q68VfQ4kLDQg/By-WdVz_Y
  3. https://zhuanlan.zhihu.com/p/109428382
  4. https://poyu677.medium.com/grpc-%E4%BD%BF%E7%94%A8-php-%E5%AF%A6%E4%BD%9C-ab485e9f1044
  5. https://pkg.go.dev/lab.yaozh.com/kz-rr/php-grpc/cmd/protoc-gen-php-grpc

6. https://www.zybuluo.com/phper/note/1764719

7. https://doc.oschina.net/grpc?t=60136

8. https://grpc.io/docs/languages/php/basics/

9. https://roadrunner.dev/docs/intro-about/2.x/en

10. https://sagardash.me/challenge-php-grpc-server-and-communicate-with-the-golang-grpc-client-84478279a317

11. https://github.com/spiral/roadrunner-laravel

12. https://ithelp.ithome.com.tw/articles/10245345

13. https://dev.to/khepin/building-a-grpc-server-in-php-3bgc

14. https://spiral.dev/docs/grpc-service/3.0/en

15. https://pjchender.dev/golang/grpc-getting-started/

--

--

Gary Ng
Gary Ng

Written by Gary Ng

軟體工程師、後端工程師

No responses yet