关于golang写grpc 服务记录

总结一下用grpc协议写服务的若干心得,并记录一些有用链接。

  1. 下载grpc依赖及安装grpc代码生成工具, 也可参见该链接创建简单可行的helloworld服务。

  2. golang 开发grpc服务用"google.golang.org/grpc", 参考此例子, 及阅读官方的tutorial可以应付大部分开发情况。

  3. grpc服务端及客户端均需要保留protoc生成的.pb.go文件作为交互依据。

  4. .proto文件指定了grpc服务端和客户端交互的序列化规定,即指定了客户端如何将请求内容序列化成字节, 服务端收到请求字节后也根据.proto文件的内容反序列化成对象。 理解上跟在go结构体属性的json:"user_name"类似,只是proto的序列化和反序列化效率更高。

  5. 使用protoc生成的.pb.go文件也会生成json",,,"db:",,,"等tag, 方便开发者在restful服务和grpc服务自由转换和选择, protoc本身不支持指定json:... tag的命名, 只能通过辅助插件在生成.pb.go时或事后修改.pb.go的tag属性。

  6. 若在原restful web服务中开发grpc服务, 可将原指定数据交互的数据结构文件如model.go/types.go.pb.go合为一份。但这也会引起一系列问题。如生产的pb.go中的结构体不支持embedded, 以及不能指定生成的pb.go中的结构体去使用本身以经用golang定义好的结构体。

  7. Restful web服务开发和grpc服务交互也会引起很多序列化麻烦,尽量在开发前设计好序列化流程, 可将json,protobuf, db等三者的序列化由同一个model定义, 省去互相转换的麻烦。

type User struct{
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" db:"name"`
}
  1. protoc 有protoc 2 和protoc 3, 也有不少类型的支持, 可通过导入类型依赖包来使用更多的数据类型。

  2. grpc可以实现连接池。在实践中发现,单个*grpc.ClientConn支持多线程并发使用,瓶颈不在于*grpc.ClientConn数量,参见及此文及其issue。grpc连接池的建立会占用较多资源, 即每个*grpc.ClientConn均有若干goroutine作为watch协程, 一旦连接池的量级较大时, 占用的资源也可观的。

  3. grpc瞬时流量大会引起 grpc Resource Exhausted , 可通过配置grpc服务端和客户端来解决, 参见

  4. grpc不能用于前端浏览器与后端直接交互, 其grpc 的典型应用场景如下:
    关于golang写grpc 服务记录_第1张图片

  5. 使用场景如Kafka接入使用,往往只需使用grpc的序列化结构体, 而不使用grpc的方法,以优化序列化和反序列化的性能。

proto.Unmarshal(......)
  1. 进行grpc 方法调用需要注意内存泄露, 这里引用源码上的注释作为解释:
// NewStream creates a new Stream for the client side. This is typically
// called by generated code. ctx is used for the lifetime of the stream.
//
// To ensure resources are not leaked due to the stream returned, one of the following
// actions must be performed:
//
//      1. Call Close on the ClientConn.
//      2. Cancel the context provided.
//      3. Call RecvMsg until a non-nil error is returned. A protobuf-generated
//         client-streaming RPC, for instance, might use the helper function
//         CloseAndRecv (note that CloseSend does not Recv, therefore is not
//         guaranteed to release all resources).
//      4. Receive a non-nil, non-io.EOF error from Header or SendMsg.
//
// If none of the above happen, a goroutine and a context will be leaked, and grpc
// will not call the optionally-configured stats handler with a stats.End message.
func (cc *ClientConn) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) {...}

对于http handler调用grpc方法的情况, 为防止内存泄露,可以为各handler增加利用context控制超时的中间件

  1. 几个grpc的典型应用举例
  • kubelet调用CRI关于golang写grpc 服务记录_第2张图片
    kubelet接收到apiserver发送过来的创建容器的指令时, kubelet会创建grpc连接CRI shimgrpc server, 最终grpc server发送容器创建请求到本地的container runtimedocker daemon)。 流程详见此. 另dockershim grpc server 源码详见.

14 gRPC stream 允许·grpc·方法调用的时候建立长连接, 允许客户端和服务端建立双向连接,具体详见。后台服务通过stream连接可实现更丰富的交互形式。如服务A基于etcd建立元数据服务, 服务B可通过stream 达到订阅watch特定元数据的效果, 确保获取最新的元数据改动。

15 后台开发中涉及到较多的.proto文件时, 可以建立独立项目将.proto文件统一保存, 通过git管理。项目中也可以将.proto文件构建成对应编程语言的文件, 如foo.pb.go, 让有需要的项目直接通过import来导入。对于没有上传到github的项目, 可以通过replace方式来实现本地import, 详见。

16 后台服务中, 若涉及到多个功能模块的grpc方法, 则建议每个功能模块设置独立的grpc server ,如kebelet的grpc server源码, 若服务中有多个grpc server 则需要监听不同的tcp port,如kebelet实际上运行着多个grpc server。

17. 实践中发现,数据结构越是复杂的情况, 越不适合将proto生成的xxx.pb.go中的结构体作为通用的model来提供给restfulgrpc接口使用,这是由于生成的xxx.pb.go有若干点局限:

  • 不支持embeded结构, 由此生成的struct本身难以沿用embeded属性的特性, issue#192。
  • 生成的struct若要实现某interface,需要在xxx.pb.gopackage下另起文件实现。
  • 生成的structslice定义只能为[]*object

在大部分情况下,restfulgrpc接口对model部分的关注点会有差异, 因此实现restful modelgrpc model的转换会效率更高, 例子详见

  1. grpcrpc的原理类似(区别见此), 大致为开发者定义protobuf的数据结构和方法接口, 编写方法实现并实现该接口。服务启动时rpc server端将protobuf指定的接口注册到内存(map结构), 然后监听指定的socketclient端调用rpc方法时,序列化调用参数, 向server端指定端口发起tcp请求及发送参数数据, server端查询内存找到client端请求的方法对应的实现,运行该方法并反序列化,返回结果。 另外rpc基于tcp/udpgrpc基于http

你可能感兴趣的:(Golang)