为何微服务都开始用grpc

4,884 阅读5分钟

RPC

什么是rpc框架? image.png 简单来说服务A调用服务B,不需要显式的发起http或者tcp的请求,只需要调用本地函数,本地函数再通过http或tcp发起调用,然后数据返回。对于服务A来说,它并不关心函数内部是如何实现的网络调用,因此实现一个rpc框架需要:

  1. 因为服务多,函数多。服务A 可能会调用服务B的f1,也可能调用服务B的f2。一般一个func会对应的一个ID,对于服务B提供的func来说,每个func应该都是唯一的。ID可以数字映射,也可以是字符串。
  2. func接收的参数得传递过去,这就涉及到序列化。序列化可以把id和参数一起传递过去,可以是json,也可以xml。
  3. 序列化好的数据,通过网络传输过去,可以是http协议,也可以是tcp协议等。
  4. 服务B监听的socket,拿到数据包。
  5. 按照事先约定好的序列化方案,反序列化。
  6. 反序列化,得到执行的func的ID和params,通过ID映射找到真正执行的func
  7. 把params注入到func中,执行。
  8. 结果序列化,通过网络返回
  9. 服务A按照同样的方式解析数据,拿到结果。

RPC的好处是把远程调用封装成本地调用,隐藏了底部的通信细节,对于开发者来说,只关注业务函数的输入和输出,不需要关心数据如何传输过去的。

GRPC

gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。grpc只不过是一种特殊的rpc协议框架,相比常规的rpc,grpc拥有更好性能。

  • http2.0
  • protobuf http2.0的好处在于二进制分帧、多路复用、头部压缩、服务端push。这里不在赘述,细节参考http2.0为什么这么快 数据包在网络中传输来传输去的成本,关系到带宽的成本。grpc序列化方式采用protobuf,protobuf比json和xml要好很多,缺点就是没有json好阅读。 在有效的带宽中,同样的数据包,protobuf可以传输更多次。且数据越小,序列化越快。protobuf是跨平台的,和语言无关。可以用proto工具生成各个语言的代码,非常方便。
  • json是文本文件,文本文件是基于字符的。
  • protobuf是二进制编码,基于值的。 这两者有什么区别,举个例子:对于123这样的数字,json编码后占3个字节,而对于二进制编码因为123可以用int8类型来表示,所以占用一个字节。 在整数和浮点数编码方面,大多数情况下json占用的空间是大于protobuf的(1,2这样的大家都是1个字节)。
    protobuf序列化的message大致数据结构如下:

image.png 我们的传参可能有filed1、field2...这些field都是类似k、v这种紧凑在一起,差别就是有的需要length,相同类型的repeated可能多个value连在一起,然后组成整个message数据流。
然后对于k也就是tag部分,它的数据结构可能是这样的:

image.png

  • number就是我们定义proto时,每个字段的number
  • wire_type始终占用低3位,一共可以表示8个数字,每个数字代表不同的类型 image.png 看个例子:

image.png
int64的1,protobuf是如何压榨空间的:

  1. 首先低3位 000表示0,0的话用varint编码
  2. 编码后的结果是只需要8位-1字节 int64按常理64位占用8字节,这里却只要1个字节。

varint

编码过程:

  1. 获取对应的补码
  2. 从补码低位开始,每取7位,依次从高往低拼接
  3. 拼接过程,如果当前的位置后面还要填充。则当前最高位补1,否则补0。
    以int32的251为例:

image.png 正数的补码就是他本身,取低7位,假设叫段A image.png
因为前面还有1,所以接着取7位,假设叫段B,开始拼接,段A在段B前面,段A后面还有段B,所以段A的最高位补1,段B的最高位补0。所以最终为:

image.png varint编码结束,原先需要4字节的存储,现在只需要2字节。每个字节的最高位不表示本身了,而是标识它的后面还有没有数据。
解码的过程也是相反的: image.png varint编码的缺点也有,比如对于int32的数据来说,每个字节都要消耗一位msb,所以最多只能表示2^28个数 对于在2^28~2^32之间的数字,还要额外的一个字节。但是往往越大的数,出现的概率越小。

zigzag

varint对于正数可以有较大的空间优化,但是负数却适得其反,以int32的-1为列:
image.png 负数的最高位始终是1,这将表示负数将占满整个字节。如果采用varint编码:
先取补码(符号位不变,其余各位取反,最后加1): image.png 然后每取7位,加上msb:
image.png 最后发现一个简简单单的-1,通过varint编码还多出一个字节。

说明一下,protobuf因为支持很多语言,有的语言没有int8,int16的概念,所以protobuf没有int8和int16 zigzag的思想是映射,把负数转成正数,然后正数再通过varint编码,来减少空间。
先来看个1+(-1) = -2的问题:

image.png 1的原码 和 -1的原码相加竟然等于-2,这很明显违反常理,于是设计出了补码,1的补码和-1的补码相加等于0,计算机通过补码的方式来运算的。
来看个-1(int32)编码的过程:

  1. int32的 -1的补码 1111xxxx1111
  2. 左移一位 1111xxxx1110
  3. 右移31位 1111xxxx1111
  4. 异或运算 0000xxxx 0001

image.png 通过这种方法可以看出-1被转成了1,然后通过varint压缩,去掉高位的0,最终4字节的-1被转成了1字节的1。
zigzag核心编解码算法:

编码:(n << 1) ^ (n >> 31)
解码:(((unsignedint)n) >>1) ^ -(n & 1)