RPC框架分层设计
基本概念
RPC(Remote Procedure Call)是一种远程过程调用的协议,用于在分布式系统中实现不同计算节点之间的通信和协作。它允许一个计算节点(客户端)通过网络请求调用另一个计算节点(服务器)上的服务或方法,并获取返回结果。
在RPC中,客户端可以像调用本地方法一样调用远程服务器上的方法,而无需了解底层网络通信的细节。客户端通过发送请求消息到服务器端,并等待服务器的响应消息。服务器接收到请求后执行相应的方法,并将执行结果封装在响应消息中返回给客户端。这样,客户端就可以通过RPC来实现与远程服务器之间的交互和数据传输。
RPC协议通常定义了一组规范,包括消息的格式、编解码方式、通信协议、错误处理机制等。常见的RPC框架有 gRPC、Apache Thrift、JSON-RPC 等,它们提供了便捷的接口和工具,使得开发人员可以方便地定义远程服务接口、生成客户端和服务器端的代码,并处理底层的网络通信和数据传输细节。
通过使用RPC,不同计算节点之间可以方便地实现跨语言、跨平台的远程调用,提升分布式系统的协作能力和效率。它在分布式系统、微服务架构、大规模数据处理等场景中被广泛应用。
本地函数调用
远程函数调用
函数id
RPC概念模型
RPC的过程由5个模型组成:
User、User-Stub、RPC-Runtime、Server-Stub、Server
一次RPC的完整过程
由以下元素组成:
IDL(Interface description language)文件 IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信
生成代码 通过编译器工具把IDL文件转换成语言对应的静态库
编解码 从内存中表示到字节序列的转换称之为编码,反之为解码,也常叫做序列化和反序列化
通信协议 规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据
网络传输 通常基于成熟的网络库 走TCP/UDP传输
RPC的好处
- 单一职责,有利于分工协作和运维开发
- 可扩展性强,资源使用率高
- 故障隔离,服务的整体可靠性更高
RPC带来的问题
- 服务宕机,对方应该如何处理?
- 在调用的过程中发生网络异常,如何保证消息的可达性? ===》 RPC框架
- 请求量突增导致服务无法及时处理,有哪些应对措施?
分层设计
编解码层
生成代码
数据格式:
-
语言特定的格式
许多编程模型都内建了将内存对象编码为字节序列的支持,例如Java有java.io.Serializable
-
文本格式
JSON、XML、CSV等文本格式,具有人类可读性
-
二进制编码
具备跨语言和高性能等优点,常见有Thrift的BinaryProtocol,Protobuf等
TLV编码
选型
兼容性:支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
通用性:支持跨平台、跨语言
性能:从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时长
协议层
rpc通信协议:
协议构造:
协议解析:
graph LR;
start--Peek-->MagicNumber--Peek-->PayloadCodec--Decode-->Payload
网络通信层
网络库
-
提供易用API
封装底层Socket API
连接管理和事件分发
-
功能
协议支持:tcp、udp和uds等
优雅退出、异常处理等
-
性能
应用层buffer减少copy
高性能定时器、对象池等
关键指标
稳定性
保障策略:
- 熔断:保护调用方,防止被调用服务出现问题而影响到整个链路
- 限流:保护被调用方,防止大流量把服务压垮
- 超时控制:避免浪费资源在不可用节点上
graph TD;
超时-->降级
熔断-->降级
限流-->降级
稳定性中包括请求成功率:
通过负载均衡和重试来保证请求成功率:
针对长尾请求可以采用Backup Request的方式:
通过注册中间件的方式来保证以上策略的实施 从而保证稳定性:
易用性
开箱即用:合理的默认参数选项、丰富的文档
周边工具:生成代码工具、脚手架工具
扩展性
- Middleware
- Option
- 编解码层
- 协议层
- 网络传输层
- 代码生成工具插件扩展
观测性
- Log、Metric、Tracing
- 内置观测性服务
高性能
小结:
企业实践
Kitex
整体架构
自研网络库
netpoll
背景:
-
原生库(go net)无法感知连接状态
在使用连接池时,池中存在失效连接,影响连接池的复用
-
原生库存在goroutine暴涨的风险
一个连接一个goroutine的模式,由于连接利用率低下,存在大量goroutine占用调度开销,影响性能。
——解决无法感知连接状态的问题:
引入epoll主动监听机制,感知连接状态
——解决goroutine暴涨的风险:
建立goroutine池,复用goroutine
——提升性能:
引入Nocopy Buffer,向上层提供NoCopy的调用接口,编解码层面实现零拷贝
扩展性设计
支持多协议,也支持灵活的自定义协议扩展
性能优化
网络库优化
编解码优化
合并部署
微服务过微,传输和序列化开销越来越大
将亲和性强的服务实例尽可能调度到同一个物理机,远程RPC调用优化为本地IPC调用