RPC原理与实现

603 阅读8分钟

什么是RPC?

RPC(Remote Procedure Call Protocol)远程过程调用协议。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。

那么我们至少从这样的描述中挖掘出几个要点:

  • RPC是协议:既然是协议就只是一套规范,那么就需要有人遵循这套规范来进行实现。目前典型的RPC实现包括:Dubbo、Thrift、GRPC、Hetty等。
  • 网络协议和网络IO模型对其透明:既然RPC的客户端认为自己是在调用本地对象。那么传输层使用的是TCP/UDP还是HTTP协议,又或者是一些其他的网络协议它就不需要关心了。
  • 信息格式对其透明:我们知道在本地应用程序中,对于某个对象的调用需要传递一些参数,并且会返回一个调用结果。至于被调用的对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于远程调用来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的。
  • 应该有跨语言能力:为什么这样说呢?因为调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。

image-20230413134629079.png

那要怎样实现透明化远程调用呢?要让开发者无感知的调用远程方法呢?答案就是动态代理!!!

动态代理会通过反射获取到远程服务的接口,当执行本地方法时就会调用invoke方法,进行通信相关逻辑,最后成功调用。所以还记得为什么要在进行远程调用的时候消费者和服务者都要定义接口信息,所以公共模块就可以把Service的接口提取出来

什么是动态代理

虽然上面说到需要用到动态代理,但是这个玩意儿到底是个啥,所以这里也可以简单聊一聊。

代理其实是一种结构性设计模式。可以控制对类对象的访问,在访问类对象时增加额外功能。类似与下图所示,代理会获取到当前接口的实现类,并在其基础上进行增强。

image-20230413125527475.png

而代理又分为静态代理和动态代理,他们的区分方式是``代理关系是否在编译期确定`。

静态代理

就比如现在想去租房,然后现在只有中介手里才有房源,所以你就可以跟中介进行沟通,而不用去直接和房东沟通。因为在编译期就知道了代理关系,所以是静态代理。通俗的来讲就是你事先知道这个中介是买房子的。

但是静态代理存在一定的缺陷,重复性:随着业务的增加需要编写很多模板代码,也就解释了为什么不适用静态代理实现RPC框架。脆弱性:一旦基础接口改动,代理类也需要同步修改。

动态代理(通用+兼容多个业务)

同样是中介,只不过这次的中介是拼命三郎,一天打n份工,不仅卖房,还卖车,还卖酒,各种。。。也就是说在联系之前,我们是不知道它卖的是什么?但是见面之后你告诉他需求,他就能帮你找到!是不能提前知道代理关系,所以是动态代理。

动态代理一般有JDK、CGLIB、JavaScript几种实现方式。这里只讨论JDK。它可以利用反射机制在运行时生成的类的字节码,主要是利用Invoke函数去直接访问基础对象,另外也可以通过使用泛型来实现兼容多个业务接口。

对比来看!静态代理就是提前确定一个类,需要把所有的类都提前确定,而动态代理则是可以将类细节隐藏,同时负责多个接口。另外静态代理的类字节码是在编译期就确定的,而动态代理则是在运行时生成的。他们的相同点就是可以不修改原来的对象,只是自己新创建一个对象进行扩展。但重点是控制对于原有对象的访问。

这里额外的进行一个扩展,刚刚提到的代理模式,是不是与装饰器模式特别像,至少在功能扩展上是的吧

装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;代理模式强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。

  1. 代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
  2. 装饰模式是以对客户端透明的方式扩展对象的功能,是继承方案的一个替代方案;代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用;
  3. 装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强;

解释清楚了什么是动态代理,那么中间有提到反射,这又又又是一个技术深入的点。

什么是反射

刚才有提到利用反射获取的类的字节码。是的,反射的作用就是在运行时动态的获取类。

在开始之前,先来梳理一下Java的类型模型,首先Java是一个强类型语言,就是变量有固定的类型。另外Java是一个静态语言,即类型检查更偏向于编译时执行,但是也不全部都是,Java有时也会在运行时进行类型检查,比如Object对象的类型检查。

现在想象一下Python在进行变量的创建与接收的时候不用判断类型,同样的,Java通过反射也拥有了这样的特性。我们可以直接获取类的对象,进而获取其全部属性。

实质上,反射的实现就是将生成的Class对象直接拿到内存来用,通过Native方法来实现这个过程,而当反射执行次数较多时,通过ASM字节码生成新的类,并将以后的反射委派给新类。

RPC的实现

讲完了RPC的原理,接下来就是它的实现方式。

常见的几种实现方式包括grpc Thrift Dubbo,这里主要通过DUBBO来实现RPC。官网上是这样说的,提供高性能通信服务治理能力的微服务开发框架。

高性能通信

高性能数据传输

内置支持Dubbo2、Tripe两个高性能通信协议

  • Dubbo2 是基于 TCP 传输协议之上构建的二进制私有 RPC 通信协议,是一款非常简单、紧凑、高效的通信协议
  • Triple 是基于 HTTP/2 的新一代 RPC 通信协议,在网关穿透性、通用性以及 Streaming 通信上具备优势,Triple 完全兼容 gRPC 协议

流式通信

业务的迅速增长会使得集群规模增加,从而带来服务治理问题:

  • 注册中心的存储瓶颈
  • 节点动态变化地址推送与解析效率下降
  • 复杂网络链路管理

服务治理

流量管控

Dubbo 丰富的流量管控规则可以控制服务间的流量走向和 API 调用,基于这些规则可以实现在运行期动态的调整服务行为如超时时间、重试次数、限流参数等,通过控制流量分布可以实现 A/B 测试、金丝雀发布、多版本按比例流量分配、条件匹配路由、黑白名单等,提高系统稳定性。

微服务生态

具有较好的扩展机制,对于大多数服务治理需求都可满足,也可以通过扩展机制轻松适配。

Emmm....感觉框架和应用大致了解一下设计思想,清除一下相关技术替代品,能够在合适的场景下用就好

Reference

JDK动态代理的原理其实很简答

反射机制