10.2 grpc的Go服务端和PHP客户端实现

前言

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 源

  1. yum install epel -y

2、然后使用 yum 安装 Golang:

  1. yum install go -y

查看版本

  1. go version
  2. #go version go1.9.4 linux/amd64

3、配置环境变量 在 /etc/profile 添加:

  1. export GOPATH=/home/go
  2. export PATH=$PATH:$GOPATH/bin

然后执行 source /etc/profile 使之生效,创建GOPATH目录

  1. mkdir /home/go

安装protobuf

1、安装相关软件 协议编译器是用C ++编写的。如果您使用的是C ++,请按照C ++安装说明在C ++运行时安装protoc。

  1. yum install autoconf automake libtool gcc gcc-c++ zlib-devel

2、 下载protobuf,并安装 去到Protocol Buffers下载最新版本(Version3.0.0 beta2),然后解压到本地。

  1. tar -zxvf protobuf-all-3.5.1.tar.gz
  2. cd protobuf-3.5.1
  3. ./configure
  4. make
  5. make install

3、查看protobuf 版本

  1. protoc --version

显示 libprotoc 3.5.1 ,证明成功。

4、然后安装golang protobuf直接使用golang的get即可

  1. go get -u github.com/golang/protobuf/proto // golang protobuf 库
  2. go get -u github.com/golang/protobuf/protoc-gen-go //protoc --go_out 工具

安装golang的grpc包

会科学上网的同学执行以下命令的就可以简单实现

  1. go get google.golang.org/grpc

不能科学上网同学也别急,也能实现,解决办法如下。

1、grpc需要一下依赖:crypto net oauth2 sys text tools,不安装会报错。

  1. mkdir -p $GOPATH/src/golang.org/x
  2. cd $GOPATH/src/golang.org/x
  3. git clone https://github.com/golang/net.git --depth 1
  4. git clone https://github.com/golang/text.git --depth 1
  5. git clone https://github.com/golang/sys.git --depth 1
  6. git clone https://github.com/golang/crypto.git --depth 1
  7. git clone https://github.com/golang/oauth2.git --depth 1
  8. mkdir -p $GOPATH/src/google.golang.org/
  9. cd $GOPATH/src/google.golang.org
  10. git clone https://github.com/google/go-genproto.git genproto --depth 1

2、从Github上克隆其他的仓库

  1. cd $GOPATH/src/google.golang.org
  2. git clone https://github.com/grpc/grpc-go.git grpc --depth 1

命令解析: 其中—depth=1 这个参数的意思是只克隆最新的commit分支。不加也行。 最后的grpc表示的是将克隆的文件存放到那个文件夹里面。 执行完上面的命令,我们就成功的将grpc的包下载到本地了。

3、 安装仓库

  1. cd $GOPATH/src/
  2. go install google.golang.org/grpc

安装php的grpc扩展

1、下载地址: http://pecl.php.net/package/gRPC

  1. wget http://pecl.php.net/get/grpc-1.12.0.tgz
  2. tar -zxvf grpc-1.12.0.tgz
  3. cd grpc-1.12.0
  4. phpize
  5. ./configure --with-php-config=/usr/bin/php-config
  6. make
  7. make install

或用pecl方式安装

  1. pecl install grpc

添加grpc.so到php.ini配置

  1. vim /etc/php.ini
  2. extension = "grpc.so"
  3. php -m | grep "grpc"

出现 grpc 证明安装成功

安装 protobuf 及其 php 扩展

为了使用gRPC获得更好的性能,请启用protobuf C扩展。

protobuf.so使用PECL 安装扩展。

  1. pecl install protobuf

现在将此行添加到您的php.ini文件中,例如 /etc/php.ini。

  1. extension=protobuf.so

创建proto文件userrpc.proto

在目录/home/go/src/userrpc,下新建userrpc.proto

  1. syntax = "proto3";
  2. package user;
  3. option go_package = "./grpc/user";
  4. // The User service definition.
  5. service User {
  6. // Get all Users with id - A server-to-client streaming RPC.
  7. rpc GetUsers(UserFilter) returns (stream UserRequest) {}
  8. // Create a new User - A simple RPC
  9. rpc CreateUser (UserRequest) returns (UserResponse) {}
  10. }
  11. // Request message for creating a new user
  12. message UserRequest {
  13. int32 id = 1; // Unique ID number for a User.
  14. string name = 2;
  15. string email = 3;
  16. string phone= 4;
  17. message Address {
  18. string province = 1;
  19. string city = 2;
  20. }
  21. repeated Address addresses = 5;
  22. }
  23. message UserResponse {
  24. int32 id = 1;
  25. bool success = 2;
  26. }
  27. message UserFilter {
  28. int32 id = 1;
  29. }

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/,这里还有一个proto生成工具,可以实现从mysql生成对应的proto文件感兴趣的请移步 https://github.com/guyan0319/mysql-to-proto

Protobuf3语言指南

可参考: https://blog.csdn.net/u011518120/article/details/54604615#GeneratingYourClasses

编译 .proto 文件

先生成Go代码,也可以和php一起生成,这里分开执行。

先创建生成Go代码的目录

  1. cd /home/go/src/userrpc
  2. mkdir grpc/user

如果需要将其以 gRPC 的方式提供服务的话,需需要在编译时指定插件(—go_out=plugins=grpc:output)。

执行命令:

  1. protoc --go_out=plugins=grpc:. userrpc.proto

查看./grpc/user/目录下新建了一个userrpc.pb.go

新建服务端server.go

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "golang.org/x/net/context"
  6. "google.golang.org/grpc"
  7. pb "userrpc/grpc/user"
  8. )
  9. const (
  10. port = ":50051"
  11. )
  12. // server is used to implement user.UserServer.
  13. type server struct {
  14. savedUsers []*pb.UserRequest
  15. }
  16. // CreateUser creates a new User
  17. func (s *server) CreateUser(ctx context.Context, in *pb.UserRequest) (*pb.UserResponse, error) {
  18. s.savedUsers = append(s.savedUsers, in)
  19. return &pb.UserResponse{Id: in.Id, Success: true}, nil
  20. }
  21. // GetUsers returns all users by given id
  22. func (s *server) GetUsers(filter *pb.UserFilter, stream pb.User_GetUsersServer) error {
  23. for _, user := range s.savedUsers {
  24. if filter.Id == 0 {
  25. continue
  26. }
  27. if err := stream.Send(user); err != nil {
  28. return err
  29. }
  30. }
  31. return nil
  32. }
  33. func main() {
  34. lis, err := net.Listen("tcp", port)
  35. if err != nil {
  36. log.Fatalf("failed to listen: %v", err)
  37. }
  38. // Creates a new gRPC server
  39. s := grpc.NewServer()
  40. pb.RegisterUserServer(s, &server{})
  41. s.Serve(lis)
  42. }

客户端client.go

  1. package main
  2. import (
  3. "io"
  4. "log"
  5. "golang.org/x/net/context"
  6. "google.golang.org/grpc"
  7. pb "userrpc/grpc/user"
  8. )
  9. const (
  10. address = "localhost:50051"
  11. )
  12. // createUser calls the RPC method CreateUser of UserServer
  13. func createUser(client pb.UserClient, user *pb.UserRequest) {
  14. resp, err := client.CreateUser(context.Background(), user)
  15. if err != nil {
  16. log.Fatalf("Could not create User: %v", err)
  17. }
  18. if resp.Success {
  19. log.Printf("A new User has been added with id: %d", resp.Id)
  20. }
  21. }
  22. // getUsers calls the RPC method GetUsers of UserServer
  23. func getUsers(client pb.UserClient, id *pb.UserFilter) {
  24. // calling the streaming API
  25. stream, err := client.GetUsers(context.Background(), id)
  26. if err != nil {
  27. log.Fatalf("Error on get users: %v", err)
  28. }
  29. for {
  30. user, err := stream.Recv()
  31. if err == io.EOF {
  32. break
  33. }
  34. if err != nil {
  35. log.Fatalf("%v.GetUsers(_) = _, %v", client, err)
  36. }
  37. log.Printf("User: %v", user)
  38. }
  39. }
  40. func main() {
  41. // Set up a connection to the gRPC server.
  42. conn, err := grpc.Dial(address, grpc.WithInsecure())
  43. if err != nil {
  44. log.Fatalf("did not connect: %v", err)
  45. }
  46. defer conn.Close()
  47. // Creates a new UserClient
  48. client := pb.NewUserClient(conn)
  49. user := &pb.UserRequest{
  50. Id: 1,
  51. Name: "test",
  52. Email: "fasd@163.com",
  53. Phone: "132222222",
  54. Addresses: []*pb.UserRequest_Address{
  55. &pb.UserRequest_Address{
  56. Province: "hebei",
  57. City: "shijiazhuang",
  58. },
  59. },
  60. }
  61. // Create a new user
  62. createUser(client, user)
  63. // Filter with an id
  64. filter := &pb.UserFilter{Id: 1}
  65. getUsers(client, filter)
  66. }

目录结构

  1. #tree -L 1
  2. ├── client.go
  3. ├── grpc
  4. ├── server.go
  5. └── userrpc.proto

运行 gRPC 服务

启动server.go

  1. go run server.go

新打开一个窗口,启动client.go

  1. 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:”fasd@163.com” phone:”132222222” addresses:

自此一个简单的 gRPC 服务就搭建起来了。

接下来我们实现php语言客户端和go服务端通信

安装 grpc_php_plugin 插件

您需要gRPC PHP protoc插件来生成客户端存根类。它可以从.proto服务定义生成服务器和客户端代码。

当您make从此repo的根目录运行时,它应该已经编译。该插件可以在bins/opt目录中找到。

您还可以通过运行以下命令来构建gRPC PHP protoc插件:

  1. $ cd ~
  2. $ git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc
  3. $ cd grpc
  4. $ git submodule update --init
  5. $ make grpc_php_plugin

插件可能会使用新protobuf版本的新功能,因此请确保安装的protobuf版本与您构建此插件的grpc版本兼容。

生成php客户端基类

  1. #创建user目录
  2. mkdir user
  3. #tree -L 1
  4. ├── client.go
  5. ├── grpc
  6. ├── server.go
  7. ├── user
  8. └── userrpc.proto

执行编译.proto命令

  1. protoc --php_out=./user --grpc_out=./user --plugin=protoc-gen-grpc=/root/grpc/bins/opt/grpc_php_plugin userrpc.proto

浏览user目录

  1. #tree
  2. ├── GPBMetadata
  3. └── Userrpc.php
  4. └── User
  5. ├── UserClient.php
  6. ├── UserFilter.php
  7. ├── UserRequest
  8. └── Address.php
  9. ├── UserRequest_Address.php
  10. ├── UserRequest.php
  11. └── UserResponse.php

这个时候你会发现生成了一个UserClient.php 文件,这个文件就是php客户端基类文件。

注意:如果运行 以下命令是不会生成UserClient.php 文件的

  1. protoc --php_out=plugins=grpc:./user userrpc.proto

使用 composer 管理依赖加载

没有安装composer,先安装

  1. curl -sS https://getcomposer.org/installer | php
  2. mv composer.phar /usr/local/bin/composer

在user目录下创建composer.json

  1. {
  2. "name": "grpc-go-php",
  3. "require": {
  4. "grpc/grpc": "^v1.12.0",
  5. "google/protobuf": "^v3.5.0"
  6. },
  7. "autoload":{
  8. "psr-4":{
  9. "GPBMetadata\\":"GPBMetadata/",
  10. "User\\":"User/"
  11. }
  12. }
  13. }

注意:需要说明的是 “google/protobuf”: “^v3.5.0”不是必须的,可以去掉,这个是为了你在没有安装php的protobuf扩展情况下,也能正常运行,这种运行方式相对效率较低。

相关依赖grpc、protobuf的最新版本可参考:https://github.com/grpc/grpc/tree/master/src/php

安装

  1. composer install

这时user目录下

  1. # tree -L 1
  2. ├── composer.json
  3. ├── composer.lock
  4. ├── GPBMetadata
  5. ├── User
  6. └── vendor

在user创建php客户端client.php

  1. <?php
  2. require_once __DIR__ . '/vendor/autoload.php';
  3. use User\UserClient;
  4. // 创建客户端实例
  5. $userClient = new UserClient('127.0.0.1:50051', [
  6. 'credentials' => Grpc\ChannelCredentials::createInsecure()
  7. ]);
  8. //处理添加用户 rpc CreateUser (UserRequest) returns (UserResponse) {}
  9. $address = new User\UserRequest\Address();
  10. $address->setCity("xian");
  11. $address->setProvince("shanxi");
  12. $userRequest = new User\UserRequest();
  13. $userRequest->setId(3);
  14. $userRequest->setEmail("demo@163.com");
  15. $userRequest->setName("demo");
  16. $userRequest->setPhone("13000000000");
  17. $userRequest->setAddresses([$address]);
  18. $request = $userClient->CreateUser($userRequest)->wait();
  19. //返回数组
  20. //$response 是 UserResponse 对象
  21. //$status 是数组
  22. list($response, $status) = $request;
  23. foreach ($response as $k=>$v){
  24. echo 'id=>'.$v->getId(),"\n\r";
  25. }
  26. //处理获取用户 rpc GetUsers(UserFilter) returns (stream UserRequest) {}
  27. //设置请求参数UserFilter
  28. $userFilter = new User\UserFilter();
  29. $userFilter->setId(1);
  30. $call = $userClient->GetUsers($userFilter);
  31. $features = $call->responses();
  32. foreach ($features as $feature) {
  33. echo "<pre>";
  34. var_dump( $feature->getName());
  35. var_dump( $feature->getId());
  36. foreach ($feature->getAddresses() as $v)
  37. {
  38. var_dump($v->getProvince());
  39. var_dump($v->getCity());
  40. }
  41. echo "</pre>";
  42. // process each feature
  43. } // the loop will end when the server indicates there is no more responses to be sent.

新打开一个窗口

运行client.php

  1. php client.php

结果:

  1. string(4) "demo"
  2. int(3)
  3. string(6) "shanxi"
  4. string(4) "xian"

证明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

https://www.jianshu.com/p/3d10009df578

links