大家好,我是陈老师~大家在搭建分布式系统的时候,必须要考虑的一个问题就是:如何实现服务之间的调用?当然,你可以使用Dubbo或SpringCloud等分布式服务框架来封装技术实现的复杂性来达到这个目的。但是,如果你现在没有这些框架,需要自己实现远程调用,你会怎么办?关注公众号:码猿技术专栏,回复关键字:1111获取阿里内部Java性能调优手册很多人会选择自己实现一套RPC框架来调用远程服务。那么你了解RPC架构的基本结构吗?如果要自己实现RPC框架来完成远程调用,应该搭建什么样的技术体系呢?接下来小编就为大家详细介绍一下。RPC架构的基本结构如果要构建一个完整的RPC架构,就需要明确架构的基本结构,而RPC架构的基本结构中有很多组件。那么接下来,我就通过RPC的基本结构的演进过程,一一为大家讲解。首先,我们通常将具有调用关系的两个服务称为服务提供者(Provider)和消费者(Consumer)。所以,简单来说,RPC就是服务消费者向提供者发起远程调用并获取结果的过程。这是最简单的RPC形式。如果要实现服务提供者和消费者之间的有效交互,那么两者之间就需要建立与网络通信相关的网络协议和通信通道。同时,服务提供者需要暴露自己的服务调用入口,随时准备接收消费者的请求。这里,我们将通信通道和网络协议分别命名为RpcChannel和RpcProtocol,服务提供者接收请求的组件称为RpcAcceptor,消费者发起请求的组件称为RpcConnector。这样一来,RPC的架构就演变成了这样:那么,对于服务提供者和消费者来说,为了让双方能够正常识别发送的请求和接收到的响应结果,需要定义一个统一的契约。我们称这个合约为远程API(RemoteAPI),以区别于本地API。这样,基于同一套远程API定义,RPC架构就具备了基于业务定义通信契约的能力。同样,为了更好的区分RPC架构中的角色,我们将实际提供业务服务的组件称为RpcServer,将真正发起客户端请求的组件称为RpcClient。这样RpcServer负责实现远程API,RpcClient负责调用远程API。当然,对于远程API,服务提供者和消费者的处理方式显然是不同的。提供者需要根据消费者的请求调用RpcServer的具体实现并返回结果。这部分工作由RpcInvoker完成,消费者通过RpcCaller组件对请求进行编码,发送给服务端,等待结果。最后,为了降低开发者的开发难度,让远程调用的执行过程看起来像是在执行本地方法,在主流的RPC实现机制中,通常会在客户端加入代理机制,提供远程服务本地化的访问入口,我们称这个代理组件为RpcProxy。另外,在服务端,为了更好地控制业务方法的执行过程,通常会引入具有线程管理、超时控制等机制的RpcProcessor组件。以上就是整个RPC架构的演进过程。从中可以看出,RPC架构中的客户端组件和服务器端组件形成了一个对称的结构。他们各司其职,但在一起又是一个整体。为了帮助大家加深理解,这里我将上面提到的各个组件进行总结。客户端组件及职责包括:RpcClient,负责调用远程API。这个过程会依赖RpcProxy提供的代理实现来实现远程API的代理实现RpcProxy,以及提供远程服务本地化访问的RpcCaller。它负责编码并向服务发送调用请求RpcConnector负责与服务器建立通信通道并向服务器发送请求。服务器组件及职责包括:RpcServer,负责实现远程APIRpcInvoker,负责调用服务器的具体实现并返回结果RpcProcessor,负责处理请求,高效控制调用过程RpcAcceptor,负责接收客户端请求和返回请求结果。client和server的公共组件包括:RpcProtocol,负责对网络传输协议进行编解码RpcChannel,负责建立和维护网络数据传输通道。这样,我们就对一个典型的RPC架构中的基本结构和组件有了完整的了解。那么,如果我们要实现这个架构,需要构建什么样的技术体系呢?RPC架构的技术体系大家都知道。建筑是一种设计思想和方法。了解了它的基本结构和组成之后,我们就可以进一步梳理出我们要实现RPC架构的技术体系,包括网络通信、序列定位、传输协议、远程调用等。网络通信我们先来看一下网络通信。网络通信涉及方方面面。对于RPC架构,一方面我们会关注性能,因此需要考虑基于TCP等特定协议的网络连接方式和IO模型;另一方面,我们还需要考虑可靠性。因为这样可以保证远程调用过程的稳定性。好的,让我们详细看一下。首先是性能问题。一般来说,基于TCP协议的网络连接有两种基本方式:长连接和短连接。长连接和短连接的本质区别在于连接的创建和关闭策略。长连接可以复用已有的连接,而短连接可以更快的释放资源。两者各有优缺点。在RPC框架的实现过程中,考虑到性能和服务治理等因素,我们通常使用长连接进行通信。典型的实现框架是Dubbo。至于IO模型,最简单最基本的网络IO模型就是阻塞IO,即BIO(BlockingIO)。BIO要求客户端请求数和服务端线程数一一对应,但是显然,由于创建线程会消耗系统资源,在分布式系统中,服务端可以创建的线程数会成为系统的瓶颈。因此,在RPC架构中,我们通常使用非阻塞IO,即NIO(Non-blockingIO)技术来提供性能。基于NIO模式的多路复用机制,创建少量的线程可以高效响应大量的请求。然后是可靠性问题。由于网络中断、超时等与网络状态相关的不稳定因素,以及业务系统自身的故障,网络间的通信必须能够在上述问题发生时快速检测和修复。常见的网络通信保障方法包括链路有效性检测和断开后的重连处理。这些机制都是比较常见的,不是我们讨论的重点,这里就不细说了。序列化而如果我们要在网络上传输数据,就需要用到数据序列化技术。目前业界已经有很多成熟的序列化工具。常见的XML和JSON是文本序列化方法的代表,它们允许数据以开发人员可读的方式传输。还有基于二进制实现的解决方案,包括Google的ProtocolBuffer和Facebook的Thrift。那么,我们在选择序列化工具时应该考虑什么呢?一个关键指标是性能。性能指标主要包括空间复杂度、时间复杂度、CPU/内存资源占用。我在下表中列出了一些目前主流的序列化技术,供大家参考:可以看到,在时间维度上,阿里巴巴的fastjson有一定的优势;而在空间维度上,相比其他技术Buffer,可以优先选择Protocol。传输协议我们知道,只要涉及到通过网络传输数据,就必须使用某种传输协议。在ISO/OSI7层网络模型中,RPC架构的设计与实现通常会涉及到传输层及以上的相关协议。大家熟悉的TCP协议属于传输层,而HTTP协议则位于应用层。无论使用7层网络模型的哪一层,在网络请求过程中,数据都是以消息的形式传输的。消息的组成具有一定的结构。消息头和消息体构成了传输消息的主体。消息体表示要传输的业务数据,消息头用于传输控制。可以看出,每一层都从上层获取数据,加上消息头信息,形成新的消息体,将新消息传递给下一层。通过扩展消息头和消息体,我们可以实现一个私有化的传输协议。这也是大部分RPC框架采用的实现方式。这样做的主要目的是简化公共协议以提高性能。此外,出于可扩展性的考虑,高度定制化的私有协议比公共协议更容易扩展。一个典型的例子就是Dubbo框架,它提供了完全自定义的Dubbo协议。通过远程调用明确了网络通信的基本方法、序列化方式和采用的传输协议后,我们就可以发起真正的远程调用了。RPC本质上是一种服务调用,服务调用有两种基本方式,即单向(OneWay)模式和请求-响应(Request-Response)模式。前者体现为异步操作,后者一般进行同步操作。首先我们要知道同步调用会导致业务线程阻塞,但是开发和管理会比较简单。为什么是这样?我们先看一下同步调用的时序图:可以看到服务线程向IO线程发送请求后,一直处于等待阶段,直到IO线程完成与网络的读写操作在被主动唤醒之前。使用异步调用的目的是为了获得高性能。在实现异步调用的过程中,我们通常会使用到Java中提供的Future机制。Future调用又可以进一步细分为两种模式,Future-Get模式和Future-Listener模式。Future-Get模式参考下图:可以看到,在这种模式下,服务线程通过主动获取结果的方式获取Future结果,而这个get过程是串行的,会导致执行get方法的线程形成一个块。Future-Listener模式不同。Future-Listener模式下,需要创建一个Listener。当Future结果产生时,注册到Future的Listener对象会被唤醒,从而形成异步回调机制。除了同步和异步调用之外,还有并行(Parallel)调用和广义(Generic)调用等调用方式。虽然有特定的应用场景,但都不是RPC架构的主流调用方式。具体展开。综上所述,可以说RPC是分布式系统中的基础技术体系,但是涉及到服务之间的交互,就需要用到RPC架构。当大家在使用分布式框架的时候,可以尝试分析一下今天介绍的RPC架构的基本结构和技术体系,加深对这个技术体系的理解。最后说一句(勿嫖,求关注)小陈的每篇文章都是用心输出,如果这篇文章对你有帮助或者启发,请帮忙点赞、观看、转发、收藏,你的支持是最大的是我坚持下去的动力!另外,陈氏知识星球已经开通。公众号回复关键词:知识星球限30元优惠券加入,仅需89元,一顿饭,不过星球回馈价值巨大,现已更新春季全家桶实战系列,实战亿级数据分库分表实战,DDD微服务实战专栏,我要进大厂,Spring,Mybatis等框架源码,架构实战22讲等,涨价了每增加一个专栏减20元关注公众号:【码猿科技专栏】,公众号有给力的粉丝福利,回复:进群,可以加入技术讨论群,和大家一起讨论技术,吹牛!
