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

栏目: PHP · 发布时间: 4年前

内容简介: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

前言

gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、 JavaGo 语言版本,分别是: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: "fasd@163.com",
        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:"fasd@163.com" 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("demo@163.com");
$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

links


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人人都是架构师:分布式系统架构落地与瓶颈突破

人人都是架构师:分布式系统架构落地与瓶颈突破

高翔龙 / 电子工业出版社 / 2017-5 / 69

《人人都是架构师:分布式系统架构落地与瓶颈突破》并没有过多渲染系统架构的理论知识,而是切切实实站在开发一线角度,为各位读者诠释了大型网站在架构演变过程中出现一系列技术难题时的解决方案。《人人都是架构师:分布式系统架构落地与瓶颈突破》首先从分布式服务案例开始介绍,重点为大家讲解了大规模服务化场景下企业应该如何实施服务治理;然后在大流量限流/消峰案例中,笔者为大家讲解了应该如何有效地对流量实施管制,避......一起来看看 《人人都是架构师:分布式系统架构落地与瓶颈突破》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具