Wikipedia上微内核(Microkernel)的定义
In computer science, a microkernel (also known as μ-kernel) is the near-minimum amount of software that can provide the mechanisms needed to implement an operating system (OS). These mechanisms include low-level address space management, thread management, and inter-process communication (IPC). If the hardware provides multiple rings or CPU modes, the microkernel may be the only software executing at the most privileged level, which is generally referred to as supervisor or kernel mode. Traditional operating system functions, such as device drivers, protocol stacks and file systems, are typically removed from the microkernel itself and are instead run in user space.
wikipedia上的定义特指这是一种操作系统内核设计风格,其对标的内核设计风格是monolithic kernel。
本文提到的微内核
本文讲到的微内核架构(Microkernel architecture)更宽泛一些,不局限于操作系统内核设计问题域,是一种设计范型(design paradigm),更接近于《Software Architecture Patterns》一书中所写:
The microkernel architecture pattern allows you to add additional application features as plug-ins to the core application, providing extensibility as well as feature separation and isolation. The microkernel architecture pattern consists of two types of architecture components: a core system and 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.
微内核架构由两大架构模块组成:核心系统
与插件模块
。设计一个微内核体系关键工作全部集中于核心系统怎么构建。
所有的软件存在的目的都是为了去解决某个现实世界中具体领域的问题,简称问题域。比如dubbo的问题域是服务化与服务治理、maven的问题域是编译打包与软件项目管理。
如果某个问题域发现有如下特征,就可以考虑使用微内核设计思想:
- 问题域能够沉淀一层比较核心的概念、流程或功能,这些元素可以被稳定维护在一个核心之中;
- Maven将代码编译打包场景定义为三套生命周期:
clean
、default
、site
;其核心的default
生命周期中20多个编译步骤将问题域进行了高度抽象;Maven的plugin(又名mojo)在定义时都需要将自己挂载到某个goal和step上; - Dubbo将SOA调用高度抽象为20余个核心SPI,这些SPI又类似协议栈分层的设计细分为核心的7、8个层次(
proxy
、cluster
、protocol
等);dubbo将自己主要的SOA服务调用功能实现都定义为这些SPI的具体扩展实现(plugin);有了这些抽象的SPI,plugin也就有了依附的基础;
- Maven将代码编译打包场景定义为三套生命周期:
- 问题域有开放封闭的迫切需求,其中封闭的部分、可扩展部分分别由不同的团队、工程来维护与组织。比如:
- Framework的实现封闭 vs. 依赖Framework的应用扩展开放,比如dubbo这样的中间件设计场景;
- 操作系统核心实现封闭 vs. 操作系统应用层开放,比如所有微内核操作系统设计场景;
- 平台级业务系统核心逻辑实现封闭 vs. 具体业务系统扩展开放,比如阿里中台核心平台系统的设计场景;
上面的两个问题域特征,刚好带出了在进行微内核架构核心系统设计时的两个关键点:
- 对问题域的核心概念、流程、功能的洞察与抽象;有了这些核心元素,plugin的扩展才能有所依附、与其代码之间的互动才能实际落地发生;
- 设计一套机制用于规范和管理plugin生命周期:定义、加载、销毁等;
Dubbo架构概要介绍
Dubbo主要解决了服务化架构中的几个关键问题:
- 远程调用(RPC)
- 解决远程的进程到进程的调用;
- 集群逻辑(cluster invoke cluster)
- 更进一步,解决服务集群到服务集群的调用问题,例如软负载机制;
- 服务发现与服务治理(Registry / Governance)
- 集中解决服务治理所需要的基础功能:服务的注册与发现、注册中心里的基础服务治理功能
采用了类似协议栈的分层设计,归纳下来主要分为三层:
- Service层 (Service/Config/Proxy)
- 解决provider侧服务暴露 / consumer侧消费服务的问题
- 集群层(Registry / Cluster)
- 解决服务注册与发现、集群调用策略领域关键问题
- RPC层(Protocol / Exchange/ Transport / Serialize)
- 解决点到点的同步远程调用领域的问题
这主要的三层符合分层架构风格的特征,即: 上层逻辑无需关注下层实现细节。 这个特征使得dubbo的扩展方可以采用类似搭积木的方式进行扩展。比如:彻底更换RPC协议,而共享上层的集群调用与服务治理实现; 更多的奥妙就不在此文展开,感兴趣的同学可以仔细研究下图(摘自Dubbo官方文档),内涵与细节非常丰富:
Dubbo与微内核架构
Dubbo为什么会使用微内核架构?最直观的原因:为了推广方便
Dubbo在设计之初,正值Alibaba B2B进行服务化转型的关键时期。所要推广的应用系统要么还处于“恐龙级单体”应用状态;要么用“土办法”解决简单的集群间调用。
想要顺利推广,得具备这几个关键特征:
- 性能好
- 在一次服务调用中,框架所占用的资源和时间要缩小到对应用层基本可以忽略的程度;
- 鲁棒性好
- 各种设计细节都需要兜底和防呆,避免因为一些次要的原因,导致整个应用系统崩溃(最经典的案例就是因为注册中心bug导致服务的提供者被全部剔除恶性事件了,其中各种心酸,具体的心得可以另开一篇文章专门探讨);
- 引入的依赖少
- 最小化对业务代码的侵入:可以做到应用容器无关(不同的web容器、homemade应用容器都不影响使用)、框架无关(不强依赖Spring)
- 可扩展性好(能做到开放封闭)
- 依赖于自己抽象的SPI以及plugin加载机制,能做到应用方非常自由地通过SPI方式插入自己想要的逻辑,而不需要修改Dubbo本身
最后这一项,就是Dubbo采用微内核设计的主要原因: dubbo所要支持的应用系统千差万别,在一个组织中推行服务化,dubbo需要面临诸多的扩展需求,举几个场景:
-
场景一:
- 遗留系统是Python写的单体应用,想要用Java来进行领域拆分改造,有一些RPC调用的场景,老系统采用Rest + VIP的方式进行远程调用;
-
场景二:
- 用户想要实现自己的分布式调用跟踪,在这个基础之上建立自己的运维工具体系。
-
场景三:
- 希望在SOA的调用链条上插入自己的filter逻辑,去实现调用审计的需求;
还有很多不一一列举。作为一个开源框架,尝试将所有上面的需求都不加区分的在框架内实现一定是不可取的,众口难调。
如何做到干好自己的活儿,又不挡用户的道儿呢?
答案不言自明。
Dubbo的扩展点实现举例
以扩展实现Filter SPI为例。
Filter SPI 定义:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.SPI;
/**
* Filter. (SPI, Singleton, ThreadSafe)
*/
@SPI
public interface Filter {
/**
* do invoke filter.
* <p>
* <code>
* // before filter
* Result result = invoker.invoke(invocation);
* // after filter
* return result;
* </code>
*
* @param invoker service
* @param invocation invocation.
* @return invoke result.
* @throws RpcException
* @see org.apache.dubbo.rpc.Invoker#invoke(Invocation)
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
复制代码
filter是链式组装的,要实现自己的filter逻辑,只需要实现下面的invoke接口即可。filter的组装需要注意官方文档中记录的约定:
- 用户自定义 filter 默认在内置 filter 之后。
- 特殊值 default,表示缺省扩展点插入的位置。比如:filter="xxx,default,yyy",表示 xxx 在缺省 filter 之前,yyy 在缺省 filter 之后。
- 特殊符号 -,表示剔除。比如:filter="-foo1",剔除添加缺省扩展点 foo1。比如:filter="-default",剔除添加所有缺省扩展点。
- provider 和 service 同时配置的 filter 时,累加所有 filter,而不是覆盖。比如:<dubbo:provider filter="xxx,yyy"/> 和 <dubbo:service filter="aaa,bbb" />,则 xxx,yyy,aaa,bbb 均会生效。如果要覆盖,需配置:<dubbo:service filter="-xxx,-yyy,aaa,bbb" />
扩展配置
<!-- 消费方调用过程拦截 -->
<dubbo:reference filter="xxx,yyy" />
<!-- 消费方调用过程缺省拦截器,将拦截所有reference -->
<dubbo:consumer filter="xxx,yyy"/>
<!-- 提供方调用过程拦截 -->
<dubbo:service filter="xxx,yyy" />
<!-- 提供方调用过程缺省拦截器,将拦截所有service -->
<dubbo:provider filter="xxx,yyy"/>
复制代码
XxxFilter.Java
package com.xxx;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;
public class XxxFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// before filter ...
Result result = invoker.invoke(invocation);
// after filter ...
return result;
}
}
复制代码
扩展实现Jar包Maven 项目结构:
src
|-main
|-java
|-com
|-xxx
|-XxxFilter.java (实现Filter接口)
|-resources
|-META-INF
|-dubbo
|-com.alibaba.dubbo.rpc.Filter (纯文本文件,内容为:xxx=com.xxx.XxxFilter)
复制代码
META-INF/dubbo/com.alibaba.dubbo.rpc.Filter:
xxx=com.xxx.XxxFilter
复制代码
xxx就是com.xxx.XxxFilter全限定名的别名了,它会出现在dubbo的provider或者consumer的配置文件中,dubbo会按需加载组装。
按照这样的方式定义其他的扩展点,以此类推,运行时dubbo会把自带的、以及应用自己扩展的实现全部加载进来,如下图所示(假设该应用还扩展了LoadBalance以及Protocol另外两个扩展点):
Dubbo扩展点加载机制实现
扩展点加载
截取自dubbo官方文档《开发者指南-扩展点加载》, Dubbo的扩展点加载由JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来,主要针对这三个缺点:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
扩展点加载源代码位于dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
扩展点装配
ServiceConfig.export()
和ReferenceConfig.refer()
是dubbo bootstrap时组装运行时扩展点的关键入口,可以根据代码顺藤摸瓜。看代码时可能会被各种xxxConfig搞晕,可以参考dubbo官方文档《开发者指南-实现细节》
写在后面
对于Dubbo来讲,本文中描述的微内核机制足够使用了。但对于上文中提到过的中台业务系统而言,仅仅依靠核心系统+plugin加载机制
又远远不够。中台业务系统面临的是更为严苛的工程挑战:
- 当扩展点SPI定义上千,如何治理?
- 如何做到复杂业务流程配置所见即所得?
- 中台系统修改之后,如何做到对现有业务系统不影响?
这些问题,阿里中台的星环系统都给出了自己答案,不得不佩服阿里中台战略坚定推进的勇气以及其实现者的智慧。
进群:可以领取免费的架构师学习资料。
进群:了解最新的学习动态
进群:了解最新的阿里,京东招聘资讯
进群:获取更多的面试资料
1、具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加群。
2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加群。
3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加群。
4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加群。
5. 群号:835638062 点击链接加入群:https://jq.qq.com/?_wv=1027&k=5S3kL3v
6.阿里Java高级大牛直播讲解知识点,分享知识,上面五大专题都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!
有疑问加站长微信联系(非本文作者)