一、微内核架构简介
1. 1 微内核的概念
微内核架构(Microkernel Architecture),有时也被称为插件化架构(Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构,通常用于实现基于产品的应用。微内核架构模式允许你将其他应用程序功能作为插件添加到核心应用程序,从而提供可扩展性以及功能分离和隔离。
微内核架构模式包括两种类型的架构组件:核心系统(Core System)和插件模块(Plug-in modules)。应用逻辑被分割为独立的插件模块和核心系统,提供了可扩展性、灵活性、功能隔离和自定义处理逻辑的特性。
图中 Core System 的功能相对稳定,不会因为业务功能扩展而不断修改,而插件模块是可以根据实际业务功能的需要不断地调整或扩展。微内核架构的本质就是将可能需要不断变化的部分封装在插件中,从而达到快速灵活扩展的目的,而又不影响整体系统的稳定。
微内核架构的核心系统通常提供系统运行所需的最小功能集。许多操作系统使用的就是微内核架构,这也是它名字的由来。从商业应用程序的角度来看,核心系统一般是通用业务逻辑,没有特殊情况、特殊规则或复杂情形下的自定义代码。
插件模块是独立的模块,包含特定的处理、额外的功能和自定义代码,来向核心系统增强或扩展额外的业务能力。通常插件模块之间也是独立的,也有一些插件是依赖于若干其它插件的。重要的是,尽量减少插件之间的通信以避免依赖的问题。
1.2 微内核架构的优点
- 灵活性高:整体灵活性是对环境变化快速响应的能力。由于插件之间的低耦合,改变通常是隔离的,可以快速实现。通常,核心系统是稳定且快速的,具有一定的健壮性,几乎不需要修改。
- 可测试性:插件可以独立测试,也很容易被模拟,不需修改核心系统就可以演示或构建新特性的原型。
- 性能高:虽然微内核架构本身不会使应用高性能,但通常使用微内核架构构建的应用性能都还不错,因为可以自定义或者裁剪掉不需要的功能。
介绍完微内核架构相关的基础知识,接下来我们将以西瓜视频播放器为例,分析一下微内核架构在西瓜视频播放器中的应用。
二、西瓜视频播放器简介
西瓜视频播放器一款带解析器、能节省流量的 HTML5 视频播放器。它从底层解析 MP4、HLS、FLV 探索更大的视频播放可控空间。
(图片来源 —— http://h5player.bytedance.com/)
它的功能特色是从底层解析 MP4、HLS、FLV 探索更大的视频播放可控控件并拥有以下特点:
-
易扩展:灵活的插件体系、PC/移动端自动切换、安全的白名单机制;
-
更丰富:强大的 MP4 控制、点播的无缝切换、有效的带宽节省;
-
较完整:完整的产品机制、错误的监控上报、自动的降级处理。
上手西瓜视频播放器只需三步:安装、DOM 占位、实例化即可完成播放器的使用。
(图片来源 —— pingan8787)
西瓜视频播放器主张一切设计都是插件,小到一个播放按钮大到一项直播功能支持。 想更好的自定义播放器完成自己业务的契合,理解插件机制是非常重要的,播放器本身有很多内置插件,比如报错、loading、重播等,如果大家想自定义效果可以关闭内置插件,自己开发即可。
默认情况下插件是自启动的,如果自定义插件不想自启动或者不想改变播放器默认的执行机制,建议以继承播放器类的方式开发。为了实现 “一切设计都是插件” 的主张,西瓜视频播放器团队采用了微内核的架构,下面我们开始来分析一下西瓜视频播放器的微内核实践。
三、西瓜视频播放器微内核实践
微内核架构模式包括两种类型的架构组件:核心系统和插件模块。在西瓜视频播放器中核心系统是由 Player 类来实现,该类对应的 UML 图如下所示:
(https://github.com/bytedance/xgplayer/blob/master/packages/xgplayer/src/player.js)
而插件模块主要就是西瓜视频播放器中的各种内置插件,比如控制条的音量控制组件、播放器贴图、播放器画中画和播放器下载控件等,除了上面提到的插件之外,目前西瓜视频播放器总共提供了 22 个插件,完整的内置插件如下图所示:
(西瓜视频播放器内置插件)
对于微内核的核心系统设计来说,它涉及三个关键技术:插件管理、插件连接和插件通信。下面我们将围绕这三个关键点来逐步分析西瓜视频播放器是如何实现的。
3.1 插件管理
核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。常见的实现方法是插件注册表机制。核心系统提供插件注册表(可以是配置文件,也可以是代码,还可以是数据库),插件注册表含有每个插件模块的信息,包括它的名字、位置、加载时机(启动就加载,或是按需加载)等。
在分析西瓜视频播放器插件管理机制前,我们先来看一下 xgplayer/packages/xgplayer/src
目录结构:
├── control
│ ├── collect.js
│ ├── cssFullscreen.js
│ ├── danmu.js
│ ├── ....
│ └── volume.js
├── error.js
├── index.js
├── player.js
├── proxy.js
├── style
│ ├── index.scss
│ ├── ...
│ └── variable.scss
└── utils
├── animation.js
├── database.js
├── ...
└── util.js
通过观察以上目录结构,我们可以发现西瓜视频播放器的插件都统一存放在 control
目录下。那么现在问题来了,这些插件是如何被加载的?什么时候被加载?要回答这个问题,我们从该项目的入口出发:
// packages/xgplayer/src/index.js
import Player from './player' // ①
import * as Controls from './control/*.js' // ②
import './style/index.scss' // ③
export default Player // ④
从 index.js
文件中,我们发现在第二行代码中使用了 import * as Controls from './control/*.js'
语句批量导入播放器的所有内置插件。该功能是借助 babel-plugin-bulk-import 这个插件来实现的。
除了使用上述插件之外,还可以借助 Webpack context API 来实现,通过执行 require.context 函数获取一个特定的上下文,就可以实现自动化导入模块。在前端工程中,如果遇到从一个文件夹引入很多模块的情况,可以使用这个 API,它会遍历文件夹中的指定文件,然后自动导入模块,而不需要每次显式的调用 import 导入模块。
Webpack context API 的使用示例如下:
const contextRequire = require.context("./modules", true);
const modules = [];
contextRequire.keys().forEach((filename) => {
if (filename.match(/^\.\/[^_][\w/]*\.([tj])s$/)) {
modules.push(contextRequire(filename));
}
});
好的,回到正题。现在我们已经知道西瓜视频播放器的所有内置插件,都是通过 babel-plugin-bulk-import 这个插件在构建阶段完成加载的。如果不想使用播放器中的内置控件,可以通过ignores
配置项关闭,使用自己开发的相同功能插件进行替换:
new Player({
el