glmapper

关于 rpc 的整理和理解

字数统计: 2.7k阅读时长: 9 min
2018/11/10 Share

架构演变

  • 所有的界面和服务均在同一个进程下

  • 基于mvc的视图与服务分离,但是实际上还是在一个应用系统中,只不过在功能层次上划分的更加细致

  • 粒度更细,对于不同的功能服务进行切分,并进行单独的部署

  • 面向服务的架构,将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来

  • 微服务

    此处不支持图片展示,自行脑补!!!

随着业务量和用户量的增加,架构也是从单一系统走向分布式系统,我能想到的是,这种架构的演变主要解决的问题在于:

  • 通过业务模块的拆分,使得每个模块的职责更加清晰,但是模块的职责边界的划分往往也是很疼头的事情。
  • 细致的划分使得项目在管理上面会更加方面,从代码的角度来说,开发和维护的成本也会降低,不会因为一个bug去跑整个项目了。
  • 提高了系统的容错率,单一系统如果宕机那就真的gg了,另外就是,单个环节出现问题也会导致项目无法正常运行(比如数据库出问题了)。对于分布式系统来说,一般都会使用冗余的方式来提高可用性,个人理解就是可以提供多个一样的服务,它们之间可以进行切换。
  • 分布式系统带来的问题一个是成本,硬件成本,运维成本都会增加。

rpc简介及常用的rpc框架

随着集中式架构向分布式架构的转变,应用系统之间的服务调用与通讯问题成为了首要解决的需求。

而RPC 的主要目标就是为了让构建分布式计算(应用)变得更加简单,在提供强大的远程调用能力时不损失本地调用的语义简洁性。 为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   @Autowired
private GlRpcAgent glRpcAgent; //rpc代理
/**
* @param param 此处约定参数以Map键值对的形式传递
*/
@Override
public List<OrderInfo> queryOrdersByUserId(Map<String, Object> param) {
//创建远程调用代理(远程服务的类的全限定名)
OrderConsumeAgent orderConsumer=glRpcAgent.getAgent("com.glmapper.rpc.interface.OrderConsumeInterface");
//通过代理获取返回结果 此处getOrders为远程服务器上的com.glmapper.rpc.interface.OrderConsumeInterface接口中的方法,param为参数
Map<String,Object> resultMap=(Map)orderConsumer.call("getOrders",param);
//解析返回结果(远程方法同样以Map集合的方式放回)
List<OrderInfo> orders = parseResultMap(resultMap);
return orders;
}

为什么要以全限定名来获取呢,这个我们将会在后面来说。

什么是RPC

In distributed computing a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in another address space(commonly on another computer on a shared network), which is coded as if it were a normal (local) procedure call, without the programmer explicitly coding the details for the remote interaction.
RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。 它允许程序调用另一个进程上(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同

从定义中可以得知,RPC主要来解决三件事情:

  • 进程间通讯
  • 提供和本地方法调用一样的调用机制
  • 屏蔽程序员对远程调用的细节实现

首先是进程间的通信问题,对于分布式环境,rpc能够帮助我们解决不同服务器之间的通信及数据传输问题,即做好方法调用到数据的转换,然后借助网络进行数据传递;rpc客户端向rpc服务端发起远程服务调用,通过请求的封装,参数的封装,序列化、编码、约定协议传输、解析请求、处理请求、封装返回消息数据、在进行返回数据的序列化、编码、在通过网络返回给客户端。再者是提供和本地方法调用一样的调用机制,为什么这么说,对于业务系统来说,我们更多的关注点在于如何解决实际的业务需求问题,而不想花更多的时间和心思在诸如上述过程中关于网络传输及编解码过程,因此对于rpc来说,需要将这些编解码、协议约定、网络传输等进行一个整体的封装,然后只向业务系统提供最简单的调用方式。最后一个屏蔽程序员对远程调用的细节实现,其实也就是第二点中提到的那些功能的封装,我们不用去关系rpc到底是如何实现的,也不用关心它是如何运作的,对于业务开发人员来说,通过约定的方式进行类似于本地方法调用的形式来调用远程服务接口就可以了。
那么如何实现透明化的远程调用呢?
什么样的内部封装才能让我们觉得像以本地调用方式调用远程服务呢?
对于java来说就是使用代理。java代理有两种方式:1) jdk 动态代理(接口代理);2)cglib代理(子类代理)。尽管字节码生成方式实现的代理更为强大和高效,但代码不易维护,大部分公司实现RPC框架时还是选择动态代理方式。这部分也将会在后续的章节中展开来说。

RPC基本原理

上面说到,rpc需要对一些远程调用的内部实现进行封装。我们说到有以下几个点:

  • 序列化
  • 编解码
  • 协议
  • 网络

从发起远程调用到接收到数据返回结果,大致过程是:

1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3)client stub找到服务地址,并将消息发送到服务端;
4)server stub收到消息后进行解码;
5)server stub根据解码结果调用本地的服务;
6)本地服务执行并将结果返回给server stub;
7)server stub将返回结果打包成消息并发送至消费方;
8)client stub接收到消息,并进行解码;
9)服务消费方得到最终结果。

那么rpc就相当于将step2-step8的步骤进行了封装。下面借用一张网上的图片来帮助我们理解这个过程。

RPC模型

对于上图,我们进行进一步的拆解得到(来自网络):


RPC 服务端通过 RpcServer 去暴露服务接口,而客户端通过 RpcClient 去获取服务接口。客户端像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理 RpcProxy。代理封装调用信息并将调用转交给 RpcInvoker 去实际执行。在客户端的 RpcInvoker 通过连接器 RpcConnector 去维持与服务端的通道 RpcChannel,并使用 RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务端。RPC 服务端接收器 RpcAcceptor接收客户端的调用请求,同样使用 RpcProtocol 执行协议解码(decode)。
解码后的调用信息传递给 RpcProcessor 去控制处理调用过程,最后再委托调用给 RpcInvoker 去实际执行并返回调用结果。

通过上述分析可知,这里面包括以下核心组件:

  • 用于暴露服务接口的RpcServer
  • 用于发现服务接口的RpcClient
  • 远程接口的代理实现RpcProxy
  • 负责协议编解码的RpcProtocol(实际的rpc框架中一般会提供多种不同的实现)
  • 网络连接器
    (之前看过一篇文章说9个组件,对于咱们这个来说,部分模块可以集成在client和server中)

常见的RPC框架

目前常见的分布式RPC框架有以下几个:

  • dubbo
    阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成
  • motan
    新浪微博开源的一个Java 框架。它诞生的比较晚,起于2013年,2016年5月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
  • rpcx
    Go语言生态圈的Dubbo, 比Dubbo更轻量,实现了Dubbo的许多特性,借助于Go语言优秀的并发特性和简洁语法,可以使用较少的代码实现分布式的RPC服务。
  • gRPC
    Google开发的高性能、通用的开源RPC框架,主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现上面的框架的功能需要进一步的开发。
  • thrift
    Apache的一个跨语言的高性能的服务框架

RPC与MQ

MQ(message queue)消息队列,从某种程度上来说,同样可以实现RPC的功能。从功能特点上来说,MQ可以把消息存储,而RPC不行。关于MQ和RPC做了以下简单的对比,如下图所示:

总结

本文对RPC的基本原理、特点以及基本组件进行了简单的说明,让我们可以对RPC有一个基本的了解。关于常见的RPC框架也做了基本认识,对于这些优秀的框架,我们在实现我们自己RPC时可以借鉴一下这些架构里的一些模式以及技术。最后说明了下为什么我们会在分布式架构中要使用RPC而不是MQ,对于MQ来说,在处理同步调用无法满足实际的生产需求,而RPC才更加适合分布式应用的实际需要。

原文作者:GuoLei Song

原文链接:http://www.glmapper.com/2018/11/10/seiri-rpc/

发表日期:November 10th 2018, 1:28:43 pm

更新日期:December 14th 2019, 11:13:52 am

版权声明:转载请注明出处

CATALOG
  1. 1. 架构演变
  2. 2. rpc简介及常用的rpc框架
    1. 2.1. 什么是RPC
    2. 2.2. RPC基本原理
    3. 2.3. RPC模型
    4. 2.4. 常见的RPC框架
    5. 2.5. RPC与MQ
    6. 2.6. 总结