基本概念
什么是 gRPC?

gRPC 最初是 google 开发的高性能,且功能强大的开源 RPC 框架,后来被纳入云原生基金会的托管项目中,由于背靠 google 老大哥,不论是技术储备还是生态建设都十分的成熟和完善。是一个应用非常广泛的 RPC 框架。

grpc服务端主动调用客户端 grpc 服务_golang

RPC 详解我们知道,RPC 框架中有两个核心组件,客户端存根(client-stub)和服务端存根(server-stub)。业务代码通过调用存根提供的接口,使得远程调用看起来像本地接口调用一样。要创建这两个存根,我们需要明确客户端存根和和服务端存根传递消息的格式,传递消息使用的协议,以及类似参数传递等问题(这些在RPC 详解中已经介绍,这里不再赘述)。而定义这些服务使用的语言我们称作(IDL),服务定义语言(interface definition language)。

gRPC 使用 proto buffers 作为服务定义语言,编写 proto 文件,即可完成服务的定义。

gRPC 为什么使用 Proto Buffers?
  • 可读性好
  • 支持多种语言代码的生成
  • 二进制数据表示,传输更快,序列化和反序列化更高效;
  • 采用强类型协议,更稳定
  • 兼容性更好,新增接口服务不影响原服务的使用

grpc服务端主动调用客户端 grpc 服务_sql_02

如何定义一个 protocol message
syntax = "proto3";

message <NameOfTheMessage> {
  <data-type> name_of_field_1 = tag_1;
  <data-type> name_of_field_2 = tag_2;
  ...
  <data-type> name_of_field_n = tag_n;
}
  • 消息类型采用驼峰格式
  • 消息域采用小写字母蛇形格式
  • 基本数据类型有:
    string, bool, bytes,
    float, double
    int32, int64, uint32, uint4, sint32, sint64, etc.
  • 数据类型也可以是自定义的消息类型和枚举类型
  • tag_n :字段编号,用于二进制消息中识别各个字段,该编号在每个消息中唯一,且不可修改。注意在将message编码成二进制消息体时字段编号1-15将会占用1个字节,16-2047将占用两个字节。所以在一些频繁使用用的message中,你应该总是先使用前面1-15字段编号。
  • 注意在将message编码成二进制消息体时字段编号1-15将会占用1个字节,16-2047将占用两个字节。所以在一些频繁使用用的message中,你应该总是先使用前面1-15字段编号。
如何使用 proto buffers 定义一个 gRPC 服务?

mysql.proto 定义了一个 SelectRecord gRPC 接口

syntax = "proto3";                     // 1

package sample;                        // 2

option go_package = "./;pb";           // 3

import "google/api/annotations.proto"; // 4

message CreateMysqlRequest {           // 5
  string sql = 1;                      // 6
}

message CreateMysqlResponse {          // 7
  string body = 1;                     // 8
}

service MysqlService {                 // 9
  rpc SelectRecord(CreateMysqlRequest) returns (CreateMysqlResponse) { // 10
    option (google.api.http) = {       // 11
      post : "/v1/mysql/select"
      body : "*"
    };
  };
}
  1. syntax = "proto3";,文件开头指定版本号;
  2. package sample;,定义本服务的包名,避免不同服务相同消息类型产生冲突;
  3. option go_package = "./;pb";,生成 go 代码对应的路径和目录;
  4. import "google/api/annotations.proto";,需要引用外部 proto 文件时使用;
  5. CreateMysqlRequest,请求消息类型定义;
  6. sql,定义消息中域的名称和类型,支持的类型可参考下面的链接,后面的数字同一个类型下面必须唯一;
  7. CreateMysqlResponse,响应消息类型;
  8. body,定义消息中域的名称和类型,支持的类型可参考下面的链接,后面的数字同一个类型下面必须唯一;
  9. MysqlService,定义服务的接口名称;
  10. SelectRecord ,远程调用方法名;
  11. option (google.api.http)gRPC网关,用于支持 http 协议。

关于 proto buffers 详细内容可以参考谷歌的教程:https://developers.google.com/protocol-buffers/docs/gotutorial

使用编译器 protoc,可以将 proto 文件编译生成客户端和服务端存根代码,要让这些代码能能够正常工作,我们还需要做以下事情。

  • 服务端

在服务端代码中添加业务逻辑代码;

type MysqlServer struct{}

// NewMysqlServer returns a new MysqlServer
func NewMysqlServer() *MysqlServer {
  return &MysqlServer{}
}

// SelectRecord selects record with the specific table
func (server *MysqlServer) SelectRecord(
  ctx context.Context, req *pb.CreateMysqlRequest,
) (*pb.CreateMysqlResponse, error) {
  sql := req.GetSql()
  // business logic
  // ...
}

创建并启动 gRPC 监听端口,并返回响应客户端请求。

func main() {
  // step 1: news a server message struct
  mySqlServer := service.NewMysqlServer()
  
  // step 2: difines a listen address
  listener, _ := net.Listen("tcp", address)
  
  // setp 3: news a grpc server
  grpcServer := grpc.NewServer()

  // setp 4: regists grpc server 
  pb.RegisterMysqlServiceServer(grpcServer, mysqlServer)
  reflection.Register(grpcServer)

  // step 5: starts grpc server
  return grpcServer.Serve(listener)
}
  • 客户端

设置连接地址和端口号,调用 gRPC 接口(客户端和服务端可能使用不同的开发语言,需要使用 protoc 编译生成对应的开发语言代码)

func main() {
  serverAddress := flag.String("address", "", "the server address")
  flag.Parse()

  cc1, err := grpc.Dial(*serverAddress)

  mysqlClient := client.NewMysqlClient(cc1)

  // business logic
  log.Printf(mysqlClient.SelectRecord("SELECT * FROM users WHERE id=1;"))
}

到这里一个完整的 gRPC 服务就完成了,那么 gRPC 有哪些优势呢?

gRPC 的优势
  • 高效的进程间通信
    不同于文本传输的 JSONXMLgRPC 使用基于二进制 proto buffers 协议进行客户端和服务端之间的通信,它是基于 HTTP2 协议标准实现的,不同进程间通信更加的快捷。

grpc服务端主动调用客户端 grpc 服务_golang_03

  • 服务接口定义更加简单,可读性好

grpc服务端主动调用客户端 grpc 服务_sql_04

  • 支持多种语言,各个系统接入成本低

grpc服务端主动调用客户端 grpc 服务_grpc服务端主动调用客户端_05

  • 支持双向流模式

grpc服务端主动调用客户端 grpc 服务_grpc服务端主动调用客户端_06

  • 支持一些常用特性功能
    如权限校验,加密通信,服务超时服务截止时间,元数据交换,压缩,负载均衡以及服务发现等等。
  • 集成到了云原生生态中
    gRPC 作为云原生基金会的一个托管项目,大多数现代框架和技术都提供了对 gRPC 的支持。
gRPC 的劣势(相信我总能给你圆回来,哈哈)
  • 对于外部服务可能不是适用
    有可能对方(本公司)系统不支持 gRPC服务,也有可能不能向对方(非本公司)暴露gRPC服务。早期 gRPC 不支持 HTTP 请求,不过现在这个问题已经解决了,可以通过 gRPC网关将 http请求转为 gRPC请求。这样服务可以支持 gRPCHTTP 两种请求。
  • 对于频繁更迭的服务接口,不是很适用
    每次修改服务接口,我们通常需要重新生成客户端和服务端代码,需要将这些代码并入现存的持续集成服务中,这可能使得整体开发周期变得复杂。但是,gRPC 对于不破坏服务约定的修改具有很好的兼容性,允许客户端和服务端使用不同版本的 proto 文件,因此代码重新生成也不是什么问题。
  • 生态相对较小
    和传统的 REST/HTTP 协议相比,gRPC 在浏览器和移动端应用的支持还处于很初级的阶段。
gRPC 支持的四种传输类型

type

request

response

scenario

unary

unary

unary

normal

client streaming

streaming

unary

continuous request

server streaming

unary

streaming

continuous response

bidirectional streaming

streaming

streaming

continuous request and response

HTTP/2 versus HTTP/1/1

grpc服务端主动调用客户端 grpc 服务_sql_07

体验一把:http://www.http2demo.io

gRPC versus REST

grpc服务端主动调用客户端 grpc 服务_rpc_08

参考资料

https://developers.google.com/protocol-buffers/docs/gotutorial

https://www.bilibili.com/video/BV1Xv411t7h5?spm_id_from=333.999.0.0