RPC就是把拦截到的方法参数,转成可以在网络中传输的二进制,并保证在服务提供方能正确地还原出语义,最终实现像调用本地一样地调用远程的目的。
1 RPC架构
RPC本质是远程调用,就要通过网络来传输数据。考虑到可靠性,一般默认采用TCP协议。为屏蔽网络传输复杂性,要封装一个单独的数据传输模块收发二进制数据,即传输模块。
用户请求是基于方法调用,方法出入参数都是对象数据,要提前转成二进制,即序列化过程。但只是把方法调用参数的二进制数据传输到服务提供方不够,要在方法调用参数的二进制数据后增加“断句”符,分隔出不同的请求,在两个“断句”符号中间放的内容就是请求的二进制数据,即协议封装。
这两个不同过程目的一样,保证数据在网络中正确传输:
数据能够传输
传输后能正确还原出传输前的语义
可把这两个处理过程放在架构中的同一个模块,统称为协议模块。
还可在协议模块加压缩功能,压缩过程也是对传输的二进制数据进行操作。在实际网络传输过程中,请求数据包在数据链路层可能因太大而被拆分成多个数据包进行传输,为减少被拆分次数,导致整个传输过程时间太长,可在RPC调用时:在方法调用参数或者返回值的二进制数据大于某个阈值的情况下,我们可以通过压缩框架进行无损压缩,然后在另外一端也用同样的压缩算法进行解压,保证数据可还原。
传输和协议两模块是RPC最基础功能,它们使对象可正确传输到服务提供方。但距离RPC目标——实现像调用本地一样调用远程,还缺点。要让这两个模块同时工作,要手写一些黏合代码,但这些代码对使用RPC的研发无意义,且属于一个重复工作,导致使用体验不友好。
要在RPC里把这些细节对研发屏蔽,让他们感觉不到本地调用和远程调用区别。假设有用到Spring,希望RPC能让我们把一个RPC接口定义成一个Spring Bean,并且这个Bean也会统一被Spring Bean Factory管理,可在项目中通过Spring依赖注入到方式引用。这是RPC调用的入口,一般叫Bootstrap模块。
点对点(Point to Point)版本的RPC框架就完成了,一般这种模式的RPC框架为单机版,没有集群能力。
集群能力:针对同一接口有多个服务提供者,但这多个服务提供者对调用方透明,所以在RPC里还要给调用方找到所有的服务提供方,并在RPC里维护好接口跟服务提供者地址的关系,调用方在发起请求时,才能快速找到对应接收地址,即“服务发现”。
但服务发现只解决接口和服务提供方地址映射关系查找,是一种“静态数据”,对RPC来说,每次发送请求时都要用TCP连接的,相对服务提供方IP地址,TCP连接状态瞬息万变,所以RPC框架要有连接管理器去维护TCP连接状态。
有了集群,提供方可能就需要管理好这些服务,RPC就要内置一些服务治理功能,如服务提供方权重的设置、调用授权等一些常规治理手段。而服务调用方需要额外做哪些事?每次调用前,都要根据服务提供方设置的规则,从集群中选择可用的连接,以发送请求。
按分层设计原则,将这些功能模块分为:
2 可扩展架构
RPC框架怎么支持插件化架构?可将每个功能点抽象成一个接口,将这个接口作为插件契约,然后把这个功能的接口与功能实现分离,并提供接口默认实现。
JDK自带SPI可动态为某接口寻找服务实现,要在Classpath下的META-INF/services目录创建一个以服务接口命名的文件,文件内容就是接口具体实现类。
JDK自带SPI的缺陷
不能按需加载,ServiceLoader加载某接口实现类时,会遍历全部获取,即接口的实现类得全部载入并实例化,造成不必要浪费。
扩展如果依赖其它的扩展,就做不到自动注入和装配,很难和其他框架集成,如扩展里面依赖了一个Spring Bean,原生Java SPI就不支持。
加上插件功能,RPC框架就包含了两大核心体系——核心功能体系与插件体系:
整个架构就成了一个微内核架构,我们将每个功能点抽象成一个接口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离并提供接口的默认实现。
这样的架构可扩展性好,实现开闭原则,用户方便通过插件扩展实现功能,而且不需要修改核心功能本身
保持了核心包的精简,依赖外部包少,有效减少开发人员引入RPC导致的包版本冲突问题。
3 总结
我们都知道软件开发的过程很复杂,不仅是因为业务需求经常变化,更难的是在开发过程中要保证团队成员的目标统一。我们需要用一种可沟通的话语、可“触摸”的愿景达成目标,我认为这就是软件架构设计的意义。
但仅从功能角度设计出的软件架构并不够健壮,系统不仅要能正确地运行,还要以最低的成本进行可持续的维护,因此我们十分有必要关注系统的可扩展性。只有这样,才能满足业务变化的需求,让系统的生命力不断延伸。
4 FAQ
我是个小测试。使用jmeter进行压力测试。jmeter官网中支持进行定制sampler取样器,写好的jar包放在lib\ext下,再启动jmter时就能看到了。插件化是一个概念,有很多种实现方式,这种也算。
spring的spring.factories这一套也是利用的面向接口编程,感觉比jdk自带的spi也好很多,既然有些问题,那为啥jdk的spi不优化一下?jdk我理解更多是标准。
jdk自带spi一般会有一个接口加载很多实现类的情况吗,因为只能用迭代器遍历,导致只能用类型判断才能找到自己想要的类,这样感觉不够优雅吧,所以我感觉应该都是一个接口配置一个实现类这样就是使用者想要的情况了。那样插件的意义就不存在了!
业务为工业设备联网数据采集,设备种类和型号繁多,产品中通过抽象出一套“驱动”的概念,把每类设备当作一个插件开发,整体产品架构不变,感觉有点这个概念。只是产品还不够大,其他插件体系还不够明确。