RPC框架分层设计

100 阅读7分钟

1.基本概念 1.1本地函数调用 1.将a和b的值压栈 2.通过函数指针找到calculate函数,进入函数取出栈中的值2和3,将其赋予x和y 3.计算x*y,并将结果存在z 4.将z的值压栈,然后从calculate返回 5.从栈中取出z返回值,并赋值给result

1.jpg 1.2远程函数调用 RPC需要解决的问题 1.函数映射 2.数据转换成字节流 3.网络传输

2.jpg 1.3一次RPC的完整过程 IDL文件——IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信。 生成代码——通过编译器工具把IDL文件转换成语言对应的静态库。 编解码——从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化。 通信协议——规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据。 网络传输——通常基于成熟的网络库走TCP/UDP传输。

屏幕截图 2023-06-10 210023.jpg 1.4RPC的好处 1.单一职责,有利于分工协作和运维开发 2.可扩展性强,资源使用率更优 3.故障隔离,服务的整理可靠性更高 1.5RPC带来的问题 1.服务宕机 2.在调用过程中发生网络异常 3.请求量突增导致服务无法及时处理 以上问题可以通过RPC框架去处理 2.分层设计 2.1分层设计

4.jpg 2.2编解码层

5.jpg 2.3编解码层——生成代码

6.jpg 2.4编解码层——数据格式 语言特定的格式——许多编程语言都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable 文本格式——JSON、XML、CSV等文本格式,具有人类可读性 二进制编码——具备跨语言和高性能等优点,常见有Thrift的BinaryProtocol,Protobuf等 2.5编解码层——二进制编码 TLV编码 ——Tag:标签,可以理解为类型 ——Length:长度 ——Value:值,Value也可以是个TLV结构

8.jpg

7.jpg 2.6编解码层——选型 兼容性——支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活性 通用性——支持跨平台、跨语言 性能——从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时长 2.7协议层

9.jpg 2.8协议层——概念 特殊字符串——一个特殊字符作为每个协议单元结束的标示

10.jpg 变长协议——以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度

11.jpg 2.9协议层——协议构造 LENGTH——数据包大小,不包含自身 HEADER MAGIC——表示数据包的seqID,可用于多路复用,单连接内递增 HEADER SIZE——头部长度,从第14个字节开始计算一直到PAYLOAD前 PROTOCOL ID——编解码方式,有Binary和Compact两种 TRANSFORM ID——压缩方式,如zlib和snappy INFO ID——传递一些定制的meta信息 PAYLOAD——消息体 2.10协议层——协议解析

12.jpg 2.11网络通信层

13.jpg 2.12网络通信层——Sockets API

14.jpg 2.13网络通信层——网络库 提供易用API 封装底层Soxket API 连接管理和事件分发 功能 协议支持:tcp、udp和uds等 优雅退出、异常处理等 性能 应用层buffer减少copy 高性能定时器、对象池等 RCP本质上就是一个远程调用,那肯定需要通过网络来传输数据。虽然传输协议可以有多种选择,但考虑到可靠性的话,我们一般默认采用TCP协议。为了屏蔽网络传输的复杂性,我们需要封装一个单独的数据传输模块来收发二进制数据,这个单独模块可以叫做传输模块 用户请求的时候是基于方法调用,方法出入参数都是对象数据,对象是肯定没法直接在网络中传输的,我们需要提前把它转成可传输的二进制,也就是需要序列化。 但是只把方法调用参数的二进制数据传输到服务提供方是不够的,我们需要在方法调用参数的二进制数据后面增加“断句”符号来分隔出不同的请求,在两个“断句”符号中间放的内容就是我们请求的二进制数据,这个过程叫做协议封装。 虽然上面是两个不同的过程,但其目的都是一样,都是为了保证数据在网络中可以正确传输。这里所说的“正确”,不仅是指数据能够传输,还需要保证传输后能正确还原出传输前的语义。所以我们可以把这两个处理过程放在架构中的同一个模块,统称为协议模块。 除此之外,我们还可以在协议模块中加入压缩功能,这是因为压缩过程也是对传输的二进制数据进行操作。 在实际的网络传输过程中,我们的请求数据包在数据链路层可能会因为太大而被拆分成多个数据包进行传输,为了减少被拆分的次数,从而导致整个传输过程时间太长的问题,我们可以在RPC调用的时候这样操作: 在方法调用参数或者返回的二进制数据大于某个阈值的情况下,我们可以通过压缩框架进行无损压缩,然后在另外一端也用同样的压缩算法进行压缩,保证数据可还原 传输和协议这两个模块是RPC里面最基本的功能,它们使对象可以正确的传输到服务提供方。但距离RPC的目标----实现像调用本地一样调用远程,还缺少点东西。因为这两个模块所提供的都是一些基础能力,要让这两个模块同时工作的话,我们需要手写一些粘合的代码,但这些代码对我们使用RPC的研发人员来说是没有意义的,而且属于一个重复的工作,会导致使用过程的体验非常不友好。 这就需要我们在RPC里面把这些细节对研发人员进行屏蔽,让它们感觉不到本地调用和远程调用的区别。这是 RPC 调用的入口,我们一般叫做Bootstrap 模块。 到这里,一个点对点(Point to Point)版本的RPC框架就完成了。此时,这个RPC只有单机能力,没有集群能力。所谓集群能力,就是针对同一个接口有着多个服务提供者,但这多个服务提供者对于我们的调用方来说是透明的,所以在RPC里面我们还需要给调用方找到所有的服务提供方,并需要在RPC里面维护好接口跟服务提供者地址的方式,这样调用方在发起请求的时候才能快速找到对应的接口地址,这就是我们常说的“服务发现”。 但服务发现只是解决了接口和服务提供方地址值映射关系的查找问题,这更多的是一种“静态数据”。说它是静态数据时因为,对于我们的RPC来说,我们每次发送请求的时候都是需要用TCP连接的,相对于服务方IP地址,TCP连接状态是瞬息万变的,所以我们的RPC框架里面要有连接管理器去维护TCP连接的状态 有了集群之后,提供方可能就需要管理好这些服务了,那我们的RPC就需要内置一些服务治理的功能,比如服务提供方权重的设置、调用授权能一些常规治理手段。而服务调用方需要额外做哪些事情呢?每次调用前,我们都需要根据提供方设置的规则,从集群中选择可用的连接用于发送请求。