- 最近在看微服務相關的設計,其中
grpc
算是微服務框架的標配所以要研究一下 - 本文適合對 grpc 有一點印象的,不知道的站內可以搜下,
golang
板塊有人寫的挺好的,我就不寫了~ - 因為公司有使用 三種語言的團隊,所以尋求一種除了,
http
之外更高效的協議,才有了之後的事情,最後挺一下swoft
跟hyperf
加油!
gin 實現 grpc 簡單的應答
安裝protobuf
1、安裝相關軟體 ,我用的是 contOS7+windows , mac應該更好裝一點
yum install autoconf automake libtool gcc gcc-c++ zlib-devel
2、 下載protobuf,並安裝
去到 Protocol Buffers 下載最新版本,然後解壓到本地。
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.10.1/protoc-3.10.1-linux-x86_64.zip
unzip protoc-3.10.1-linux-x86_64.zip
protoc --version # 能看到 版本資訊就安裝成功了
安裝 protobuf golang 外掛
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
前提條件
-
go 1.13 以上版本
-
go.mod 代理 ok
export GOPROXY=https://goproxy.cn export GO111MODULE=on
-
目錄如下
- rpc-hello - pb - hello.proto - go.mod
-
hello.proto 內容如下
syntax = "proto3"; package hello; // 定義服務 service Hello { rpc SayHello (HelloRequest) returns (HelloReply) {} } // 請求體的結構體 message HelloRequest { string name = 1; } // 響應的結構體 message HelloReply { string message = 1; int64 code = 2; }
-
進入 pb 下 執行
protoc --go_out=plugins=grpc:. hello.proto
- 會發現 多了
rpc-hello\pb\hello.pb.go
這樣的一個檔案
grpc四種服務型別:
1、簡單方式:這就是一般的rpc呼叫,一個請求物件對應一個返回物件
2、服務端流式(Sever-side streaming )
3、客戶端流式(Client-side streaming RPC)
4、雙向流式(Bidirectional streaming RPC)
簡單方式的呼叫
-
更改目錄為
rpc-hello - pb - hello.proto - hello.pb.go - go.mod - service - service.go - client - client.go
-
服務端 實現 service
package main import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "log" "net" pb "rpc-hello/pb" ) // server 用來實現 hello.HelloServer type server struct{} // 實現 hello.SayHello 方法 // (context.Context, *HelloRequest) (*HelloReply, error) func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { return &pb.HelloReply{Message: "Hello " + in.Name, Code: 200}, nil } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterHelloServer(s, &server{}) //在 server 中 註冊 gRPC 的 reflection service reflection.Register(s) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
-
客戶端 實現 client.go
package main import ( "fmt" "github.com/gin-gonic/gin" "google.golang.org/grpc" "log" "net/http" pb "rpc-hello/pb" ) func main() { r := gin.Default() r.GET("/rpc/hello", func(c *gin.Context) { sayHello(c) }) // Run http server if err := r.Run(":8052"); err != nil { log.Fatalf("could not run server: %v", err) } } func sayHello(c *gin.Context) { // Set up a connection to the server. conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := pb.NewHelloClient(conn) name := c.DefaultQuery("name","戰士上戰場") req := &pb.HelloRequest{Name: name} res, err := client.SayHello(c, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "result": fmt.Sprint(res.Message), "code": fmt.Sprint(res.Code), }) }
-
先 執行
go run service.go
切換另一個終端執行go run client.go
訪問http://xxx.xxx.xx.xx:8052/rpc/hello
就能看到效果~
php 原生實現客戶端
安裝 protoc-gen-php 擴充套件
git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
cd grpc
git submodule update --init # 這一步 要下4個小時,建議還是要掛git代理
make grpc_php_plugin # 警告沒事,沒error 就行
# 建議 國內使用者換這個 ,再切換到對應版本分支,不然太難受了
git clone https://gitee.com/devin2019/grpc.git
cd grpc
git checkout xxxx # 對應分支
git submodule update --init #這一步 要下4個小時,建議還是要掛git代理
make grpc_php_plugin # 警告沒事,沒error 就行
- 最終
grpc_php_plugin
在 你的/pathto/grpc/bins/opt/grpc_php_plugin
安裝 grpc 擴充套件
wget https://pecl.php.net/get/grpc-1.25.0.tgz
tar -zxvf grpc-1.25.0.tgz
/usr/bin/phpize #(這個根據`phpize`實際情況來)
./configure --with-php-config=/usr/bin/php-config #(這個根據`php-config`實際情況來)
make && make install
vim /etc/php.d/grpc.ini #這個根據實際情況去決定 是改`php.ini`還是別的什麼
寫入 extension=grpc.so
安裝 protobuf 擴充套件
-
要想 gRPC獲得更好的效能,就安裝 protobuf 擴充套件
wget https://pecl.php.net/get/protobuf-3.10.0.tgz tar -zxvf protobuf-3.10.0.tgz /usr/bin/phpize #(這個根據`phpize`實際情況來) ./configure --with-php-config=/usr/bin/php-config #(這個根據`php-config`實際情況來) make && make install vim /etc/php.d/protobuf.ini #這個根據實際情況去決定 是改`php.ini`還是別的什麼 寫入 extension=protobuf.so
-
偷懶做法 就直接
composer
拉個包"require": { "google/protobuf": "^v3.10.0" },
生成檔案 帶客戶端
-
使用命令為
protoc --php_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=/root/go/bin/grpc_php_plugin hello.proto
這個是帶Client
的做法 -
這裡 我 把
grpc_php_plugin
做了軟連結 你按安裝時的位置輸出就好 -
不帶
Client
的 為protoc --php_out=plugins=grpc:. grpc.proto
|-- composer.json |-- composer.lock |-- GPBMetadata | |-- Hello.php |-- Hello | |-- HelloClient.php | |-- HelloReply.php | |-- HelloRequest.php |-- hello.proto |-- index.php |-- vendor
index.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use \Grpc\ChannelCredentials;
use \Hello\HelloClient;
use \Hello\HelloRequest;
use \Hello\HelloReply;
// 建立客戶端例項
$helloClient = new HelloClient('127.0.0.1:50051', [
'credentials' => ChannelCredentials::createInsecure()
]);
$helloRequest = new HelloRequest();
$helloRequest->setName("有事別找我");
$request = $helloClient->SayHello($helloRequest)->wait();
//返回陣列
/** @var array $status */
/** @var HelloReply $response */
list($response, $status) = $request;
var_dump($response->getMessage());
echo PHP_EOL;
var_dump($status);
composer.json
{
"name": "rpc/test",
"require": {
"grpc/grpc": "v1.25.0",
"google/protobuf": "^v3.10.0"
},
"autoload":{
"psr-4":{
"GPBMetadata\\":"GPBMetadata/",
"Hello\\":"Hello/"
}
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}
}
-
先做了快取的,一定要在改過之後
composer dump -o
-
開啟
service.go
執行php index.php
客戶端就完成了
php swoole
框架 hyperf
實現 實現客戶端 與服務端 與 golang
服務互調
- 官方文件
swoole
安裝就不講了,比起swoole2.x
現在的安裝也很簡單了hyperf
安裝也很簡單,唯一要 注意的是 需要 在php.ini
中 加入swoole.use_shortname=off
- 使用
composer create-project hyperf/hyperf-skeleton
來 構建基礎程式碼 - 選中
grpc
服務其它的額外元件都不用選
服務端
-
我們先將 上一步 原生生成的檔案 ,在專案根目錄下加一個
grpc
目錄,內部結構如下|-- GPBMetadata | |-- Hello.php |-- Hello | |-- HelloClient.php | |-- HelloReply.php | |-- HelloRequest.php
-
更改 composer.json 同樣是在 psr-4 中 加入
"GPBMetadata\\": "grpc/GPBMetadata", "Grpc\\": "grpc/Grpc"
-
config/autoload/server
中 加入[ 'name' => 'grpc', 'type' => Server::SERVER_HTTP, 'host' => '0.0.0.0', 'port' => 50051, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ SwooleEvent::ON_REQUEST => [\Hyperf\GrpcServer\Server::class, 'onRequest' ], ], ],
-
在
config/routes.php
里加入Router::addServer("grpc", function () { Router::addGroup('/hello.Hello', function () { Router::post('/SayHello', 'App\Controller\IndexController@sayHello'); }); });
-
在
app/Controller/IndexController.php
中 加入public function sayHello(HelloRequest $request) { $message = new HelloReply(); $message->setMessage("Hello {$request->getName()}"); $message->setCode(200); return $message; }
-
這裡要注意的是 .proto 檔案中的定義和 gRPC server 路由的對應關係:
/{package}.{service}/{rpc}
-
服務端封裝詳情可以看原始碼,也不難
客戶端
-
在
app/Controller/IndexController.php
中修改use Hello\HelloClient; use Hello\HelloReply; use Hello\HelloRequest; use Hyperf\HttpServer\Contract\RequestInterface; public function index(RequestInterface $request) { $client = new HelloClient("127.0.0.1:50051", ['credentials' => null]); $name = $request->input("name","戰士上戰場"); $helloRequest = new HelloRequest(); $helloRequest->setName($name); /** * @var HelloReply $reply */ list($reply, $status) = $client->sayHello($helloRequest); $message = $reply->getMessage(); $code = $reply->getCode(); $client->close(); var_dump(memory_get_usage(true)); return [ 'message' => $message, 'code' => $code, ]; }
-
上一步中 用了
HelloClient
看看 框架封裝,跟原生的區別 -
封裝的
namespace Hello; use Hyperf\GrpcClient\BaseClient; /** * 定義服務 */ class HelloClient extends BaseClient{ public function sayHello(HelloRequest $argument) { return $this->simpleRequest( '/hello.Hello/SayHello', $argument, [HelloReply::class, 'decode'] ); } }
-
自動生成的
namespace Hello; /** * 定義服務 */ class HelloClient extends \Grpc\BaseStub { /** * @param string $hostname hostname * @param array $opts channel options * @param \Grpc\Channel $channel (optional) re-use channel object */ public function __construct($hostname, $opts, $channel = null) { parent::__construct($hostname, $opts, $channel); } /** * @param \Hello\HelloRequest $argument input argument * @param array $metadata metadata * @param array $options call options */ public function SayHello(\Hello\HelloRequest $argument, $metadata = [], $options = []) { return $this->_simpleRequest( '/hello.Hello/SayHello', $argument, ['\Hello\HelloReply', 'decode'], $metadata, $options); } }
-
結論是其實差不了多少
-
然後 你可以試試
php grpc
服務端 跟golang grpc
客戶端的互調 -
果然裝環境才是學習程式最難的地方,特別是這個下了4個小時的git命令,太南了0.0