概述
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
- 递增ref
- 获取current
- 检查逻辑连接是否充足,充足则返回
- 检查物理连接是否达到上限,扩容或超载返回
- 扩容物理连接,然后返回
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,
}))
}