您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

微内核插件架构从原理到实践

时间:2022-09-02 15:16:45  来源:今日头条  作者:互联网技术集中营

前言

最近一段时间一直在参与一些SaaS产品的设计,发现SaaS主产品有90%的功能基本都是通用性,其中10%各个租户都会出现定制化,比如一些页面表单字段的差异化、业务规则差异化以及流程差异化等,这些差异化如果软件架构的可扩展性不够,很容易出现后期维护成本非常高。其实技术发展到今天,软件架构设计有一个核心的理念一直没有不变,如何面的业务发展的不确定性快速实现,比如Nosql的出现为了弥补传统关系型数据库数据结构的不确定性,规则引擎的出现为了解决业务规则的不确定性。今天同样面对SaaS产品各个租户的需求不确定性以及差异化,让我很容易到微内核插件架构设计,微内核插件架构设计是一种非常典型的架构设计模式。

 

微内核架构本质上是为了提高系统的扩展性 。所谓扩展性,是指系统在经历不可避免的变更时所具有的灵活性,以及针对提供这样的灵活性所需要付出的成本间的平衡能力。也就是说,当在往系统中添加新业务时,不需要改变原有的各个组件,只需把新业务封闭在一个新的组件中就能完成整体业务的升级,我们认为这样的系统具有较好的可扩展性。

 

就架构设计而言,扩展性是软件设计的永恒话题。而要实现系统扩展性,一种思路是提供可插拔式的机制来应对所发生的变化。当系统中现有的某个组件不满足要求时,我们可以实现一个新的组件来替换它,而整个过程对于系统的运行而言应该是无感知的,我们也可以根据需要随时完成这种新旧组件的替换。

微内核插件架构

在正式介绍微内核插件架构之前,先介绍一下什么是内核以及内核分类。

百度百科是这样介绍内核:

内核,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。内核的分类可分为单内核和双内核以及微内核。

 

微内核(Micro kernel)是提供操作系统核心功能的内核的精简版本,它设计成在很小的内存空间内增加移植性,提供模块化设计,以使用户安装不同的接口,如 DOS、Workplace OS、Workplace UNIX 等。IBM、Microsoft、开放软件基金会(OSF)和 UNIX 系统实验室(USL)、鸿蒙 OS 等新操作系统都采用了这一研究成果的优点。

 

与微内核相对应的一个概念是宏内核,宏内核是包含很多功能的底层程序,干的事情很多,且不可插拔;一点微小的修改都可能会影响到整个内核,典型的”牵一发而动全身“。linux 就是宏内核,也因此被称为 monolithic OS。Linux除了时钟中断、进程创建与销毁、进程调度、进程间通信外,其他的文件系统、内存管理、输入输出、设备驱动管理都需要内核完成,其中的文件系统、内存管理、设备驱动等都被作为系统进程放到了用户态空间,属于可扩展插件部分。

 

微内核只负责最核心的功能,其他功能都是通过用户态独立进程以插件方式加入进来的,微内核负责进程的管理、调度和进程之间通讯,从而完成整个内核需要的功能。当某个功能出现问题时,由于该功能是以独立进程方式存在的,所以不会对其他进程有什么影响从而导致内核不可用,最多就是内核某一功能现在不可用而已。

 

微内核架构(Microkernel Architecture),也被称为插件式架构(plug-in architecture),作为一个在几十年前就被创建出来的架构模式,它如今仍然被广泛应用在各个领域中。从组成结构上讲, 微内核架构包含两部分组件:内核系统和插件 。这里的内核系统通常提供系统运行所需的最小功能集,而插件是独立的组件,包含自定义的各种业务代码,用来向内核系统增强或扩展额外的业务能力。

 

微内核架构由以下两部分组成:核心系统(core system)和插件(plug-in component),将应用系统的业务逻辑拆分成核心系统和插件,能够提供很好的可扩展性和灵活性,极大地方便了后续需求的新增和修改。

 

核心模块只拥有能使应用运行的最小功能逻辑。许多操作系统使用微内核系统架构,这就是该结构的名字由来。从业务应用的角度,核心系统通常定义了一般商务逻辑,不包含特殊情况、特殊规则、复杂的条件的特定处理逻辑。

 

插件模块是独立存在的模块,包含特殊的处理逻辑、额外的功能和定制的代码,能拓展核心系统业务功能。通常,不同的插件模块互相之间独立,但是你可以设计成一个插件依赖于另外一个插件的情况。最重要的是,你需要让插件之间的互依赖关系降低到最小,为避免繁杂的依赖问题。

常见的微内核插件架构案例

在Web浏览器领域,谷歌的Chrome浏览器之所以被认为功能强大,一个很重要的原因是它有着丰富的插件类型;在开发工具领域,微软的VS Code初始安装后还只是个简单的文本编辑器,但用户可以安装各种插件,从而让它摇身一变成为功能强大的IDE。

 

Chrome和VS Code以及Eclipse、IDEA都是微内核架构的典型应用例子,它们提供一个具备最基础能力的核心系统,并定义好插件的开发接口。至于需要开发或安装哪种类型的插件,则完全由普通开发者和用户决定,这样的设计让系统具备了极强的可定制化和可扩展能力。

 

常见的一些开源框架比如Dubbo、ShardingSphere、Skywalking以及Apache ShenYU网关都支持插件化架构,每个开源软件的微内核插件设计核心思想都是一致的,其具体技术实现略有不同。在dubbo中,SPI的使用几乎是疯狂。dubbo针对JAVA SPI的局限性,自己重写了一套加载机制。可以针对不同的需求使用不同的实现类以及提供一些高级特性。

 

阿里巴巴的星环TMF框架其核心思想也是基于微内核插件架构,TMF2.0框架改造的交易平台支持了淘宝、天猫、聚划、盒马、大润发等一系列集团交易业务,通过业务管理域与运行域分离、业务与业务的隔离架构,大幅度提高了业务在可扩展性、研发效率以及可维护性问题,同时以更好的开放模式,让业务方能自助进行无侵入的需求开发。

微内核插件架构设计

微内核插件架构包含两个核心组件:系统核心(Core System)和插件化组件(Plug-in component)。Core System负责管理各种插件,当然Core System也会包含一些重要功能,如插件注册管理、插件生命周期管理、插件之间的通讯、插件动态替换等。整体结构如下:

 

微内核插件架构设计需要考虑如下四点:

插件管理

  • 核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。常见的实现方法是插件注册表机制。
  • 核心系统提供插件注册表(可以是配置文件,也可以是代码,还可以是数据库),插件注册表含有每个插件模块的信息,包括它的名字、位置、加载时机(启动就加载,还是按需加载)等。

插件连接

  • 插件连接指插件如何连接到核心系统。通常来说,核心系统必须制定插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可。
  • 常见的连接机制有 OSGi(Eclipse 使用)、消息模式、依赖注入(Spring 使用),甚至使用分布式的协议都是可以的,比如 RPC 或者 HTTP Web 的方式。

插件通信

  • 插件通信指插件间的通信。虽然设计的时候插件间是完全解耦的,但实际业务运行过程中,必然会出现某个业务流程需要多个插件协作,这就要求两个插件间进行通信。
  • 微内核的核心系统也必须提供类似的通信机制,各个插件之间才能进行正常的通信。

 

事实上, Java中已经为我们提供了一种微内核架构的实现方式,就是JDK SPI。这种实现方式针对如何设计和实现 SPI 提出了一些开发和配置上的规范,ShardingSphere、Dubbo 使用的就是这种规范,只不过在这基础上进行了增强和优化。另外Java中提供了OSGI,也可以作为插件化架构设计,早期淘宝HSF组件中间版本做过OSGi的尝试,最终还是因为它的使用它的代价大于好处而放弃。

 

SPI

SPI主要用于框架设计。在框架设计中可能会有不同的实现类。如果所有的实现类都放入代码中的话,代码的耦合程度就太高了。SPI就是为了降低代码耦合度的,但是如果不知道SPI机制在看一些源码的时候就感觉云里雾里的。SPI简单来说就是使用配置文件来决定使用哪个实现类。将原来可能要在代码里面直接引用的实现类,写入到配置文件中,然后通过一个加载器去加载。

SPI优势很明显就是简单易用,如果对于只有一个实现类,比如JDBC。每个厂商都去实现但是每个厂商都只有自己一个实现类。针对这种上游接口Java SPI是合理的。

 

可以看到Java SPI是将文件里面的所有接口都去实现。在系统的实际开发中一个接口的实现类可能很多,在不同的场景下使用的也不一样。Java SPI这个时候就不太适用了。

 

如果有些实现类在运行时没有使用,并且加载比较繁琐,必然会耗费整个系统的资源。

 

OSGI

OSGI的全称是open services gateway initiative,是一个插件化的标准,而不是一个可运行的框架。

 

OSGI的优势主要如下几点:

1.可复用性强

OSGI框架本身可复用性极强,很容易构建真正面向接口的程序架构,每一个Bundle 都是一个独立可复用的单元。

 

2.基于OSGI的应用程序可动态更改运行状态和行为。

在OSGI框架中,每一个Bundle实际上都是可热插拔的,因此,对一个特定的Bundle进行修改不会影响到容器中的所有应用,运行的大部分应用还是可以照常工作。当你将修改后的Bundle再部署上去的时候,容器从来没有重新启过。这种可动态更改状态的特性在一些及时性很强的系统中比较重要,尤其是在Java Web项目中,无需重启应用服务器就可以做到应用的更新。

 

3.职责分离

基于OSGI框架的系统可分可合,其结构的优势性导致具体的Bundle不至于影响到全局,不会因为局部的错误导致全局系统的崩溃。例如Java EE项目中可能会因为某个Bean的定义或注入有问题,而导致整个应用跑不起来,而使用OSGI则不会有这种问题,顶多相关的几个Bundle无法启动。

 

OSGI同样也有一些缺点:

1.类加载机制

每个Bundle都由单独的类加载器加载,与一些Java EE项目中使用比较多的框架整合比较困难,如Spring MVC、Struts2等,例如笔者尝试在OSGI应用中整合Spring MVC时,通过DispatcherServlet启动的Bean与OSGI Bundle启动的Bean无法相互依赖,需要做特殊处理,后面文章中会有介绍。

 

2.OSGI框架提供的管理端不够强大

现在的管理端中仅提供了基本的Bundle状态管理、日志查看等功能,像动态修改系统级别的配置(config.ini)、动态修改Bundle的配置(Manifest.mf)、启动级别等功能都尚未提供,而这些在实际的项目或产品中都是非常有必要的。

 

3.使用成本高

采用OSGI作为规范的模块开发、部署方式自然给现有开发人员提出了新的要求,需要学习新的基于OSGI的开发方式。

微内核插件架构设计

1.定义扩展点注解

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Extension {


    String tenantId() default "0";//根据SaaS租户ID进行隔离


    int ordinal() default 0;
  
    Class<? extends ExtensionPoint>[] points() default {};


    String[] plugins() default {};


}

2.定义业务扩展点

扩展点表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明。

public interface ExtensionPoint {


}

3.插件实体定义

主程序定义插件类Plugin,用于封装从插件配置文件读取业务扩展实现,具体定义如下:

@Data
public class Plugin {
    /**
     * 插件名称
     */
    private String pluginId;
    
    /**
     * 插件版本
     */
    private String version;
    /**
     * 插件路径
     */
    private String path;
    /**
     * 插件类全路径
     */
    private String className;
    
}

4.插件管理

创建插件管理类,初始化插件。

/**
 * 使用URLClassLoader动态加载jar文件,实例化插件中的对象
 *
 */
public abstract class PluginManager {
    
    private Map<String, Class> clazzMap = new HashMap<>();
 
    public PluginManager(List<Plugin> plugins) throws PluginException {
        init(plugins);
    }
    
    /**
     * 插件初始化方法
     */
    private void init(List<Plugin> plugins) throws MalformedURLException {
        try{
            int size = plugins.size();
            for(int i = 0; i < size; i++) {
                Plugin plugin = plugins.get(i);
                String filePath = plugin.getPath();
                // URL url = new URL("file:" + filePath);
                URL url = new File(plugin.getPath()).toURI().toURL();
                URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
                Class<?> clazz = urlClassLoader.loadClass(plugin.getClassName());
                clazzMap.put(plugin.getClassName(), clazz);
            }
        }catch (Exception e) {
            throw new PluginException("plugin " + plugin.getPluginName() + " init error," + e.getMessage());
        }
    }
    
    /**
     * 获得插件
     * @param className 插件类全路径
     * @return
     * @throws PluginException
     */
    public ExtensionPoint getInstance(String className) throws PluginException {
       
        // 插件实例化对象,插件都是实现ExtensionPoint接口
        Class clazz = clazzMap.get(className);
        Object instance = null;
        try {
            instance = clazz.newInstance();
        } catch (Exception e) {
            throw new PluginException("plugin " + className + " instantiate error," + e.getMessage());
        }
        return (ExtensionPoint)instance;
    }


    
    PluginState startPlugin(String pluginId);


    /**
     * 停止所有插件
     */
    void stopPlugins();




    /**
      * 停止插件
     */
    PluginState stopPlugin(String pluginId);


    /**
     * 卸载所有插件
     */
    void unloadPlugins();


    /**
      *卸载插件
     */
    boolean unloadPlugin(String pluginId);
}

5.测试

public class MAIn {
    
    public static void main(String[] args) {
        try {
            // 从配置文件加载插件
            List<Plugin> pluginList = PluginLoader.load();
            // 初始化插件管理类
            PluginManager pluginManager = new PluginManager(pluginList);
            // 循环调用所有插件
            for(Plugin plugin : pluginList) {
                ExtensionPoint extensionPoint = pluginManager.getInstance(plugin.getClassName());
                System.out.println("开始执行[" + plugin.getName() + "]插件...");
                // 调用插件
                extensionPoint.xxx();
                System.out.println("[" + plugin.getName() + "]插件执行完成");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

以上就是一个非常简版的基于扩展点的微内核插件,其设计思想和多数微内核插件架构相差无几,当然如果要用到生产还需要考虑很多问题,比如热部署问题、类隔离机制以及对于多语言的支持等。

 

关于Java的一些开源微内核插件架构也有一些开源实现:

SpringPlugin:https://Github.com/spring-projects/spring-plugin

p4fj:https://github.com/pf4j/pf4j

jspf:https://code.google.com/archive/p/jspf/

 

结合我们自己的一些业务场景,参考p4fj设计思想我也开发了一个微内核插件框架,解决了热部署问题、类隔离机制以及对于多语言等问题,主要应用于SaaS一些业务租户定制化需求,后面我也会考虑将其开源出来放到GitHub上,大家敬请关注。

总结

Robert C.Martin曾经说过,软件开发技术发展的历史就是一个如何想方设法方便地增加插件,从而构建一个可扩展、可维护的系统架构的故事。在敏捷开发的潮流之下,需求的变更如同家常便饭,系统不应该因为某一部分发生变更从而导致其他不相关的部分出现问题。将系统设计为微内核架构,就等于构建起了一面变更无法逾越的防火墙,插件发生的变更就不会影响系统的核心业务逻辑。

 

微内核架构的设计思想,能够极大提升系统的可扩展性和健壮性,在其他的一些软件方法论里,我们也隐约能看到它的影子。比如在领域驱动设计中,领域层就相当于核心系统,它定义了系统的核心业务逻辑;基础设施层则相当于插件,切换不同的基础设施并不会影响系统的业务逻辑,这得益于基础设施层依赖倒置的设计原则。

 

当然,作为微内核架构也有着一些缺点,它天然具备了单体架构的一些劣势,比如核心系统作为架构的中心节点并不具备Fault tolerance能力。因此,该架构模式往往被广泛应用于一些着重提供很强的用户定制化功能的小型产品,如VS Code等,它们对系统的Elasticity、Fault tolerance和Scalability并没有很高的要求。



Tags:微内核   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
我们聊聊DDD、SOA、微服务和微内核
DDD、SOA、微服务和微内核,看到经常有人把这几个概念拿出来一起讲。事实上,DDD和其他三个不是一个维度的东西。DDD其实特别好理解,DDD就是领域来驱动设计嘛,是一种设计思想。很...【详细内容】
2023-12-08  Search: 微内核  点击:(233)  评论:(0)  加入收藏
Kubernetes 微内核的分布式操作系统
如今,Kubernetes 已经成为分布式集群管理系统和公有云/私有云的事实标准。实际上,Kubernetes 是一个分布式操作系统,它是 Google 在分布式操作系统领域十余年工程经验和智慧的...【详细内容】
2023-08-21  Search: 微内核  点击:(153)  评论:(0)  加入收藏
聊聊【软件架构模式】—微内核架构
概述: 内核模式也被称为插件架构模式。 将附加应用程序功能作为插件添加到核心应用程序,以提供可扩展性以及功能分离和隔离。 这种模式由两种类型的架构组件组成:一个核心系统...【详细内容】
2023-06-20  Search: 微内核  点击:(292)  评论:(0)  加入收藏
微内核插件架构从原理到实践
前言最近一段时间一直在参与一些SaaS产品的设计,发现SaaS主产品有90%的功能基本都是通用性,其中10%各个租户都会出现定制化,比如一些页面表单字段的差异化、业务规则差异化以及...【详细内容】
2022-09-02  Search: 微内核  点击:(608)  评论:(0)  加入收藏
HarmonyOS 微内核
鸿蒙开始于 2012 年,虽一开始定位于物联网方向,但到如今,已经发展为一款可兼容 Android 应用的跨平台操作系统. 最新的 2.0 的官方描述是: HarmonyOS 是新一代的智能终端操...【详细内容】
2021-06-18  Search: 微内核  点击:(988)  评论:(0)  加入收藏
什么是微内核架构设计?
简介: 作为一名Java程序员,相信同学们都听说过微内核架构设计,也有自己的理解。那么微内核是如何被提出来的?微内核在操作系统内核的设计中又有什么作用?本文从插件化(Plug-in)架...【详细内容】
2021-02-24  Search: 微内核  点击:(372)  评论:(0)  加入收藏
鸿蒙操作系统用的微内核到底是什么?
作者 | RT-Thread责编 | 伍杏玲【CSDN 编者按】安卓系统是宏内核,而最近热议的华为鸿蒙操作系统是微内核。什么是微内核?微内核是如何发展?其优缺点是什么呢?一起来看看吧。 背...【详细内容】
2019-06-28  Search: 微内核  点击:(927)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(12)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(18)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(118)  评论:(0)  加入收藏
站内最新
站内热门
站内头条