乐趣区

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…。该仓库存储了 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-…
  • GO 版本:https://github.com/grpc/grpc-go
  • Node 版本:https://github.com/grpc/grpc-…

如果使用 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/fil…。下载以上文件解压,将 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… 仓库的 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…)进行安装。

执行成功,会在 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…),其中下面这条非常慢,仓库太大了,虽然重定向到 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 ‘GoogleProtobufInternalMessage’ not found
解决:请安装 PHP 的 protobuf c 扩展。

3、PHP 报错:Fatal error: Class ‘GrpcBaseStub’ 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/c…
2、centos 6.x/7.x 使用 yum 升级 git 版本 – 夜空
https://blog.slogra.com/post-…
3、Protobuf 小试牛刀 – 飞鸿影
https://www.cnblogs.com/52fhy…

退出移动版