一.分层设计
Thrift 是一歀基于 CS 架构的 RPC 框架,最初由 Facebook 研发,2008 年转入 Apache 组织。开发人员可以使用 Thrift 提供的 IDL来定义数据结构、异常和接口。IDL 的功能类似 Protobuf 的 message,但 message 只能用于定义数据结构,因此 IDL 比 message 功能更加强大。这里以Thrift为例,无论是客户端还是服务端,Code层主要是用户自己编写的业务逻辑代码。Service.Processor层和读写层主要通过代码生成工具把 IDL 文件转换成不同语言对应的 lib 代码,里面封装了编解码逻辑。TProtocal是框架的编解码层,TTransport是框架传输协议层,Network IO用于框架网络通信。
二.编解码层
Service.Processor层、读写层、TProtocal层是框架编解码层,其生成代码主要是客户端和服务端通过依赖同一份IDL文件来生成不同语言的CodeGen。
编解码层的数据格式有语言特定的格式、文本格式包括JSON、XML、CSV等、二进制编码,能够跨语言和高性能、TLV 编码由Tag标签和 Lenght长度和Value值组成,编码结构清晰,扩展性较好,但是由于增加了Type和Length两个冗余字段,有额外的内存开销,特别是在大部分字段都是基本类型的情况下有不小的空间浪费。
编解码层的选型从兼容性来看要考虑能否支持自动增加新的字段也不影响老的服务。从通用性来看要考虑两个层面:一是技术层面,是否支持跨平台和跨语言,二是流行层面,序列化和反序列化需要多方参与,若很少人使用或流行独较低,可能增加学习成本,缺乏稳定的跨语言跨平台的公共包。从性能来看要考虑空间和时间的开销,复杂序列化协议会耗费更多解析时间,对于分布式存储系统,巨大的空间开销也会增大成本。
三.协议层
协议是双方确定的交流语义,以如:我们没计一个字符串传输的协议,它允许客户端发送一个字符串,服务端接收到相应的字符串。这个协议首先发送一个4字节消息总长度、然后再发送1字节的字符集,接下来就是消息的payload,字符集名称和字符串正文。
下面是协议的组成部分
LENGTH:数据包大小,不包含自身 HEADER MAGIC:标识版本信息,协议解析时候快速校验 SEQUENCE NUMBER: 表示数据包的 seqID可用于多路复用,单连接内递增 HEADER SIZE: 头部长度,从第14个字节开始计算一直到 PAYLOAD前 PROTOCOL ID编解码方式,有 Binary 和Compact 两种 TRANSFORM ID: 压缩方式,如 zlib 和snappy
INFO ID: 传递一些定制的 meta 信息 PAYLOAD: 消息体
四.网络通信层
网络通信中最常见的是socket API,socket函数创建套接字,bind将一个套接字绑定到一地址上。listen监听进来的连接,bscklog含义有点复杂,指定挂起的连接队列的长度,当客户端连接的时候,服务器可能正在处理其他逻辑而未调用accept接受连接,此时会导这个连接被挂起,内核维护挂起的连接队列,backlog则指定这个队列长度,accept函数从队列中取出连接请求并接收它,然后这个连接就从挂起队列移除。如果队列未满,客户端调用connect马上成功,如果满了可能会阻塞等待队列未满。
网络库中提供易用 API,用于封装底层 Socket API连接管理和事件分发;功能方面,支持tcp、udp 和 uds 等协议、优雅退出、异常处理等;性能方面,应用层 buffer 减少 copy,高性能定时器、对象池等。
五.小结
RPC框架主要核心有三层:编解码层、协议层和网络通信层 。二进制编解码的实现原理和选型要点。协议的一般构造,以及框架协议解析的基本流程,Socket API 的调用流程,以及选型网络库时要考察的核心指标。