RPC是什么?
RPC(Remote Procedure Call)远程过程调用,是一种计算机通信协议。RPC允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
RPC的工作流程如下:
- 客户端程序通过客户端stub程序打包请求参数,然后发送调用请求到服务端。
- 服务端收到请求后,通过服务器stub程序将参数取出。
- 服务端执行远程程序,得到结果后再打包返回给客户端。
- 客户端接收到返回数据后,客户端stub程序将结果取出。
- 程序员面对的只是简单的函数调用,RPCStub程序帮助完成了参数打包、传输、以及结果解包等工作。
不同RPC框架之间主要区别在通信协议、传输方式、序列化方式、跨语言支持等方面。但基本原理是相似的,都是要实现透明的远程过程调用。
RPC需要解决的问题
- 1,函数映射
- 2,数据转换成字节流
- 3,网络传输
一次RPC完整的过程
- IDL (Interface description language) 文件
IDL 通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信。
- 生成代码
通过编译器工具把 IDL 文件转换成语言对应的静态库。
- 编解码
从内存中表示到字节序列的转换称为编码,反之为解码,也常叫做序列化和反序列化。
- 通信协议
规范了数据在网络中的传输内容和格式。除必须的请求/响应数据外,通常还会包含额外的元数据。
- 网络传输
通常基于成熟的网络库走 TCP/UDP 传输
RPC的概念模型
User,User-Stub,RPC-Runtime,Server-Stub,Server
相比于网络传输,RPC具有的优势
- 隐藏了底层通信细节。程序员不需要关系网络通信的细节,只需要按照本地函数调用的方式进行编程。RPC框架帮助完成底层的网络通信。
- 优化了通信模型。RPC利用订阅发布模式,只需要服务端和客户端建立一次连接,之后可以进行多次服务调用。相比于HTTP请求响应模式,RPC更适合服务端和客户端之间频繁通信的场景。
- 提供高性能的数据交互。RPC框架底层通常采用二进制序列化,相比XML和JSON序列化,可以大幅提供通信效率。并使用多线程、异步通信模式进一步优化性能。
- 提供负载均衡和故障转移机制。大多RPC框架都内置了服务注册与发现机制,可以很方便实现服务端的负载均衡和高可用部署。
- 支持跨语言调用。主流的RPC框架大都提供了多语言绑定,这样客户端和服务端可以使用不同的语言实现,极大提高了系统的灵活性。
- 提供IDL接口定义。RPC框架通常都有一个IDL接口定义语言,可以事先定义好服务接口契约,方便服务提供方和消费方进行统一的接口调用。
- 简化系统架构。使用RPC框架可以整体上减少系统复杂性,使得分布式系统开发更加简单高效。
RPC框架适合什么样的网络通信场景?
- 需要频繁服务调用的场景。比如微服务架构中的服务间通信。
- 需要线性扩展能力的场景。通过RPC的服务注册发现机制,可以轻松实现服务端的动态扩容。
- 对实时性要求较高的场景。RPC的通信效率优于HTTP,更适合实时响应。
- 需要服务治理能力的场景。RPC内置了服务监控、容错、负载均衡等服务治理机制。
- 跨语言交互的场景。RPC框架提供语言无关的IDL接口定义。
相对而言,RPC框架不太适合:
- 需求简单的网络通信场景。比如简单的请求响应,使用RPC会增加复杂性。
- 通信效率要求不高的场景。RPC的语言绑定和数据序列化会增加一定开销。
- 一次性通信场景。RPC更适合持续通信场景,不适合一次连接一次调用的通信。
- 并发请求量特别大的场景。RPC需要维持长连接,大量并发会使框架变慢。
- 对时间敏感的实时通信场景。RPC相比专门的实时通信中间件效率较低。
RPC框架的分层设计
编解码层
生成代码
数据格式
语言特定的格式,编程语言内建的将内存对象编码为字节序列的格式
文本格式,JSON,XML,CSV等文本格式,具有人类可读性
二进制编码,具备跨语言和高性能等优点,例如Thrift的BinaryProtocol,Protobuf等
由于前两种都有一定的限制,可能会导致歧义,常使用二进制编码的形式对代码进行编码,实现可以有TLV编码和Varint编码。
二进制编码
- TLV编码
T-Tag:标签,可以理解为类型
L-Langth:长度
V-Value:值,Value也可以是一个TLV结构
协议层
特殊结束符
一个特殊结束符作为每个协议单元结束的标示。
变长协议
以定长加不定长的部分组成,其中定长的部分需要描述不定长的部分的长度。
协议构造
LANGTH:数据包大小
HEADER MAGIC: 标识版本信息,协议解析时候快速校验
SEQUENCE NUMBER: 表示数据包的 segID可用于多路复用,单连接内递增
HEADER SIZE: 头部长度,从第14个字节开始计算一直到 PAYLOAD前
PROTOCOL ID:编解码方式,有 Binary 和Compact 两种
TRANSFORM ID: 压方式,如 zlib 和snappyINFO ID: 传递一些定制的 meta 信息
PAYLOAD: 消息体
协议解析
网络通信层
Socket API
网络库
- 提供易用 API
- 封装底层 Socket API
- 连接管理和事件分发
- 功能
- 协议支持: tcp、udp 和 uds 等
- 优雅退出、异常处理等
- 性能
- 应用层 buffer 减少 copy
- 高性能定时器、对象池等
RPC框架的关键指标
稳定性
保障策略
- 熔断:保护调用方,防止被调用的服务出现问题而影响到整个链路。
一个服务A调用服务B时,服务B的业务逻辑又调用了服务C,而这时服务C响应超时了,由于服务B依赖服务C,C超时直接导致B的业务逻辑一直在等待,而这个时候服务A又频繁地调用服务B,服务B就可能会因为堆积大量的请求而导致服务宕机,由此就导致了服务雪崩的问题。
- 限流:保护被调用方,防止大流量把服务压垮。
当调用端发送请求过来时,服务端在执行业务逻辑之前先执行检查限流条件,如果发现访问量过大并且超出了限流条件,就让服务端直接降级处理或者返回给调用方一个限流异常
- 超时控制:避免浪费资源在不可用的节点上
当下游的服务器因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,避免资源浪费。
请求成功率
-
负载均衡
-
重试
长尾请求
长尾请求一般指延迟明显高于均值的那部分占比较小的请求。业界关于延迟有一个常用的P99标准,P99单个请求响应从小到大排列,顺序处于99%位置的值即为P99值,那后面这1%可以认为是长尾请求。
正常情况下,我们发出一个请求Req1,当这个请求正常时间内没有返回时,我们再发出一个请求Req2,等待Req2正常返回。此时所花费的时间是t1+t2。
而如果我们首先设定一个阈值t3(比超时时间小,通常建议是RPC请求的pct99),当Req1发出去后超过t3时间都没有返回,我们马上发送一个请求Req2,这样同时有两个请求运行,然后我们等待请求返回,只要Req1或者Req2有一个请求返回,就可以立刻结束这次请求。这样整体的耗时就是t4。相比于上图中超时之后再发送请求所花费的t1+t2,这种机制能够大大减少整体延时。
注册中间件
易用性
-
开箱即用:合理的默认参数,丰富的文档
-
周边工具:生成代码工具,脚手架工具
扩展性
- MiddleWare
- Option
- 编解码层
- 协议层
- 网络传输层
- 代码生成工具插件扩展
观测性
Log,Metric,Tracing
高性能
高性能可以分为两个维度:高吞吐和低延迟。大部分场景下低延迟更重要。
场景:
- 单机多机
- 单连接多连接
- 单/多client 单/多server
- 不同大小的请求包
- 不同请求类型:例如pingpong,streaming等
手段:
- 连接池
- 多路复用
- 高性能编解码协议
- 高性能网络库