深入浅出 RPC 框架|青训营笔记
深入浅出 RPC 框架|青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的的第十二篇笔记。
本章目录:
基本概念
本地函数调用
远程函数调用(RPC - Remote Procedure Calls)
因为不是一个栈,因此每个函数需要搞一个ID,把对应的函数的ID传过去。
把对应的数据转成字节流传过去。
RPC 概念模型
RPC整体流程如图。。。
一次RPC的完整过程
比本地函数调用,远程调用的话我们不知道对方有哪些方法,以及参数长什么样,所以需要有一种方式来描述或者声明我有哪些方法,方法的参数都是什么样子的,这样的话大家就能接照这个来调用,这个描校件就是IDL文件。
刚才我们提到服务双方是通过约定的规范进行远程调用,双方都依赖同一份IDL文件,需要通过工具来生成对应的生成文件,具体调用的时候用户代码需要依赖生成代码,所以可以把用户代码和生成代码看做一个整体。
编码只是解决了跨语言的数据交换格式,但是如何通讯呢?需要制定通讯协议,以及数据如何传输?我的网络模型如何呢?那就是这里的transfer要做的事情。
RPC的好处
RPC带来的问题
小结
分层设计
以Apache Thrifit为例
编解码层
生成代码
数据格式
二进制编码
TLV编码结构简单清晰,并且扩展性较好,但是由于增加了Type和Length两个冗余信息,有额外的内存开销,特别是在大部分字段都是基本类型的情况下有不小的空间浪费。
选型
协议层
概念
协议构造
协议解析
先读取MAGIC,知道你是啥类型的协议,然后读取编解码方式,把Payload有效载荷解析出来。
网络通信层
Socktes API(重要)
这里和之前讲得差不多,客户端必须知道服务器的IP和端口号。
服务器端:socket函数创建一个套接字,bind将一个套接字绑定在一个地址上。listen监听进来的链接。
backlog:指挂起的连接队列的长度,当客户端连接的时候,服务器可能正在处理其他逻辑而未调用accept接受链接,此时会导致这个连接被挂起,内核维护挂起的链接队列,backlog则指定这个队列的长度。
accept函数从队列中取出连接请求并接受它,然后这个连接就从挂起队列中移除。如果队列未满,客户端调用connect马上成功,如果满了可能会阻塞等待队列未满(实际上在Linux中测试并不是这样的结果,这个后来再专门研究)。Linux的backlog默认是128,通常情况下,我们也指定为128即可。
connect 客户端向服务器发起连接,accept接受一个连接请求,如果没有连接则会一直阻塞到有连接进来。得到客户端的fd后,就可以调用read,write函数和客户端通讯,读写方式和其他I/O类似。
read从fd读数据,socket默认是阻塞模式的,如果对方没有写数据,read会一直阻塞,write同理。
socket 关闭套接字,当另一端socket关闭后,这一端读写的情况:
尝试去读会得到一个EOF(End Of File),并返回0.
尝试去写会出发一个SIGPIPE信号,并返回-1和errno=EPIPE,SIGPIPE的默认行为是终止程序,所以我们通常应该忽略这个信号,避免程序终止。
如果这一端不去读写,我们可能没有办法知道对端的socket关闭了。
网络库
小结
关键指标
稳定性
保障策略
某种程度上讲:熔断、限流、超时都是降级的措施
请求成功率
长尾请求
左侧是正常的请求:会花费t1+t2的时间。右侧是我们使用的p99时间内,我们认为t3时间内99%的请求都应当被返回了,然后当超过p99这个时间认为出了问题直接再发一次请求,所以t4为总花费时间。
长尾请求就是指的最后那1%的请求,这些请求明显高于平均时间。
注册中间件
Kitex Client和Server的创建接口均采用Option模式,提供了极大的灵活性,很方便就能注入这些稳定性策略。
易用性
Kitex使用Suite来打包自定义的功能,提供一键配置基础依赖的体验。
扩展性
主要通过中间件middleware来实现。
观测性
Log:日志。Metric:监控面板,可以看服务的kps(网速)是多少,延迟是多少。Tracing:链式跟踪,排查问题的时候看层层请求,看哪个地方出了问题。
高性能
高性能往往分成两个维度:高吞吐(单位时间内尽可能处理多的请求)和低延迟(一次请求发过去,返回时间尽可能短,更重要一点)。