基于青训营的学习视频和一些资料,本文主要介绍了RPC框架的基本概念、核心功能、调用流程以及关键指标。
Num6
RPC基本概念
RPC 英文全称 Remote Procedure Call,在分布式计算中成为远程过程调用,如果涉及面向对象编程,亦可称为远程调用或远程方法调用。RPC 是一种进程间通信的模式,程序分布在不同的地址空间里,如果在同一主机里,RPC 可以通过不同的模拟地址空间进行通讯。使用的架构为客户端/服务器(C/S)。
完整的 RPC 框架主要包括客户端、服务端、注册中心三部分,客户端中有服务发现、调用模块、RPC 协议等组件,服务端中有服务暴露、处理程序、线程池、RPC 协议等组件。
目前流行的开源 RPC 框架有阿里巴巴的Dubbo、Facebook 的Thift、Google 的gRPC、Twitter 的Finagle等。 先重点介绍三种:
- gRPC:是 Google 公布的开源软件,基于HTTP 2.0 协议,并支持常见的众多编程语言。RPC 框架是基于 HTTP 协议实现的,底层使用到了 Netty 框架的支持。
- Thrift:是 Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架。 用户只要在其之上进行二次开发就行,应用对于底层的 RPC 通讯等都是透明的。不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的。
- Dubbo:是阿里集团开源的一个极为出名的 RPC 框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是极其鲜明的特色。
RPC核心功能
PC 协议部分就是 RPC 的核心功能,其主要组成部分为:客户端、客户端Stub、网络传输模块、服务端 Stub、服务端等。 客户端:服务调用方。 客户端 Stub:存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,在通过网络传输发送给服务端。 服务端 Stub:接收客户端发送过来的请求信息并进行解包,然后在调用本地服务进行处理。 网络传输模块:底层传输,可以是TCP或HTTP。
下面分别介绍核心 RPC 框架的重要组成:
- 客户端(Client):服务调用方。
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
- 服务端(Server):服务的真正提供者。
- Network Service:底层传输,可以是 TCP 或 HTTP。
RPC调用流程
本地客户端以本地调用方式调用服务,首先需要通知到本地的存根(Client Stub),接着本地存根会进行一些数据格式的包装,网络请求的封装等,最终组装成能够进行网络传输的消息体,按照一定的规则将这个消息体通过Socket发送到指定的目标机器上。
服务端的存根(Server Stub)在接收到相关的数据信息之后,需要将其按照事先约定好的规则进行解码,从而识别到消息体内部的信息,然后将对应的请求转发到本地服务对应的函数中进行处理。处理完的数据需要再通过Socket返回给调用方。
调用方存根在接收到服务方数据的时候,需要进行数据解码,最后得到这次请求的最终结果。
调用的流程如下图所示:
RPC关键指标
稳定性 —— 保障策略
保障策略主要有三种:
- 熔断:服务A调用服务B,服务B调用服务C,服务C响应超时,导致B一直等待,返回给A就是超时的,A就会频繁的调用服务B,因为频繁堆积的请求导致服务B宕机,就会产生服务雪崩 。保护调用方,防止被调用的服务出现问题影响到整个链路。
- 限流:保护被调用方,防止大流量把服务压垮。
- 超时控制:避免资源浪费在不可用的节点上。被调用端可能由于某种原因响应过慢,调用端会停止一些不必要的请求,快速的返回,及时释放资源,避免资源浪费在不可调用的服务上。
稳定性 —— 请求成功率
负载均衡(服务A均匀调用服务B的节点)、重试(多试几次可能就好了)
稳定性 —— 长尾请求
明显高于平均响应时间的占比比较小的那一部分请求,提高长尾请求的请求成功率,通过backup request
稳定性 —— 注册中间件
无论是client还是server,创建的时候都可以通过options的方式将这些可选的方式加上,灵活的注入稳定性策略,保证请求的稳定。
易用性
开箱即用,提供合理的默认配置,提供丰富的文档(遇到问题解决)
周边工具:生成代码工具(辅助用户更好的使用框,IDL 生成代码工具)、脚手架工具(生成一些重复的代码)
RPC 框架会有一个suite提供熔断、限流、降级等基本的功能。
扩展性
- 编解码层:支持不同的编解码
- 协议层:支持尽可能多的RPC通信协议
- 网络传输层:支持不同的网络库
- 中间件的执行流程:
request会分别经过middleware1,2,n后才会真正的将请求发送到远端,负载均衡,超时控制都是通过中间件的方式加一些策略的。
观测性
传统的三件套:Log(日志)、Mrtric(监控)、Tracing(跟踪,知道在那个阶段超时,往往会携带ID,通过ID将整个链路串起来。)
RPC框架有内置观测性服务,框架内部的配置是什么,运行过程中环境变量是什么,运行过程中的线程有多少,协程有多少,使用了哪些中间件,都需要框架主动的暴露出来。
高性能
- 目标:高吞吐(在单位时间内尽可能处理多的请求)、低延迟(发出请求返回时间尽可能的短)
- 场景:单机多机、单连接多连接、不同大小的请求包、不同请求类型(不是简单的发送一个请求返回一个响应,一个请求多个响应)、单/多 client ,单/多 server
- 手段:连接池、多路复用(提高连接的复用率,减少重复创建消费连接的开销)、高性能编解码协议、高性能网络库
RPC框架的实现
通过上面几点的介绍,如果要我们实现一个 RPC 框架,我们应该如果做呢?要实现 一个RPC 框架,我们只需要解决的以下几件最基本的事情:
网络通讯
远程调用中,客户端和服务端的通讯是基于网络连接的,所以首先需要建立通信连接,通过这个连接把请求信息的字节流传给服务端,然后再把序列化后的响应结果传回客户端,在这个通讯过程中,它所使用的协议是没有限制的,能完成传输就行,但是在这里我们需要考虑两个问题:如何选择网络协议和如何建立连接。
-
网络协议的选择:多数 RPC 框架选择 TCP 作为传输协议,但其实 UDP 也可以,也有部分选择HTTP,比如 gRPC 使用 HTTP2,但是不同的协议各有优劣势,TCP 更加高效,而 HTTP 在实际应用中更加的灵活,具体需要根据使用场景来选择,下文会介绍如何选择正确的网络传输协议
-
通讯连接的建立:RPC 所有交换的数据都在这个连接里传输,这个连接可以是按需连接(需要调用时就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。
服务寻址
解决寻址的问题,也就是说服务端如何确定客户端要调用的函数,在本地调用中,函数是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以在远程调用中,客户端和服务端需要分别维护一个【ID -> 函数】的映射表,ID 在所有进程中都是唯一确定的,客户端在做远程过程调用时,附上这个 ID,服务端通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
而寻址问题的具体实现方式,则可以通过注册中心,服务提供者完成后,对外暴露相应的功能并将自己注册到注册中心上,接着服务消费者从注册中心寻找服务,然后调用该服务对应的方法完成远程调用
序列化和反序列化:
在本地调用中,我们只需要把参数信息压到内存栈中,然后让函数自己去栈中读取,但是远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。所以远程过程调用中,客户端和服务端交互时,方法的参数和结果需要通过底层的网络协议如 TCP 传递,由于网络协议是基于二进制的(只有二进制数据才能在网络中传输),那么这些值需要序列化成二进制的形式,通过寻址和传输将序列化的二进制发送目标服务器。目标服务器接收到数据时,需要对数据进行反序列化。序列化和反序列化的速度也会影响远程调用的效率。
将对象转换成二进制流的过程叫做序列化
将二进制流转换成对象的过程叫做反序列化
小结
RPC 框架作为架构微服务化的基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率。希望大家看完能够有所收获。