这是我参与「第五届青训营」伴学笔记创作活动的第 14 天
RPC框架
基本概念
- RPC:Remote Procedure Calls
- RPC需要解决的问题
- 函数映射
- 在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的,所以函数都有自己的一个ID,在做 RPC的时候要附上这个 ID,还得有个 ID 和函数的对照关系表,通过 ID找到对应的函数并执行。
- 数据转换成字节流
- 在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。
- 网络传输
- 函数映射
概念模型
一次完整的过程
好处
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率更优
- 故障隔离,服务的整体可靠性更高
分层设计
以Apache Thrift为例
编解码层-生成代码
数据格式
-
语言特定的格式
- 许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable
- 和这门语言绑死在一起了。安全和兼容性也是问题
- 许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable
-
文本格式
- JSON、XML、CSV等文本格式,具有人类可读性
- 由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以在性能比较差
- JSON、XML、CSV等文本格式,具有人类可读性
-
二进制编码
- 具备跨语言和高性能等优点,常见有Thrift的BinaryProtocol,Protobuf 等
二进制编码
- TLV编码
- Tag:标签,可以理解为类型
- Length:长度
- Value:值,可以是TLV结构
选型
协议层
协议构造
解析
Sockets API
- backlog的含义有点复杂,这里先简单的描述:指定挂起的连接队列的长度,当客户端连接的时候,服务器可能正在处理其他逻辑而未调用accept接受连接,此时会导致这个连接被挂起,内核维护挂起的连接队列,backlog则指定这个队列的长度,accept函数从队列中取出连接请求并接收它,然后这个连接就从挂起队列移除。如果队列未满,客户端调用connect马上成功,如果满了可能会阻塞等待队列未满(实际上在Linux中测试并不是这样的结果,这个后面再专门来研究)
网络库
- 提供易用API
- 封装底层Socket API
- 连接管理和事件分发
- 功能
- 协议支持: tcp、udp 和uds等
- 优雅退出、异常处理等
- 性能
- 应用层buffer 减少copy
- 高性能定时器、对象池等
关键指标
稳定性
- 熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路
- 一个服务 A 调用服务 B 时,服务 B 的业务逻辑又调用了服务 C,而这时服务 C 响应超时了,由于服务 B 依赖服务 C,C 超时直接导致 B 的业务逻辑一直等待,而这个时候服务 A 继续频繁地调用服务 B,服务 B 就可能会因为堆积大量的请求而导致服务宕机,由此就导致了服务雪崩的问题
- 限流:保护被调用方.防止大流量把服务击垮
- 超时控制:避免浪费资源在不可用节点上
请求成功率-重试
- 避免重试风暴
长尾请求
- 我们预先设定一个阈值 t3(比超时时间小,通常建议是 RPC 请求延时的 pct99 ),当 Req1 发出去后超过 t3 时间都没有返回,那我们直接发起重试请求 Req2 ,这样相当于同时有两个请求运行。然后等待请求返回,只要 Resp1 或者 Resp2 任意一个返回成功的结果,就可以立即结束这次请求,这样整体的耗时就是 t4 ,它表示从第一个请求发出到第一个成功结果返回之间的时间,相比于等待超时后再发出请求,这种机制能大大减少整体延时。
注册中间件
易用性
- 开箱即用
- 合理的默认参数选项、丰富的文档
- 周边工具
- 生成代码工具、脚手架工具
扩展性
一次请求发起首先会经过治理层面,治理相关的逻辑被封装在middleware中,这些middleware会被构造成一个有序调用链逐个执行,比如服务发现、路由、负载均衡、超时控制等,mw执行后就会进入到remote 模块,完成与远端的通信
观测行
高性能
企业实践
Kitex
- 扩展性设计
- 网络库优化
- 编解码优化
- 合并部署