你们想要的Dubbo,三歪搞来了
本文公众号来源:柳树的絮叨叨
作者:发型吃饭的柳树
本文已收录至我的GitHub
授人以鱼,不如授之以渔,其实这句话说的不只是如何教人。
从另一个角度看这句话,我们在学一样东西的时候,要找到这样东西的”渔“是什么。
对于一项技术来说,它背后的设计思想,就是学习它的”渔“,对于 Dubbo,”渔“,是微内核架构。
首先,我们以「保险索赔」为例,了解下什么是微内核架构。
保险索赔保险索赔的规则往往很复杂,不同保险产品、不同地区的索赔规则可能都不一样。举个例子,假设在纽约州(NY),汽车挡风玻璃被岩石击碎,是可以索赔的,但是在加利福尼亚州(CA)则不行。这时候如果直接把这个逻辑写到代码里去,就是这样:
if (在纽约) {
if (被岩石击碎) {
// 索赔...
}
} else if (在加利福尼亚) {
if (被岩石击碎) {
// 不索赔...
}
}
而且保险规则可不只这一条,到时候写出来就是这样:
if (在纽约) {
if (被岩石击碎) {
// 索赔...
}
if (被陨石击碎) {
// 索赔...
}
if (被流星击碎) {
// 索赔...
}
// more and more...
} else if (在加利福尼亚) {
if (被岩石击碎) {
// 不索赔...
}
if (被陨石击碎) {
// 不索赔...
}
if (被流星击碎) {
// 不索赔...
}
// more and more...
}
可以看到,我们把索赔规则的代码耦合到了索赔的核心系统中:
这会带来两个问题:
修改索赔规则需要重新发布整个系统
索赔规则的改动可能会影响整个系统,甚至导致整个系统不可用
于是我们把这些规则抽取出来,有个专门的地方去管理这些规则,简单说,就是「解耦」:
这样就解决了「耦合度高」的问题,但其实解耦的还不够彻底。不同州的规则还是放到一起的,而我们在索赔处理的时候,每次只需要加载一个州的索赔规则,不存在既需要纽约州的规则,又需要加州规则的情况: 另外,如果后面新来了一个州,想接入索赔系统,那么如何让这个州,在不影响其他州的情况下,配置自己的索赔规则?于是有了这样一套保险索赔的「微内核架构」: 简单说就是,这套系统分两个模块:1、中间的核心模块处理保险索赔的基本业务逻辑;2、保险规则由每个州自己去实现,做成插件,可以被单独加载和移除,不影响核心系统和其他插件。了解完了保险索赔系统的微内核实现,我们再来看看微内核架构,到底是什么。什么是微内核架构
Oreilly 对于微内核架构的定义是这样的(纯英文,大家要再三细品):
The microkernel architecture pattern consists of two types of architecture components: a core system and plug-in modules.简单说,就是微内核架构包含两个组件:核心系统(core system)和插件模块(plug-in modules),目的是为了扩展性、灵活性和隔离性。核心系统和插件模块又都有什么职责呢?
Application logic is divided between independent plug-in modules and the basic core system, providing extensibility, flexibility, and isolation of application features and custom processing logic.
The core system of the microkernel architecture pattern traditionally contains only the minimal functionality required to make the system operational.核心系统只包含让系统可以运作的最小功能,有点像 MVP(Minimum Viable Product ,最小可用产品)。而插件模块,则包含一些特殊处理逻辑、额外的功能、自定义代码,用于强化和扩展核心系统,提供更多的业务能力。这么讲还是比较抽象,所以,接下来,进入主题,来看看 Dubbo 这个 RPC 框架,是如何基于微内核架构进行设计的。什么是 RPC 系统的 core?
The plug-in modules are stand-alone, independent components that contain specialized processing, additional features, and custom code that is meant to enhance or extend the core system to produce additional business capabilities.
Dubbo 在本质上是在解决如何进行远程调用(rpc)的问题,通常一个 rpc 系统都长这个样子:
但是这些都是一个 rpc 系统所必须的吗?能不能去掉哪个模块后,依然可以进行 rpc 调用?相信大多数人都可以发现,stub 层是可以去掉的,去掉后,无非你就没法再进行透明式调用罢了。还有吗?还有其他哪个模块也可以去掉吗?我们来看一个极简的 rpc 调用: 在这个例子里,服务提供方,和服务消费方,是位于同一块内存的:- 服务提供方,暴露服务时,只需要把自己注册到一个 map 里
- 服务消费方,引用服务时,则只需从 map 里获取到服务提供方的引用
- 当服务消费方调用服务提供方的方法时,其实是一次本地内存调用,不涉及什么网络传输、协议转换、序列化、反序列化
我们从系统的角度,看看 Dubbo 的整体设计图:
这个是一种传统的分层视角,每一层都有自己要解决的问题,用 DDD 的话来说,就是每个域都有自己的问题空间:- proxy 层,解决的是:如何实现服务接口的透明代理;
- cluster 层,解决的是:当有多个服务提供者时,如何调用、如何负载均衡等等;
- 底下三层,也就是 remote 层,解决的是:如何进行远程调用;
- …
- 当你只需要一次 injvm 的 rpc 调用时,只用 Protocol ,足矣;
- 如果你需要远程调用,而且有多个服务提供方,那需要引入 remote、cluster 和 registry;
- 如果你还需要透明式的 rpc 调用,那就再引入 proxy 层
Protocol 层,其实就是上面提到的,一个最简化的 rpc 模型:
三个角色:- exporter:对应服务提供方,负责把服务暴露到某个地方
- invoker:对应服务消费方,从某个地方引用服务,并调用服务
- protocol:使用什么样的 protocol,决定了会有什么样的 invoker 和 exporter
injvm
协议,那就会生成 InjvmInvoker 和 InjvmExporter:
而如果你采用的是 dubbo
协议,则会生成 DubboInvoker 和 DubboExporter:
仔细看代码,你会发现,dubbo
协议的 refer 方法,会把 invoker 放进一个 invokers 集合里,injvm
协议的 refer 方法,则直接 new 一个 invoker 后就返回了,说明前者是有可能存在多个服务提供者的,而后者只会有一个。
而这些细节上的差异,追溯到根源,就是你用了什么样的协议(protocol)。
在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用。—— from Dubbo 框架设计微内核架构的其他使用
除了上面提到的保险索赔、Dubbo,微内核架构还被用到很多地方。
其实微内核架构的起源,是操作系统: 左边是 Microkernel,右边是与之对应的 Monolithic Kernel,前者只提供最最基础的操作系统能力,而把更多的能力开放给外界来提供,而后者则倾向于提供一个大而全的操作系统。这里不展开讲,大家可以前往维基百科了解下。后来,这种思想逐渐被演变成一种架构设计模式,于是有了「微内核架构」。它被用在了许多客户端应用,像 Chrome 浏览器: Eclipse 编辑器: Chrome 核心就是一个浏览器,用来浏览网页。你可以给它添加各种各样的插件,像翻译插件、广告屏蔽插件等等;而对于第三方开发者,则可以给它开发各种插件。
Eclipse 也一样,核心就是一个编辑器,和记事本没什么区别,给它添加各种各样的插件,像代码高亮、java 代码编译等等,就成了一个好用的开发工具;第三方开发者同样可以给它开发各种插件。之后它又被进一步用在了一些软件框架、业务系统上,比如今天讲到的 Dubbo 和保险索赔系统。甚至在之后的「六边形架构」、DDD 上,都可以看到「微内核架构」的影子,这两种设计思想被大量用到各种框架、中间件的设计上,比如有赞的 MAXIM 全链路压测引擎: 你可以用一两句话概况这种思想,比如:开闭原则、模板模式、把不变的和变化的隔离等等,但是仅仅通过这种标签式的、高浓缩的、刻板印象的语言就来概况它,未免还是太过缺乏细节和激情了。微内核架构的优缺点
作为一种架构设计的模式,通常都会考虑这些问题:
- 如何降低系统的复杂度
- 如何提高系统的可维护性
- 如何提高系统的可扩展性
- 如何提高系统的可配置性
- 核心系统和插件是低耦合的,插件可插拔
- 核心系统和插件之间是隔离的,改变也是隔离的
- 核心系统可以保持稳定
- 插件可支持动态添加(热部署)
- 插件可以独立测试
- 大部分实现都是基于产品的(product based),实现时不会考虑高可伸缩性,当然这同样取决于你的实现方式。
- 微内核架构需要深思熟虑的设计和契约的规划管理,因此实现起来比较复杂。
- 契约的版本机制、插件的注册机制、插件的粒度、插件连接方式的选择都使得实现起来是复杂的。
授人以鱼不如授人以渔,其实这句古话是有出处的:
临河而羡鱼,不如归家织网。—— 《淮南子·说林训》人类历史上迸发过许多璀璨的思想,就像微内核架构其实来源于操作系统的微内核。我们在回过头去看的时候,可以嘲笑他们有些观念落伍了,但不要忘了一件事,慢一点,再慢一点,不要错过一些可能闪耀出来的那点星光。参考:
Microkernel Architecture
- Microkernel
- 微内核架构详解
- Dubbo 框架设计
- Dubbo 扩展点重构
各类知识点总结
下面的文章都有对应的原创精美PDF,在持续更新中,可以来找我催更~
- 92页的Mybatis
- 129页的多线程
- 141页的Servlet
- 158页的JSP
- 76页的集合
- 64页的JDBC
- 105页的数据结构和算法
- 142页的Spring
- 58页的过滤器和监听器
- 30页的HTTP
- 42页的SpringMVC
- Hibernate
- AJAX
- Redis
- ......
扫码或者微信搜Java3y 免费领取原创思维导图、精美PDF。在公众号回复「888」领取,PDF内容纯手打有任何不懂欢迎来问我。
原创电子书
原创思维导图