这是我参与「第五届青训营」伴学笔记创作活动的第 14 天
基本概念
-
相比本地函数调用,RPC调用需要解决的问题
- 函数映射
- 数据转换成字节流
- 网络传输
-
一次 RPC 的完整过程:
- 序列化/反序列化
- 协议编码/解码
- 网络模块通信
-
RPC 带来的问题将由 RPC 框架来解决
-
服务宕机如何感知?
- Healthcheck
-
遇到网络异常应该如何应对?
- 统一 Error-handling
-
请求量暴增怎么处理?
- 动态扩容
-
RPC 框架分层设计
编解码层
-
数据格式
- 语言特定格式:例如 java.io.Serializable
- 文本格式:例如 JSON、XML、CSV 等
- 二进制编码:常见有 Thrift 的 BinaryProtocol,Protobuf,实现可以有多种形式,例如 TLV 编码 和 Varint 编码
-
选型考察点
-
兼容性
-
通用型
-
性能
- 空间开销
- 时间开销
-
- 生成代码和编解码层相互依赖,框架的编解码应当具备扩展任意编解码协议的能力
协议层
- 以 Thrift 的 THeader 协议为例
LENGTH字段是32位,计算数据包中剩余的字节数,不包括长度字段。HEADER SIZE字段是16位,定义了除HEADER MAGIC、FLAGS、SEQUENCE NUMBER和HEADER SIZE字段外头部剩余部分的大小。头部大小字段以字节/4表示。
变换ID是变量。每个变换的数据由代码中的变换ID定义,头部没有给出大小。如果客户端指定了变换ID,服务器不知道该变换ID,则必须返回错误,因为我们不知道如何转换数据。
相反,info头部中的数据是可忽略的。这只应该是时间戳、调试跟踪等。使用头部大小,如果不知道info ID,您应该能够跳过此数据并安全读取有效负载。
Info ID应该按从旧到新的顺序支持,以便如果我们读取不支持的info ID,则其余的info ID也不会被支持,我们可以安全地跳到有效负载。
Info ID和变换ID应该共享相同的ID空间。
网络通信层
- 阻塞 IO 下,耗费一个线程去阻塞在 read(fd) 去等待用足够多的数据可读并返回。
- 非阻塞 IO 下,不停对所有 fds 轮询 read(fd) ,如果读取到 n <= 0 则下一个循环继续轮询。
第一种方式浪费线程(会占用内存和上下文切换开销),第二种方式浪费 CPU 做大量无效工作。而基于 IO 多路复用系统调用实现的 Poll 的意义在于将可读/可写状态通知和实际文件操作分开,并支持多个文件描述符通过一个系统调用监听以提升性能。 网络库的核心功能就是去同时监听大量的文件描述符的状态变化(通过操作系统调用),并对于不同状态变更,高效,安全地进行对应的文件操作。