前言
gRPC 是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
gRPC 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多複用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間佔用。
本例系統爲 CentOS Linux release 7.5.1804 (Core) ,具體實現如下:
安裝GO(已安裝跳過)
1、安裝yum 源
yum install epel -y
2、然後使用 yum 安裝 Golang:
yum install go -y
查看版本
go version
#go version go1.9.4 linux/amd64
3、配置環境變量
在 /etc/profile 添加:
export GOPATH=/home/go
export PATH=$PATH:$GOPATH/bin
然後執行 source /etc/profile 使之生效,創建GOPATH目錄
mkdir /home/go
安裝protobuf
1、安裝相關軟件
協議編譯器是用C ++編寫的。如果您使用的是C ++,請按照C ++安裝說明在C ++運行時安裝protoc。
yum install autoconf automake libtool gcc gcc-c++ zlib-devel
2、 下載protobuf,並安裝
去到Protocol Buffers下載最新版本(Version3.0.0 beta2),然後解壓到本地。
tar -zxvf protobuf-all-3.5.1.tar.gz
cd protobuf-3.5.1
./configure
make
make install
3、查看protobuf 版本
protoc --version
顯示 libprotoc 3.5.1 ,證明成功。
4、然後安裝golang protobuf直接使用golang的get即可
go get -u github.com/golang/protobuf/proto // golang protobuf 庫
go get -u github.com/golang/protobuf/protoc-gen-go //protoc --go_out 工具
安裝golang的grpc包
能翻牆的同學執行以下命令的就可以簡單實現
go get google.golang.org/grpc
不能翻牆同學也別急,也能實現,解決辦法如下。
1、grpc需要一下依賴:crypto net oauth2 sys text tools,不安裝會報錯。
mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/net.git --depth 1
git clone https://github.com/golang/text.git --depth 1
git clone https://github.com/golang/sys.git --depth 1
git clone https://github.com/golang/crypto.git --depth 1
git clone https://github.com/golang/oauth2.git --depth 1
mkdir -p $GOPATH/src/google.golang.org/
cd $GOPATH/src/google.golang.org
git clone https://github.com/google/go-genproto.git genproto --depth 1
2、從Github上克隆其他的倉庫
cd $GOPATH/src/google.golang.org
git clone https://github.com/grpc/grpc-go.git grpc --depth 1
命令解析:
其中--depth=1 這個參數的意思是隻克隆最新的commit分支。不加也行。
最後的grpc表示的是將克隆的文件存放到那個文件夾裏面。
執行完上面的命令,我們就成功的將grpc的包下載到本地了。
3、 安裝倉庫
cd $GOPATH/src/
go install google.golang.org/grpc
安裝php的grpc擴展
1、下載地址: http://pecl.php.net/package/gRPC
wget http://pecl.php.net/get/grpc-1.12.0.tgz
tar -zxvf grpc-1.12.0.tgz
cd grpc-1.12.0
phpize
./configure --with-php-config=/usr/bin/php-config
make
make install
或用pecl方式安裝
pecl install grpc
添加grpc.so到php.ini配置
vim /etc/php.ini
extension = "grpc.so"
php -m | grep "grpc"
出現 grpc 證明安裝成功
安裝 protobuf 及其 php 擴展
爲了使用gRPC獲得更好的性能,請啓用protobuf C擴展。
protobuf.so使用PECL 安裝擴展。
pecl install protobuf
現在將此行添加到您的php.ini文件中,例如 /etc/php.ini。
extension=protobuf.so
創建proto文件userrpc.proto
在目錄/home/go/src/userrpc,下新建userrpc.proto
syntax = "proto3";
package user;
option go_package = "./grpc/user";
// The User service definition.
service User {
// Get all Users with id - A server-to-client streaming RPC.
rpc GetUsers(UserFilter) returns (stream UserRequest) {}
// Create a new User - A simple RPC
rpc CreateUser (UserRequest) returns (UserResponse) {}
}
// Request message for creating a new user
message UserRequest {
int32 id = 1; // Unique ID number for a User.
string name = 2;
string email = 3;
string phone= 4;
message Address {
string province = 1;
string city = 2;
}
repeated Address addresses = 5;
}
message UserResponse {
int32 id = 1;
bool success = 2;
}
message UserFilter {
int32 id = 1;
}
option go_package = "./grpc/user"; 爲pb.go文件生成的目錄。
syntax = "proto3";說明本教程中的示例使用協議緩衝區語言的proto3版本。
grpc四種服務類型:
1、簡單方式:這就是一般的rpc調用,一個請求對象對應一個返回對象
2、服務端流式(Sever-side streaming )
3、客戶端流式(Client-side streaming RPC)
4、雙向流式(Bidirectional streaming RPC)
之所以用到stream,針對當業務需要傳輸大量的數據時(或者客戶端向服務器傳輸大量數據,或反之,或者雙向需要傳輸大量數據),數據的傳輸時間可能有些長,接收端需要收到所有的數據後才能繼續處理,而不能一邊接收數據一邊處理數據。
這裏我們採用了其中兩種:
// Get all Users with id - A server-to-client streaming RPC.
rpc GetUsers(UserFilter) returns (stream UserRequest) {}
// Create a new User - A simple RPC
rpc CreateUser (UserRequest) returns (UserResponse) {}
使用方式就是前面加 stream 標記。如stream UserRequest
如想了解其他示例,請閱讀http://www.grpc.io/docs/
Protobuf3語言指南
可參考: https://blog.csdn.net/u011518...
編譯 .proto 文件
先生成Go代碼,也可以和php一起生成,這裏分開執行。
先創建生成Go代碼的目錄
cd /home/go/src/userrpc
mkdir grpc/user
如果需要將其以 gRPC 的方式提供服務的話,需需要在編譯時指定插件(--go_out=plugins=grpc:output)。
執行命令:
protoc --go_out=plugins=grpc:. userrpc.proto
查看./grpc/user/目錄下新建了一個userrpc.pb.go
新建服務端server.go
package main
import (
"log"
"net"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "userrpc/grpc/user"
)
const (
port = ":50051"
)
// server is used to implement user.UserServer.
type server struct {
savedUsers []*pb.UserRequest
}
// CreateUser creates a new User
func (s *server) CreateUser(ctx context.Context, in *pb.UserRequest) (*pb.UserResponse, error) {
s.savedUsers = append(s.savedUsers, in)
return &pb.UserResponse{Id: in.Id, Success: true}, nil
}
// GetUsers returns all users by given id
func (s *server) GetUsers(filter *pb.UserFilter, stream pb.User_GetUsersServer) error {
for _, user := range s.savedUsers {
if filter.Id == 0 {
continue
}
if err := stream.Send(user); err != nil {
return err
}
}
return nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Creates a new gRPC server
s := grpc.NewServer()
pb.RegisterUserServer(s, &server{})
s.Serve(lis)
}
客戶端client.go
package main
import (
"io"
"log"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "userrpc/grpc/user"
)
const (
address = "localhost:50051"
)
// createUser calls the RPC method CreateUser of UserServer
func createUser(client pb.UserClient, user *pb.UserRequest) {
resp, err := client.CreateUser(context.Background(), user)
if err != nil {
log.Fatalf("Could not create User: %v", err)
}
if resp.Success {
log.Printf("A new User has been added with id: %d", resp.Id)
}
}
// getUsers calls the RPC method GetUsers of UserServer
func getUsers(client pb.UserClient, id *pb.UserFilter) {
// calling the streaming API
stream, err := client.GetUsers(context.Background(), id)
if err != nil {
log.Fatalf("Error on get users: %v", err)
}
for {
user, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("%v.GetUsers(_) = _, %v", client, err)
}
log.Printf("User: %v", user)
}
}
func main() {
// Set up a connection to the gRPC server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// Creates a new UserClient
client := pb.NewUserClient(conn)
user := &pb.UserRequest{
Id: 1,
Name: "test",
Email: "[email protected]",
Phone: "132222222",
Addresses: []*pb.UserRequest_Address{
&pb.UserRequest_Address{
Province: "hebei",
City: "shijiazhuang",
},
},
}
// Create a new user
createUser(client, user)
// Filter with an id
filter := &pb.UserFilter{Id: 1}
getUsers(client, filter)
}
目錄結構
#tree -L 1
├── client.go
├── grpc
├── server.go
└── userrpc.proto
運行 gRPC 服務
啓動server.go
go run server.go
新打開一個窗口,啓動client.go
go run client.go
結果爲:
2019/07/04 17:01:16 A new User has been added with id: 1
2019/07/04 17:01:16 User: id:1 name:"test" email:"[email protected]" phone:"132222222" addresses:<province:"hebei" city:"shijiazhuang" >
自此一個簡單的 gRPC 服務就搭建起來了。
接下來我們實現php語言客戶端和go服務端通信
安裝 grpc_php_plugin 插件
grpc_php_plugin 插件可以幫我們自動生成(client stub)客戶端(封裝了grpc
的服務接口),方便我們直接調用。
# 下載 grpc 的庫到本地
cd ~ && git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
# 更新子模塊依賴
cd grpc && git submodule update --init
# 這裏我們只編譯 php 的插件 如果要編譯所有的 make && make install
make grpc_php_plugin
# 插件路徑
ll ./bins/opt/grpc_php_plugin
生成php客戶端基類(client stub 類)
#創建user目錄
mkdir user
#tree -L 1
├── client.go
├── grpc
├── server.go
├── user
└── userrpc.proto
執行編譯.proto命令
protoc --php_out=./user --grpc_out=./user --plugin=protoc-gen-grpc=/root/grpc/bins/opt/grpc_php_plugin userrpc.proto
瀏覽user目錄
#tree
├── GPBMetadata
│ └── Userrpc.php
└── User
├── UserClient.php
├── UserFilter.php
├── UserRequest
│ └── Address.php
├── UserRequest_Address.php
├── UserRequest.php
└── UserResponse.php
這個時候你會發現生成了一個UserClient.php 文件,這個文件就是php客戶端基類文件。
注意:如果運行 以下命令是不會生成UserClient.php 文件的
protoc --php_out=plugins=grpc:./user userrpc.proto
使用 composer
管理依賴加載
沒有安裝composer,先安裝
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
在user目錄下創建composer.json
{
"name": "grpc-go-php",
"require": {
"grpc/grpc": "^v1.12.0",
"google/protobuf": "^v3.5.0"
},
"autoload":{
"psr-4":{
"GPBMetadata\\":"GPBMetadata/",
"User\\":"User/"
}
}
}
注意:需要說明的是 "google/protobuf": "^v3.5.0"不是必須的,可以去掉,這個是爲了你在沒有安裝php的protobuf擴展情況下,也能正常運行,這種運行方式相對效率較低。
相關依賴grpc、protobuf的最新版本可參考:https://github.com/grpc/grpc/...
安裝
composer install
這時user目錄下
# tree -L 1
├── composer.json
├── composer.lock
├── GPBMetadata
├── User
└── vendor
在user創建php客戶端client.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use User\UserClient;
// 創建客戶端實例
$userClient = new UserClient('127.0.0.1:50051', [
'credentials' => Grpc\ChannelCredentials::createInsecure()
]);
//處理添加用戶 rpc CreateUser (UserRequest) returns (UserResponse) {}
$address = new User\UserRequest\Address();
$address->setCity("xian");
$address->setProvince("shanxi");
$userRequest = new User\UserRequest();
$userRequest->setId(3);
$userRequest->setEmail("[email protected]");
$userRequest->setName("demo");
$userRequest->setPhone("13000000000");
$userRequest->setAddresses([$address]);
$request = $userClient->CreateUser($userRequest)->wait();
//返回數組
//$response 是 UserResponse 對象
//$status 是數組
list($response, $status) = $request;
foreach ($response as $k=>$v){
echo 'id=>'.$v->getId(),"\n\r";
}
//處理獲取用戶 rpc GetUsers(UserFilter) returns (stream UserRequest) {}
//設置請求參數UserFilter
$userFilter = new User\UserFilter();
$userFilter->setId(1);
$call = $userClient->GetUsers($userFilter);
$features = $call->responses();
foreach ($features as $feature) {
echo "<pre>";
var_dump( $feature->getName());
var_dump( $feature->getId());
foreach ($feature->getAddresses() as $v)
{
var_dump($v->getProvince());
var_dump($v->getCity());
}
echo "</pre>";
// process each feature
} // the loop will end when the server indicates there is no more responses to be sent.
新打開一個窗口
運行client.php
php client.php
結果:
<pre>string(4) "demo"
int(3)
string(6) "shanxi"
string(4) "xian"
</pre>
證明Go爲服務端,php爲客戶端的grpc服務搭建完成。
小結:
優點
Grpc使用http2協議,故支持http2的全雙工,多路複用等特性,基於HTTP/2 多語言客戶端實現容易。
Grpc使用protobuf作爲序列化工具,具有序列化效率高,壓縮數據體積小等優點。
缺點
Api實現起來比較繁瑣,給開發帶來難度。
總的來說Grpc是一個不錯的跨語言rpc解決方案,當然每個人都自己的看法或見解。針對不同的業務場景採用不同的解決方案,最終都是運行效率和開發效率的相互妥協的結果。
參考資料:
官方網站:http://www.grpc.io/
官方文檔:http://www.grpc.io/docs/
中文翻譯:http://doc.oschina.net/grpc