编程

gRPC 概述

996 2023-06-28 22:20:00

概述

GRPC 是一个高性能、通用的开源 RPC 框架,基于底层 HTTP/2 协议标准和协议层 Protobuf 序列化协议开发,支持众多的开发语言。

gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

gRPC 使用 protocol buffers 作为接口描述语言(IDL)以及底层的信息交换格式

优点

  1. 基于 HTTP/2 之上的二进制协议(Protobuf 序列化机制);
  2. 一个连接上可以多路复用,并发处理多个请求和响应;
  3. 多种语言的类库实现;
  4. 服务定义文件和自动代码生成(.proto 文件和 Protobuf 编译工具)。
  5. RPC 还提供了很多扩展点,用于对框架进行功能定制和扩展,例如,通过开放负载均衡接口可以无缝的与第三方组件进行集成对接(Zookeeper、域名解析服务、SLB 服务等)

通信方式

Simple RPC

简单 rpc ,这就是一般的 rpc 调用,一个请求对象对应一个返回对象

proto 语法:

rpc simpleHello(Person) returns (Result) {}

Server-side streaming RPC

服务端流式 rpc,一个请求对象,服务端可以传回多个结果对象

proto 语法:

rpc serverStreamHello(Person) returns (stream Result) {}

Client-side streaming RPC

客户端流式 rpc 客户端传入多个请求对象,服务端返回一个响应结果

proto 语法:

rpc clientStreamHello(stream Person) returns (Result) {}

Bidirectional streaming RPC

双向流式 rpc,结合客户端流式 rpc 和服务端流式 rpc,可以传入多个对象,返回多个响应对象

proto 语法:

rpc biStreamHello(stream Person) returns (stream Result) {}

安装

golang 版本的 grpc 要求 go 版本要在 1.6 以上

安装 gRPC

使用 go get 命令安装 grpc

$ go get -u google.golang.org/grpc

由于某些不可逆原因,上面命令会报连接超时,可以到 github 上将项目 clone$GOPATH/src/google.golang.org/

$ cd $GOPATH/src/google.golang.org
$ git clone git@github.com:grpc/grpc-go.git grpc

安装 Protocol Buffers v3

$ apt install protobuf-compiler

安装 protoc 插件

安装 golang 版本对应的 protobuf 生成工具

$ go get -u github.com/golang/protobuf/protoc-gen-go

运行 demo

进入 example 目录

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

删除原来的 helloworld.pb.go 文件,并使用 protoc 生成自己生成一个

$ rm helloworld/helloworld.pb.go // 删除原来的helloworld.pb.go文件
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld // 根据 .proto 文件生成对应的.go文件

编写 grpc 接口时,在 .proto 文件定义接口通信数据格式和接口信息,然后通过 protoc 自动生成对应的 go 代码,大大方便了开发

  • -I PATH:指定搜索引入的目录。可以指定多个,目录会按顺序搜索。未指定,则使用当前工作目录。
  • --go_out:指定输出 go 代码
  • plugins=grpc.proto 中的 servicegrpc 扩展的功能,需要使用 grpc 插件进行解析才能生成对应的接口定义代码。

运行 grpc servergrpc client

$ go run greeter_server/main.go // 启动grpc server
$ go run greeter_client/main.go // 启动grpc client

如果 helloworld 下面没有 greeter_server 和 greeter_client 文件夹执行下列两种其中一种命令

$ go get -u google.golang.org/grpc/examples/helloworld/greeter_client
$ go get -u google.golang.org/grpc/examples/helloworld/greeter_server
$ go get -u google.golang.org/grpc

实践

开发一个求和的 grpc 接口

定义 .proto 文件

proto 目录下创建接口描述文件 sum.proto 文件:

Message 命名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式
Enums 类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式
即便业务上不需要参数也必须指定一个请求消息,一般会定义一个空 message。

syntax = "proto3"; // 使用 proto3,没写默认proto2。建议grpc配合proto3使用,兼容性更高

// java生成选项
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package proto; // 生成的go所属的package

// 需要序列化的结构体
message SumResp {
    int64 sum = 1;
}

message SumReq {
    int64 a = 1;
    int64 b = 2;
}

service CalcSvc {
    // 每个rpc接口声明都必须有且一个参数和一个返回值
    rpc Sum(SumReq) returns (SumResp) {}
}

根据接口描述文件生成源码

进入 proto 目录,执行

$ protoc sum.proto --go_out=plugins=grpc:.

可以看到,在本目录下生成 sum.pb.go 文件,且 packageproto

开发服务端接口

首先查看生成的 sum.pb.go 文件,可以看到根据 sum.proto 文件中的 CalcSvc 接口定义生成了对应的接口:

// CalcSvcServer is the server API for CalcSvc service.
type CalcSvcServer interface {
    // 每个rpc接口声明都必须有且一个参数和一个返回值
    Sum(context.Context, *SumReq) (*SumResp, error)
}

开发服务端接口只要就是根据这些接口定义实现具体的业务逻辑

在项目下创建 service/main.go

package main

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    "grpc-demo/proto" //路径可能不同,看自己的proto包放在哪个目录下
    "log"
    "net"
)

// 类型断言
var _ proto.CalcSvcServer = new(CalcSvc)

type CalcSvc struct{}

func (CalcSvc) Sum(ctx context.Context, req *proto.SumReq) (resp *proto.SumResp, err error) {
    // 建议使用GetA,不要直接使用req.A,可能存在req=nil的情况
    a := req.GetA() 
    b := req.GetB()
    log.Println("request coming ...")
    return &proto.SumResp{
        Sum: a + b,
    }, err
}

func main() {
    lis, err := net.Listen("tcp", ":8888")
    if err != nil {
        log.Fatal(err)
    }
    // 注册服务到gRPC
    s := grpc.NewServer()
    proto.RegisterCalcSvcServer(s, &CalcSvc{})
    // 启用Server Reflection,可以使用gRPC CLI去检查services
    // https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md
    reflection.Register(s)
    // 启动服务
    if err := s.Serve(lis); err != nil {
        log.Fatal(err)
    }
}

客户端访问

在项目下创建 client/main.go

package main

import (
    "context"
    "google.golang.org/grpc"
    "grpc-demo/proto" //路径可能不同,看自己的proto包放在哪个目录下
    "log"
)

func main() {
    // 创建gRPC连接
    // WithInsecure option 指定不启用认证功能
    conn, err := grpc.Dial(":8888", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    // 创建gRPC client
    client := proto.NewCalcSvcClient(conn)
    // 请求gRPC server
    resp, err := client.Sum(context.Background(), &proto.SumReq{
        A: 5,
        B: 10,
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("5 + 10 = %d", resp.GetSum())
}

运行

$ go run service/main.go &
$ go run client/main.go