protobuf和grpc开发必知必会

78 阅读2分钟

概述

protobuf和grpc是开发微服务常用的协议和工具,但是怎么用好grpc,以及probuf为什么高效,可以主动学习下。

details

protobuf/grpc 入门

protobuf/grpc的优点

  • “协议缓冲”其名,对于添加字段、减少字段这类协议修改友好,只要保证tagNum相同即可
  • 高效的二进制编码
  • 跨语言,远程调用的优势
  • 通过gateway一键支持HTTP接口
  • 充分利用HTTP2协议的多路复用,因此注意grpc的conn对象是可复用的

typeid=tagNum+tagType

tagType(3bit定长):

  • 0: int32、int64、uint32、uint64、sint32、sint64、bool、enum
  • 1: fixed64、sfixed64、double
  • 2: string、bytes、结构体类型、数组类型、map类型
  • 5: fixed32、sfixed32、float

tagNum: varint编码

编码

  • varint编码: int32、int64、uint32、uint64、enum(int32)
  • zigzag+varint: sint32、sint64
  • 定长编码: fixed32、fixed64、sfixed32、sfixed64
  • IEEE754: float、double
  • 字符串: 使用字节流本身
  • 数组(定长): typeid + length + data
  • 数组(变长): {tagid+length+data}
  • map: 转换为KV的数组进行编码
  • 空值: 不编码

grpc pool

https://github.com/shimingyah/pool 仅用700行代码就实现了客户端的连接池,基本无锁,这2点保证了直接使用它比你自己写一个可靠。

facade

type Pool interface {
    Get() (Conn, error)
    Close() error
    Status() string
}

type Conn interface {
    Value() *grpc.ClientConn
    Close() error
}

原理

  • 连接模型
    • 物理连接:conns []*conn,真实的grpcCient
    • 逻辑连接:current * opt.MaxConcurrentStreams
  • 状态量
    • index: 负载均衡index,使用物理连接轮询模式
    • current: 物理连接的数量
    • ref: 当前已分配的逻辑连接
    • closed:状态值
    • opt.Reuse: 当物理连接已创建到limit,是否创建出一次物理性连接返回
  • 一些细节设计
    • 初始化Pool时,直接初始化固定数组len=option.MaxActive,然后创建出opt.MaxIdle的物理连接
    • 物理连接池扩容使用的是指数扩容,直接将容量翻倍
    • 当Pool全部回收处于空闲,触发缩容检查,缩容到opt.MaxIdle
    • conn.Close必须由用户调用,否则ref不会减1,造成连接泄露
    • 从上面和源码可以看出,MaxConcurrentStreams并不是一个硬限制,而是一个软限制,实际物理连接的并发请求可能大于它

核心方法 Get

  1. 递增ref
  2. 获取current
  3. 检查逻辑连接是否充足,充足则返回
  4. 检查物理连接是否达到上限,扩容或超载返回
  5. 扩容物理连接,然后返回

grpc server/client的配置

s := grpc.NewServer(
    grpc.InitialWindowSize(pool.InitialWindowSize),
    grpc.InitialConnWindowSize(pool.InitialConnWindowSize),
    grpc.MaxSendMsgSize(pool.MaxSendMsgSize),
    grpc.MaxRecvMsgSize(pool.MaxRecvMsgSize),
    grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
        PermitWithoutStream: true,
    }),
    grpc.KeepaliveParams(keepalive.ServerParameters{
        Time:    pool.KeepAliveTime,
        Timeout: pool.KeepAliveTimeout,
    }),
)

func Dial(address string) (*grpc.ClientConn, error) {
    ctx, cancel := context.WithTimeout(context.Background(), DialTimeout)
    defer cancel()
    return grpc.DialContext(ctx, address, grpc.WithInsecure(),
        grpc.WithBackoffMaxDelay(BackoffMaxDelay),
        grpc.WithInitialWindowSize(InitialWindowSize),
        grpc.WithInitialConnWindowSize(InitialConnWindowSize),
        grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(MaxSendMsgSize)),
        grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxRecvMsgSize)),
        grpc.WithKeepaliveParams(keepalive.ClientParameters{
            Time:                KeepAliveTime,
            Timeout:             KeepAliveTimeout,
            PermitWithoutStream: true,
        }))
}

ref

Protobuf编码原理及优化技巧探讨