RPC
1、概念
RPC(Remote Procedure Call)叫作远程过程调用,它是利用网络从远程计算机上请求服务,可以理解为把程序的一部分放在其他远程计算机上执行(需要有注册中心)。通过网络通信将调用请求发送至远程计算机后,利用远程计算机的系统资源执行这部分程序,最终返回远程计算机上的执行结果。
2、组成部分
PRC主要设计五个部分:
-
user(服务调用方)
服务调用方也叫服务消费者,调用服务端并获取返回结果
-
user-stub(调用方的本地存根)
user-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPCRuntime 实例传输到远端的实例
-
RPCRuntime(RPC通信者)
RPCRuntime负责数据包的重传,数据包的确认、数据包路由和加密等,在Consumer端和Provider端都会有一个RPCRuntime实例,负责双方之间的通信,可靠地将存根船渡地数据包传输到另一端,即:公共的接口
-
server(服务端)
服务提供方就是服务端,它的职责就是提供服务,执行接口实现的方法逻辑,也就是为服务提供方的本地存根提供方法的具体实现。
-
server-stub(服务端的本地存根)
user-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPCRuntime 实例传输到远端的实例
服务暴露
暴露到远程:其中的远程其实是指有一个统一的管理中心来管理所有应用服务的地址和服务信息,这个统一的管理中心就是注册中心(Registry)。
3、底层通信协议
RPC框架与具体的协议无关。RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC,它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC。
(1)TCP/HTTP:众所周知,TCP 是传输层协议,HTTP 是应用层协议,而传输层较应用层更加底层,在数据传输方面,越底层越快,因此,在一般情况下,TCP 一定比 HTTP 快。
(2) 消息ID:RPC 的应用场景实质是一种可靠的请求应答消息流,和 HTTP 类似。因此选择长连接方式的 TCP 协议会更高效,与 HTTP 不同的是在协议层面我们定义了每个消息的唯一 id,因此可以更容易的复用连接。
(3) IO方式:为了支持高并发,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持。
(4) 多连接:既然使用长连接,那么第一个问题是到底 client 和 server 之间需要多少根连接?实际上单连接和多连接在使用上没有区别,对于数据传输量较小的应用类型,单连接基本足够。单连接和多连接最大的区别在于,每根连接都有自己私有的发送和接收缓冲区,因此大数据量传输时分散在不同的连接缓冲区会得到更好的吞吐效率。所以,如果你的数据传输量不足以让单连接的缓冲区一直处于饱和状态的话,那么使用多连接并不会产生任何明显的提升,反而会增加连接管理的开销。
(5)心跳:连接是由 client 端发起建立并维持。如果 client 和 server 之间是直连的,那么连接一般不会中断(当然物理链路故障除外)。如果 client 和 server 连接经过一些负载中转设备,有可能连接一段时间不活跃时会被这些中间设备中断。为了保持连接有必要定时为每个连接发送心跳数据以维持连接不中断。心跳消息是 RPC 框架库使用的内部消息,在前文协议头结构中也有一个专门的心跳位,就是用来标记心跳消息的,它对业务应用透明。
4、优点
-
RPC 支持长链接,通信不必每次都要像 http 一样去重复 3 次握⼿,减少了网络开销
-
其次就是 RPC 框架一般都有注册中心模块,有完善的监控管理功能,服务注册发现、服务下线、服务动态扩展等都方便操作,服务化治理效率大大提高
-
基于 TCP 协议实现的 RPC,能更灵活地对协议字段进行定制,相比 http 能减少网络传输字节数,降低网络开销(握手)提高性能。实现更大的吞吐量和并发数
5、应用
使用最广泛的 Spring Cloud、Dubbo,基于 Spring Boot 特性整合了开源行业中优秀的组件,整体对外提供了一套在微服务架构中服务治理的解决方案。
6、问题
问:服务启动的时候服务基本信息被注册到注册中心,如果服务提供者挂了,注册中心如何知道服务不可用了呢?
答:服务掉线分为主动下线和心跳检测
主动下线:比如服务由于发版时,在重启之前先主动通知注册中心:我要重启了,有流量进来先不要分给我,让别的机器服务,等我重启成功后在放流量进来,或者是在管理后台手动直接摘掉机器,这个是主动下线。
心跳检测:心跳检测是处理服务非正常下线(如断电断网)的情况,这个时候如果注册中心不知道该服务已经掉线,一旦被其调用就会带来问题。为了避免出现这样的情况,注册中心增加一个心跳检测功能,它会对服务提供者(Provider)进行心跳检测,比如每隔 30s 发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线,赶紧更新 Consumer 的服务列表,告诉 Consumer 调用别的机器
问:如果注册中心挂了,比如你用的是 Zookeeper,如果 Zookeeper 挂了,那服务之间还能相互调用吗?
答:首先注册中心挂掉也要分两种情况,如果数据库挂了,ZK 还是能用的,因为 ZK 会缓存注册机列表在缓存里;
其次 ZK 本身就是一个集群的,一台机器挂了,ZK 会选举出集群中的其他机器作为 Master 继续提供服务;
如果整个集群都挂了也没问题,因为调用者本地会缓存注册中心获取的服务列表。省略和注册中心的交互,Consumer 和 Provider 采用直连方式,这些策略都是可配置的。
7、RPC 框架实现过程
-
客户端 invoke 方法编写,使用 JDK 的动态代理技术,客户端调用远程服务方法时调用的是 InvocationHandler 的 invoke 方法。
-
客户端 Filter 方法编写,完善的 RPC 框架少不了监控、路由、降级、鉴权等功能。
-
创建 Socket,在 Filter 方法中实现 Client.write 方法,其逻辑为从连接池(ChannelPool)中获取连接,然后将数据写进 Channel。
-
实现数据序列化、压缩,目的减少网络传输的数据量,向服务端发送 request 数据,这里可以使用 Netty 异步通讯框架。
-
服务端收到客户端发过的消息后,从 Channel 中将消息读出来之前,也会先经反序列化解压。
-
请求就到了服务端 Filter 中。请求依次经过监控、鉴权方法。
-
根据客户端传递来的服务信息和参数,通过反射调用相应的业务服务并拿到业务处理结果。然后在 ResponseFilter 中将返回结果写入 Channel。
-
服务端序列化、压缩等,发送给客户端。
-
客户端收到消息后,经过客户端反序列化、解压缩,后交给 ResponseThreadPoolProcessor 线程池处理。
-
ResponseThreadPoolProcessor 收到消息后,就将结果返回给之前的方法调用,整个调用请求就结束了。
Dubbo
通信原理:Dubbo四大组件
-
Provider:服务提供者。
-
Consumer:服务消费者。
-
Registry:服务注册与发现的中心,提供目录服务,亦称为服务注册中心(如:zookeeper)
-
Monitor:服务管控中心:对服务设置权限、降级处理,统计服务的调用次数、调用时间等信息的日志服务等(如:sentinel)
-
服务集成 RPC 后,服务(这里的服务就是图中的 Provider,服务提供者)启动后会通过 Register(注册)模块,把服务的唯一 ID 和 IP 地址,端口信息等注册到 RPC 框架注册中心(图中的 Registry 部分)。如springboot集成dubbo后会通过@DubboService注解标注。
-
当调用者(Consumer)想要调用服务的时候,通过 Provider 注册时的的服务唯一 ID 去注册中心查找在线可供调用的服务,返回一个 IP 列表(3.notify 部分)。如springboot集成dubbo后会通过@DubboReference注解标注。
-
第三步 Consumer 根据一定的策略,比如随机 or 轮训从 Registry 返回的可用 IP 列表真正调用服务(4.invoke)。
-
最后是监视功能,RPC 框架都提供监控功能,监控服务健康状况,控制服务线上扩展和上下线