07 | 架构设计:设计一个灵活的RPC框架
RPC就是把拦截到的方法参数,转成可以在网络中传输的二进制,并保证在服务提供方能正确地还原出语义,最终实现像调用本地一样地调用远程的目的。
RPC的本质是一个远程调用,需要通过网络来传输数据,我们一般采用TCP协议作为数据通信协议,为了屏蔽网络传输的复杂性,我们需要封装一个独立的数据传输模块来收发二进制数据,这个模块被称为传输模块。
我们在调用方法时,方法的出入参数都是对象数据,我们需要将对象转换成二进制,即进行序列化操作,同时,我们还需要在方法调用参数的二进制数据后面增加“断句”符号来分隔出不同的请求,这个过程被称为协议封装。
传输模块和协议封装都是为了保证数据在网络中可以正确传输,我们将这两个模块放在一起,称为协议模块。
我们还可以在协议模块中添加压缩功能,在方法调用参数或者返回值的二进制数据大于某个阈值时,我们使用压缩框架对数据进行无损压缩,然后再另外一段使用同样的压缩机制进行解压处理,保证数据可以还原。
传输模块和协议模块是RPC中最基础的功能,它们使得对象可以正确的传输到服务提供方。但是为了让这两个模块可以同时工作,我们还需要手写一些粘合代码,这些代码对于RPC框架的使用者来说是没有意义的,属于重复工作,我们将这些事情进行封装处理,形成RPC调用的入口,一般称为Bootstrap模块。
什么是”服务发现“?
针对同一个接口有多个服务提供者,但这多个服务提供者对于我们的调用方来说是透明的,所以在RPC里面我们还需要给调用方找到所有的服务提供方,并需要在RPC里面维护好接口和服务提供者地址间的关系,这样调用方在发起请求时才能快速找到对应的接收地址。
一个划分为四层的RPC基础架构的示意图如下。
上面的框架有什么问题吗?最主要的问题就是扩展性不高,当我们有需求变更时,很容易需要大范围调整框架。
我们可以尝试使用插件化架构的方法论来优化我们的RPC架构。
我们怎么在RPC框架里面支持插件化架构?
我们可以将每个功能点抽象成一个接口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离,并提供接口的默认实现。
加入插件功能后,RPC框架就包含了两大核心体系:核心功能体系和插件体系,示意图如下。
彩蛋 - Java SPI
什么是Java SPI?
SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制,可以用来扩展框架和替换组件,它的核心思想是解耦。
例如针对DB驱动程序,java.sql.Driver接口定义了相关规范,不同的厂家会针对这一规范提供不同的实现,例如MySQL和PostgreSQL会提供不同的实现给用户。
SPI和API有什么区别?
- API是调用并用于实现目标的类、接口、方法等的描述。
- SPI是扩展和实现类似目标的类、接口和方法等的描述。
SPI的使用流程是怎样的?
- 相关组织或者公司发布接口规范定义。
- 第三方根据接口提供具体实现。
- 开发者在程序中针对SPI进行配置并使用。
JDK提供的SPI有哪些不足?
- 不能按需加载,需要遍历所有的实现,并实例化,然后才能在循环中找到我们需要的实现。
- 获取某个实现类的方式不够灵活。
- 多个并发多线程使用ServiceLoader类的实例是不安全的。