★微服务系列
1 什么是RPC通信
2 RPC通信详解
2.1 RCP角色和职能
2.2 RPC调用流程
RPC(Remote Procedure Call)远程过程调用,即一个节点通过网络调用的方式来请求另一个节点提供的服务的过程,也可以简单的理解为client访问server上提供的函数。
他的基本调用流程如下:
- 客户端(client)以本地调用方式(即以接口的方式)调用服务;
- 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制 byte[]);
- 客户端通过sockets将消息发送到服务端;
- 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
- 服务端存根( server stub)根据解码结果调用本地的服务;
- 本地服务执行并将结果返回给服务端存根( server stub);
- 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
- 服务端(server)通过sockets将消息发送到客户端;
- 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息反序列化);
- 客户端(client)得到最终结果。
2.3 多
内容项 | RPC调用 | 本地调用 |
函数寻址 | IP端口路由(NamingService + LoadBalancer)+函数路由 | 内存指针 |
传递数据 | 序列化后的数据流 | 内存对象 |
调用方异常处理 | timeout、retry、curcuit breaker | 抛出Exception / 函数返回固定异常标识的数据 |
被调用方异常处理 | 认证鉴权、过载保护 | 入参检查 / 执行异常捕获和处理 |
问题定位 | 分布式Trace、监控、日志中心 | 日志记录 / 断点调试跟踪 |
性能优化 | 连接池、多路复用、线程池、轻量级线程、non-block IO 等 | 编译器优化(inline等) |
这里可以看出rpc比函数调用复杂的多,比如:
- 函数寻址:你怎样调到你想要的哪个函数?在本地调用中其实就是一个函数指针,但是在RPC场景下,你要找到这个函数其实非常复杂,一个服务一般有多个下游实例,首先要选择一个下游实例,一般这个是由NamingService+LB来实现,到达对应的实例后,服务端还要解析请求体,找到函数名,然后做函数路由。
- 数据传递:在本地调用过程中其实就是传递一个指针或者值,在RPC场景下其实是通过网络传递的,网络上需要传递一个内存对象序列化之后的一个二进制网络数据流,response回来的时候也需要经过一个反序列化的过程。
- 异常处理:本地调用的情况下,无非就是判断下这个函数的返回值或者有没有抛一些异常,但是在RPC场景下就很复杂,比如网络拥塞了,服务端处理慢了或者超时,还有很多异常的情况,所以我们要做很多系统容错的事情,比如:超时、重试等策略来解决这些问题。本地调用的时候我只需要检查参数是否合法,但是在RPC的场景下我们要做一些类似认证鉴权,过载保护等策略,避免流量过大将server打挂。
- 问题定位:本地调用方法很多,比如:断点调试,打本地日志。但是在RPC场景下,这些方法其实是行不通的,我们需要分布式tracing、监控和分布式日志中心来帮助我们定位问题。
- 性能优化:本地调用其实我们不用关心太多,因为编译器会帮我们做一些列的优化,但是在RPC的场景下,就需要我们自己优化通信效率,常用的优化手段比如:连接池、多路复用、线程池等等很多方法,这些方法实现起来都非常的复杂。现在大家应该能理解RPC场景是非常复杂的
正因为有如此的复杂性,所以我们需要一个RPC框架来处理这些复杂的事情,让RPC看起来就像本地调用一样简单。
RPC 框架调用流分析
2.6.1 RPC框架功能(简单版本)
实现的过程:
- client初始化一个channel,监听NamingService,从服务名字中解析出来服务真正的上游实例地址
- 客户端将请求的的数据进行序列化
- 上游可能多个实例,需要LB去选择一个下游的IP+Port,选出来之后需要和上游实例建立连接和发送请求
- 建立连接之后发送请求
- 服务端接着接受连接和接受数据,收到数据之后将二进制数据反序列化为一个内存对象request
- 然后再调用server的响应方法进行处理
- 服务端通过sockets将消息发送到客户端;
- 客户端接收到结果消息,并进行解码(将结果消息反序列化)
2.6.2 RPC框架功能(复杂版本)
有些RPC框架不只是处理通信相关的工作(如数据的序列化和反序列化,协议的解析/打包,数据的压缩解压缩,数据的加密和解密),还可以做很多微服务治理的工作。
比如Dubbo支持对服务的治理,包括 服务注册与发现、故障注入、超时重试、负载均衡、连接管理和健康检查等。除此之外,服务端还有认证鉴权、并发流量限制、函数路由、协议适配和参数校验等等复杂的策略。
所以一个成熟的RPC框架也可以是一个非常复杂全面的分布式系统,在一定程度上协助工程进行微服务建设。
对比项 | Dubbo | gRPC | brpc | Thrift |
公司 | Ali | Baidu | ||
通讯协议 | tcp/http | http2 | 多种协议 | tcp/http |
序列化协议 | 可扩展 | protobuf | protobuf/json/mcpack | 可扩展 |
开发语言 | Java | 跨语言 | C++ / Java | 跨语言 |
主要特点 | 服务治理、扩展性 | 跨语言、性能 | 高性能、扩展性 | 跨语言 |
github star |
36.9K | 33.5K | 12.9K | 8.9K |
2.8 与RESTful API 的区别
RPC 主要用于公司内部的服务调用,性能消耗低,传输效率高,实现复杂。
HTTP 主要用于对外的异构环境,浏览器接口调用,App 接口调用,第三方接口调用等。
RPC 使用场景(大型的网站,内部子系统较多、接口非常多的情况下适合使用 RPC):
- 长链接。不必每次通信都要像 HTTP 一样去 3 次握手,减少了网络开销。
- 注册发布机制。RPC 框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。
- 安全性,没有暴露资源操作。
- 微服务支持。就是最近流行的服务化架构、服务化治理,RPC 框架是一个强力的支撑。
3 总结
通过本篇我们详细学习了RPC的概念和原理,以及它能够提供的能力。也对目前业内主流的RPC的框架有了一定的了解。后面一篇我们以Dobbo为例子,来学习下怎么使用RPC框架来进行服务之间的通信。