1. 程式人生 > >gRPC入坑記

gRPC入坑記

概要

由於gRPC主要是谷歌開發的,由於一些已知的原因,gRPC跑demo還是不那麼順利的。單獨寫這一篇,主要是gRPC安裝過程中的坑太多了,記錄下來讓大家少走彎路。

主要的坑:

  • 如果使用PHP、Python開發gRPC的客戶端,需要編譯gRPC命令列工具,生成proto的程式碼生成外掛,否則proto裡定義的service無法編譯出來。編譯需要使用GCC4.8級以上版本,否則報不支援C++11。然後需要龜速下週grpc原始碼,並下載一大堆第三方依賴。這個過程非常痛苦。使用golang、java的可以忽略。
  • PHP還需要按照grpc的c擴充套件。編譯需要使用GCC4.8級以上版本。
  • 如果使用golang開發服務,依賴的第三方服務基本是下載不下來的,需要使用go mod
    增加對映規則到github倉庫,github下載也是龜速。

本文講解gRPC demo的同時,會介紹如何解決這些坑。本文對應的Github地址:https://github.com/52fhy/grpc-sample 。該倉庫儲存了demo示例,以及部分系統編譯好的二進位制包,大家覺得有些步驟裡耗時實在太長了,可以直接clone該倉庫,複製二進位制包到對應目錄(僅限測試開發,生產環境還是老老實實自己編譯吧)。

升級GCC

gRPC命令列工具編譯需要使用 GCC4.8及以上版本。CentOS6系列的內建版本是GCC4.7。

使用gcc --version可以檢視版本。

如果你的系統GCC版本>=4.8,可以忽略本節。如果僅使用golang、java,請忽略本節。

注:不建議大家下載GCC原始碼包或者使用yum下載GCC4.8及以上版本,原因:
1) 原始碼包安裝真的是非常非常的慢 2) yum 源下載速度慢的像蝸牛。下面的SCL安裝方法是推薦大家用的,安裝好後原來的版本還能用。

如果需要升級gcc至4.8或更高版本,建議直接採用安裝SCL源之後安裝devtoolset-6(devtoolset-6目前gcc版本為6.3),因為devtoolset-4及之前的版本都已經結束支援,只能通過其他方法安裝。

升級到gcc 6.3:

yum -y install centos-release-scl
yum -y install devtoolset-6-gcc devtoolset-6-gcc-c++ devtoolset-6-binutils
scl enable devtoolset-6 bash

需要注意的是scl命令啟用只是臨時的,退出shell或重啟就會恢復原系統gcc版本。如果要長期使用gcc 6.3的話:

echo "source /opt/rh/devtoolset-6/enable" >>/etc/profile

這樣退出shell重新開啟就是新版的gcc了。其它版本同理。

升級到gcc 7.3:

yum -y install centos-release-scl
yum -y install devtoolset-7-gcc devtoolset-7-gcc-c++ devtoolset-7-binutils
scl enable devtoolset-7 bash

已經停止支援的devtoolset4(gcc 5.2)及之前版本的安裝方法,可能比較慢,大家感興趣的話可以嘗試。

升級到gcc 4.8:

wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtoolset-2.repo
yum -y install devtoolset-2-gcc devtoolset-2-gcc-c++ devtoolset-2-binutils
scl enable devtoolset-2 bash

升級到gcc4.9:

wget https://copr.fedoraproject.org/coprs/rhscl/devtoolset-3/repo/epel-6/rhscl-devtoolset-3-epel-6.repo -O /etc/yum.repos.d/devtoolset-3.repo
yum -y install devtoolset-3-gcc devtoolset-3-gcc-c++ devtoolset-3-binutils
scl enable devtoolset-3 bash

升級到gcc 5.2:

wget https://copr.fedoraproject.org/coprs/hhorak/devtoolset-4-rebuild-bootstrap/repo/epel-6/hhorak-devtoolset-4-rebuild-bootstrap-epel-6.repo -O /etc/yum.repos.d/devtoolset-4.repo
yum install devtoolset-4-gcc devtoolset-4-gcc-c++ devtoolset-4-binutils -y
scl enable devtoolset-4 bash

編譯gRPC命令列工具

如果僅使用golang、java,請忽略本節。

gRPC分C、JAVA、GO、NodeJS版本,C版本包括C++, Python, Ruby, Objective-C, PHP, C#,這些語言都是基於C版本開發的,共用程式碼庫一個程式碼庫。

  • C版本:https://github.com/grpc/grpc
  • JAVA版本:https://github.com/grpc/grpc-java
  • GO版本:https://github.com/grpc/grpc-go
  • Node版本:https://github.com/grpc/grpc-node

如果使用C版本的gRPC,最終要從原始碼裡編譯出下列工具:

grpc_cpp_plugin  
grpc_csharp_plugin  
grpc_node_plugin  
grpc_objective_c_plugin  
grpc_php_plugin  
grpc_python_plugin  
grpc_ruby_plugin

這些工具作為外掛供proto編譯器使用。需要先下載 grpc/grpc github上的原始碼。

git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
cd grpc
git submodule update --init
make && sudo make install

# 生成的外掛路徑
ll ./bins/opt/

# 複製到bin目錄
cp -r ./bins/opt/* /usr/local/bin/

這裡有2個坑:
1、grpc/grpc倉庫比較大,鑑於國內訪問的網速,建議使用國內映象。碼雲(https://gitee.com)提供了同步更新的映象地址:

git clone https://gitee.com/mirrors/grpc-framework grpc

這樣下載速度提高了不少。
2、git submodule update這個命令實際就是在下載.gitmodules檔案裡定義的第三方依賴項到third_party目錄,這個依賴項有很多,大家可以開啟.gitmodules檔案檢視下詳情。依賴的倉庫都在github上,下載沒幾個小時是下載不下來的,就等著慢慢下載吧。

回頭想想,我們花費了很多時間,結果只是為了得到grpc的proto編譯外掛。

福利:Mac下已編譯完成的二進位制包:https://files.cnblogs.com/files/52fhy/bins.tar.gz 。下載以上檔案解壓,將bins/opt/裡的所有檔案複製到/usr/local/bin/

PHP相關支援

如果僅使用golang、java,請忽略本節。

PHP暫時不支援作為grpc的服務端。作為客戶端是可以的,需要機器安裝:

  • protoc編譯工具
  • protobuf c擴充套件
  • gRPC命令列工具(grpc_php_plugin)
  • grpc c擴充套件
  • grpc php庫

其中protocprotobuf c擴充套件已經在 Protobuf 小試牛刀 介紹過了,這裡不再贅述。上一小節裡如果安裝成功,那麼grpc_php_plugin也是有了的。下面介紹如何安裝PHP版的gRPC庫。

安裝grpc c擴充套件:
要求:GCC編譯器需要4.8及以上版本。可以使用pecl安裝:

pecl install grpc

也可以指定版本:

pecl install grpc-1.12.0

或者下載原始碼(http://pecl.php.net/package/grpc)安裝:

wget http://pecl.php.net/get/grpc-1.21.3.tgz
tar zxvf grpc-1.21.3.tgz && cd grpc-1.21.3
phpize
./configure
make
make install

grpc/grpc程式碼庫裡也有PHP擴充套件的C原始碼,在grpc/src/php/ext/grpc目錄,進去也可以直接編譯。

編譯完成後在php.ini裡新增,使用php --ri grpc可以檢視資訊。

安裝完C擴充套件後,還需要使用composer安裝grpc的庫:

composer require grpc/grpc

gRPC示例

編寫gRPC proto

一共定義了三個檔案:

└── proto
    ├── GreeterService.proto
    ├── Response.proto
    └── User.proto

其中 User 作為 Model定義,Response 用於 RPC統一返回定義,GreeterService 則是服務介面定義。

限於篇幅,proto檔案詳見 https://github.com/52fhy/grpc-sample 倉庫的proto目錄。

GreeterService.proto檔案內容如下:

syntax = "proto3";
package Sample.Model; //namesapce

import "User.proto";
import "Response.proto";

service Greeter {
  // Sends a greeting
  rpc SayHello (User) returns (Response) {}
}

這裡面定義了一個service,相當於定義了一個服務介面,我們把方法名、引數定義好了,後面需要去實現它。由於gRPC不支援PHP作為服務端,這裡我們使用Golang作為服務端。

首先需要使用proto工具編譯出golang的程式碼:

mkdir -p Pb_Go

#編譯
cd proto
protoc --go_out=plugins=grpc:../Pb_Go/ *.proto
cd - 

如果提示protoc-gen-go找不到,請根據文章介紹(https://www.cnblogs.com/52fhy/p/11106670.html#autoid-2-0-0)進行安裝。

執行成功,會在 Pb_Go目錄裡生成Go程式碼:

Pb_Go
├── GreeterService.pb.go
├── Response.pb.go
└── User.pb.go

如果需要生成PHP客戶端的程式碼,則需要使用grpc php的命令列工具grpc_php_plugin,前面小結如果執行成功,這個工具已經有了。然後:

out=output/php
mkdir -p $out

#編譯
cd proto
protoc --php_out=../$out --grpc_out=../$out --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin   *.proto
cd - 

# 修改名稱空間
cd $out
mv GPBMetadata Sample/Model/
find . -name '*.php' ! -name example.php -exec sed -i "" -e 's#GPBMetadata#Sample\\Model\\GPBMetadata#g' -e 's#\\Sample\\Model\\GPBMetadata\\Google#\\GPBMetadata\\Google#g' {} \;

上面是在Mac下操作的,命令和Linux有些不同。CentOS下gRPC編譯工具未編譯。

最終生成的檔案:

├── output
│   └── php
│       └── Sample
│           └── Model
│               ├── GPBMetadata
│               │   ├── GreeterService.php
│               │   ├── Response.php
│               │   └── User.php
│               ├── GreeterClient.php
│               ├── Response.php
│               ├── User.php
│               └── UserList.php

注意:編譯那裡如果我們不加--grpc_out=../$out --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin,生成的PHP類是沒有GreeterClient的。這個檔案是gRPC編譯工具自動生成的,用於連線gRPC服務端。

go編寫服務

我們用Golang寫服務端。上面雖然生成了Golang的部分程式碼,但真正的服務還沒有寫呢。

main.go

首先我們新建個main.go,程式碼不多,我直接貼出來:

package main

import (
    "fmt"
    "log"
    "net"
    "time"

    pb "grpc-sample/Pb_Go"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

const (
    addr = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, u *pb.User) (*pb.Response, error) {
    return &pb.Response{ErrCode: 0, ErrMsg: "success", Data: map[string]string{"name": "Hello " + u.Name}}, nil
}

func main() {
    lis, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    fmt.Printf("%s server start at %s\n", time.Now(), addr)

    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    s.Serve(lis)
}

然後就可以編譯了。

有個大坑:go build main.go的時候會先下載go.mod裡定義的依賴(依賴比較多,詳情檢視:https://github.com/52fhy/grpc-sample/blob/master/go.mod),其中下面這條非常慢,倉庫太大了,雖然重定向到github:

replace google.golang.org/api => github.com/googleapis/google-api-go-client v0.6.1-0.20190616000641-99157d28da34

為了快速下載,我在碼雲上做了映象,地址:gitee.com/52fhy/google-api-go-client 。改了之後下載快多了。

編譯成功後,生成了二進位制檔案main。我們可以直接執行:

$ ./main

2019-06-30 17:16:07.752508 +0800 CST m=+0.028838467 server start at :50051

go test

為了測試我們寫的服務是否正常,可以寫測試用例:
test_client.go

package main

import (
    "context"
    "google.golang.org/grpc"
    pb "grpc-sample/Pb_Go"
    "testing"
)

func TestExec(t *testing.T)  {
    conn, err := grpc.Dial(":50051", grpc.WithInsecure())
    if err != nil {
        t.Errorf("dial error: %v\n", err)
    }
    defer conn.Close()

    // 例項化客戶端
    client := pb.NewGreeterClient(conn)

    // 呼叫服務

    user := pb.User{}
    user.Id = 1
    user.Name = "test"
    result, err := client.SayHello(context.Background(), &user)
    if err != nil {
        t.Errorf("grpc error: %v\n", err)
    }
    t.Logf("Recevied: %v\n", result)
}

執行:

$ go test -v client_test.go

=== RUN   TestExec
--- PASS: TestExec (0.01s)
    client_test.go:29: Recevied: errMsg:"success" data:<key:"name" value:"Hello test" > 
PASS
ok      command-line-arguments  0.021s

執行有點慢,感覺依賴的庫多了。

php客戶端

使用gRPC PHP客戶端,確保你已經安裝了:

  • protobuf c擴充套件
  • grpc c擴充套件
  • grpc php庫

示例:
client_test.php

<?php

use Grpc\ChannelCredentials;
use Sample\Model\User;
use Sample\Model\UserList;
use Sample\Model\GreeterClient;

ini_set("display_errors", true);
error_reporting(E_ALL);
require_once "autoload.php";
$user = new User();
$user->setId(1)->setName("test");

$client = new GreeterClient("192.168.99.1:50051", [
    'credentials' => ChannelCredentials::createInsecure(), //不加密
//    'timeout' => 3000000,
]);

//分別是響應、狀態物件
list($reply, $status) = $client->SayHello($user)->wait();

if (!$reply) {
    echo json_encode($status);
    return;
}

//序列化為string
echo $reply->serializeToJsonString(true) . PHP_EOL;
echo $reply->getErrCode() . PHP_EOL; //errCode
echo $reply->getErrMsg() . PHP_EOL; //errMsg

//data
foreach ($reply->getData() as $key => $value) {
    echo $key . "-" . $value . PHP_EOL;
}

執行後輸出:

$ php tests/client_test.php

{"errMsg":"success","data":{"name":"Hello test"}}
0
success
name-Hello test

常見問題

1、CentOS6使用 go mod獲取第三方依賴包unknown revision xxx錯誤
解決:其實go mod呼叫鏈中會用到一些git指令,當git版本比較舊時,呼叫失敗產生錯誤,並給出歧義的提示資訊。方法就是升級git版本,CentOS6自帶的git是1.7版本。升級完畢後,再嘗試go mod。

快速升級方法:
centos6:

# 安裝yum源
wget http://opensource.wandisco.com/centos/6/git/x86_64/wandisco-git-release-6-1.noarch.rpm && rpm -ivh wandisco-git-release-6-1.noarch.rpm

## 安裝git 2.x
yum install git -y

## 驗證
git --version
git version 2.14.1

2、PHP報錯:Fatal error: Class 'Google\Protobuf\Internal\Message' not found
解決:請安裝PHP的protobuf c擴充套件。

3、PHP報錯:Fatal error: Class '\Grpc\BaseStub' not found
解決:使用composer require grpc/grpc安裝grpc。另外對應的grpc C擴充套件也要安裝。

4、下載 github release包很慢怎麼辦?
解決:下載Mac版 Free Download Manager 下載工具可以解決Github 下載緩慢或失敗問題。速度嗖嗖的。

參考

1、為CentOS 6、7升級gcc至4.8、4.9、5.2、6.3、7.3等高版本
http://www.vpser.net/manage/centos-6-upgrade-gcc.html
2、centos 6.x/7.x使用yum升級git版本 - 夜空
https://blog.slogra.com/post-721.html
3、Protobuf 小試牛刀 - 飛鴻影
https://www.cnblogs.com/52fhy/p/11106670.h