简介

  gRPC是Google主导的一个高性能,跨语言的RPC(远程过程调用)框架。官网: https://grpc.io/
  支持多种常见的流行编程语言,如C++ Java Node.js PHP Python等等。 其实从本质上来看到gRPC其实是基于使用protobuf做为数据传输格式,借助HTTP2协议通信做的一个RPC框架,只要任何语言实现了gRPC的规范即可以实现跨平台调用。类比Restful Api使用json作为数据传输格式,over HTTP1.1进行通信,大致也就这么回事,没想象中那么复杂。

  跨语言和跨平台很大功劳在于Google推出的这个protobuf数据传输格式。我们可以把它看做和json xml一类作用的东西。
protobuf数据格式被编译后形成二进制数据,相对json等传输格式,解析效率高,压缩体积小,并且能够明确传递数据的数据类型等等。

1.什么是protobuf?

  详细信息可以参考官方文档与介绍: https://developers.google.com/protocol-buffers/

2.实践

  采用PHP作为client, Golang作为Server端模拟一个用户登录过程。(PHP目前只支持作为客户端的角色实现。我们知道由于PHP语言特性本身不支持内置提供强大的HTTP服务,借助于fasgic与nginx或者apache等工作,所以这个也是不支持作为gRPC server服务端的主要原因。)

  其实工作顺序大致分为下面几个步骤:

1.编写 protobuf文件,定义好rpc接口 参数 以及返回值

syntax = "proto3";

package Lib.Grpc; // grpc包 在php中也是namespace=> \Lib\Grpc\

option go_package = "protobuf"; // golang中包名

// 成功响应数据
message ApiResponse {
    uint32 Code = 1;
    string msg = 2;
}

// 附加信息
message LoginLogData {
     string OpName = 1;
     string OpAddress = 2;
     string OpIp  = 3;
}

// 登录表单
message LoginFormData {
    string UserName = 1;
    string PassWord = 2;
    LoginLogData log = 3;
}

// welcome接口
service WelcomeApi {
    // 用户登录rpc接口
    rpc DoLogin(LoginFormData) returns (ApiResponse);
}

2.使用protoc工具将上面的protobuf文件转化为符合gRPC的PHP代码以及Go代码

#预备知识:   protobuf知识得了解 以及 protoc生成各种语言的插件集成
   #生成go服务端的protobuf代码
   protoc --go_out=plugins=grpc:../backend-go/lib/protobuf/ *.proto
   
   #生成php端的protobuf代码
   protoc --php_out=../frontend-php/application/  --grpc_out=../frontend-php/application/ --plugin=protoc-gen-grpc=./bin/macos/grpc_php_plugin  .
   
   #每次修改protobuf文件内容或者新增protobuf文件的时候
   #需要执行以下上面的2个命令这样同步protobuf转化代码
   #接下来的事情不用我们管了,直接在代码中new使用我们定义好的对象就行,protoc已经帮我们转化代码了
   #对于Go还好  对于PHP记得自己增加psr-4的目录声明  否则autoload不起作用,导入不了正确的文件

3.PHP客户端要做的事情

1. pecl install grpc  #安装grpc扩展
    2. composer require grpc/grpc google/protobuf  #安装这2个composer包 一个是处理基于grpc扩展的composer包 一个是处理protobuf的composer包

4.启动Go服务端代码

// WelcomeApi服务的实现
    type WelcomeApi struct{
     protobuf.UnimplementedWelcomeApiServer
    }
    
    // 登录
    func (*WelcomeApi) DoLogin(ctx context.Context, req *protobuf.LoginFormData) (*protobuf.ApiResponse, error) {
    
        user := models.Users{}
    
        models.DB.First(&user, "user_name = ?", req.UserName)
    
        response := protobuf.ApiResponse{}
    
        if user.Id == 0 {
            response.Code = 3001
            response.Msg = "用户名或密码错误"
            return &response,nil
        }else{
            if user.PassWord == Md5(req.PassWord) {
                response.Code = 0
                response.Msg = "登录成功"
    
                go func() { // 日志直接异步 go 不阻塞
                    // 记录登录日志
                    loginLog := models.UserLogs{}
                    loginLog.OpName = req.Log.OpName
                    loginLog.OpAddress = req.Log.OpAddress
                    loginLog.OpIp = req.Log.OpIp
                    loginLog.Uid = user.Id
                    models.DB.Create(&loginLog)
                }()
    
            }else{
                response.Code = 3001;
                response.Msg = "用户名或密码错误"
            }
            return &response,nil
        }
    }
    
    func main() {
        lis, err := net.Listen("tcp", ":30081")
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        protobuf.RegisterWelcomeApiServer(s, &WelcomeApi{}) //注册service路由
    
        reflection.Register(s) // 反射
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }

5.PHP客户端调用情况

//开始执行登录
    $loginFrom = new \Lib\Grpc\LoginFormData();
    $loginFrom->setUserName($user_name);
    $loginFrom->setPassWord($pass_word);

    // 操作日志
    $address = getAddressByIp($_SERVER['REMOTE_ADDR']);
    $loginLog = $this->saveOpLog("登录");
    $loginFrom->setLog($loginLog);

    // grpc客户端
    $client = new \Lib\Grpc\WelcomeApiClient('192.168.2.136:30081',[
            'credentials' => \Grpc\ChannelCredentials::createInsecure()
    ]);
    
    // 设置超时时间
    $client->waitForReady(3000000); //微秒
    list($resp,$status) = $client->DoLogin($loginFrom)->wait(); // 像本地一样调用

    if ($status->code == 0) {  // 代表接口执行成功
        if ($resp instanceof \Lib\Grpc\ApiResponse) {
            $success = ['code'=>$resp->getCode(),'msg'=>$resp->getMsg()];
            echo json_encode($success);
         }
    }else{ // 接口执行失败
        throw new Exception($status->details,$status->code);
    }

3.调试工具推荐

  我们在普通开发Restful Api的时候常常借助于Postman等工具来调试接口,以及无界面的工具
curl等。 那么我们开发gRPC项目的时候,是不是也有类似的产品或者工具呢?肯定是有的。

  GUI工具,类似Postman=>bloomrpc: https://github.com/uw-labs/bloomrpc

  UI效果还不错,值得一试。导入protobuf,填写好调用的server端信息即可调试了。

grpc java 简单 grpc javascript_rpc

  命令行工具,类似curl=>grpcurl: https://github.com/fullstorydev/grpcurl