0. 博客迁移
也可访问 不恰饭的小站
文章目录
NodeRed 系统相关的配置文件会以 JONS 格式存储在本地,同时也提供了插件机制实现系统数据的自定义存储,本文从 Storage 模块出发,从源码分析下如何以插件形式实现自定义存储,也对这种方式的可能的用途进行分析。
1. NodeRed 系统数据有哪些
文件名称 | 说明 |
---|---|
package.json | nodered npm 信息 |
.sessions.json | 维护会话信息,每一个建立的会话都会记录 |
flows_[hostname].json | NodeRed 流程文件 |
flows_[hostname]_cred.json | 用以保存节点中以.credentials 保存的信息,加密与否可使用配置文件中 credentialSecret 参数配置 |
.config.runtime.json | 运行时配置 |
.config.users.json | 用户配置 |
.config.nodes.json | 节点配置 |
.config.projects.json | Git 配置 |
2. NodeRed 中系统数据存储机制
NodeRed 提供 storage 模块支持上述文件的读写。
2.1. 源码分析
以接口形式封装模块,模块的使用者只依赖接口,不依赖接口的实现,符合面向对象的依赖倒置原则(依赖倒置原则可以减少类间的耦合性,提高系统的稳定,降低并行开发引起的风险,提高代码的可读性和可维护性。)
- 以接口形式封装,具体的实现由动态加载的插件实现
getFlows: async function() { //====>此处为接口
return storageModule.getFlows().then(function(flows) { //====>此处getFlows为具体实现
return storageModule.getCredentials().then(function(creds) {
var result = {
flows: flows,
credentials: creds
};
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex");
return result;
})
});
},
- 在初始化时按配置文件选择要使用的存储模块
init: async function(_runtime) {
runtime = _runtime;
// Any errors thrown by the module will get passed up to the called
// as a rejected promise
storageModule = bmoduleSelector(runtime.settings); // ====>从配置项中加载模块
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
sessionsAvailable = storageModule.hasOwnProperty("getSessions") && storageModule.hasOwnProperty("saveSessions");
if (!!storageModule.projects) {
var projectsEnabled = false;
if (runtime.settings.hasOwnProperty("editorTheme") && runtime.settings.editorTheme.hasOwnProperty("projects")) {
projectsEnabled = runtime.settings.editorTheme.projects.enabled === true;
}
if (projectsEnabled) {
storageModuleInterface.projects = storageModule.projects;
}
}
if (storageModule.sshkeys) {
storageModuleInterface.sshkeys = storageModule.sshkeys;
}
return storageModule.init(runtime.settings,runtime);
},
- 在可能有越权行为的接口处增加检验,保证文件访问不会越权
...
function is_malicious(path) {
return path.indexOf('../') != -1 || path.indexOf('..\\') != -1;
}
getLibraryEntry: async function(type, path) {
if (is_malicious(path)) {
var err = new Error();
err.code = "forbidden";
throw err;
}
return storageModule.getLibraryEntry(type, path);
},
...
- 在有可能同时写文件(共同资源)时,做互斥操作
saveSettings: async function(settings) {
if (settingsAvailable) {
return settingsSaveMutex.runExclusive(() => storageModule.saveSettings(settings))
}
},
2.2. API
storage API 使用插件式配置 Node-RED 运行时存储数据
Function | Description |
---|---|
Storage.init(settings) | initialise the storage system |
Storage.getFlows() | get the flow configuration |
Storage.saveFlows(flows) | save the flow configuration |
Storage.getCredentials() | get the flow credentials |
Storage.saveCredentials(credentials) | save the flow credentials |
Storage.getSettings() | get the user settings |
Storage.saveSettings(settings) | save the user settings |
Storage.getSessions() | get the user sessions |
Storage.saveSessions(sessions) | save the user sessions |
Storage.getLibraryEntry(type,name) | get a type-specific library entry |
Storage.saveLibraryEntry(type,name,meta,body) | save a type-specific library entry |
3. NodeRed 中系统数据存储机制有哪些参与者
4. NodeRed 为什么这么设计,这种设计的优劣有哪些
以接口形式封装模块,模块的使用者只依赖接口,不依赖接口的实现,符合面向对象的依赖倒置原则(依赖倒置原则可以减少类间的耦合性,提高系统的稳定,降低并行开发引起的风险,提高代码的可读性和可维护性。)
6. 相关知识
- async-mutex:异步互斥
支持使用互斥量和信号量两种方式
-
mutex(互斥体)
术语“互斥体”通常是指一种用于同步不同线程上运行的并发进程的数据结构。例如,在访问非线程安全资源之前,线程会锁定互斥体。这保证阻塞线程,直到没有其他线程持有互斥锁,从而强制对资源的独占访问。一旦操作完成,线程释放锁,允许其他线程获取锁并访问资源。
虽然 Javascript 是严格的单线程,但其执行模型的异步特性允许需要类似同步原语的竞争条件。例如,考虑一个与 web worker 通信的库,为了完成一项任务,它需要与 worker 交换几个后续消息。因为这些消息是以异步方式交换的,所以在这个过程中很有可能再次调用这个库。根据异步过程中处理状态的方式,这将导致难以修复甚至更难跟踪的竞争情况。
这个库通过将互斥的概念应用于 Javascript 解决了这个问题。锁定互斥体将返回一个承诺,一旦互斥体变得可用,该承诺就会解决。一旦异步进程完成(通常需要事件循环的多次旋转),调用提供给调用者的回调以释放互斥体,允许下一个调度的工作线程执行。
-
semaphore(信号量)
想象一下,您需要控制对共享资源的几个实例的访问。例如,您可能希望在执行转换的几个工作进程之间分发图像,或者您可能希望创建一个 web crawler 来并行执行定义数量的请求。信号量是一种数据结构,它被初始化为一个正整数值,并且可以被多次锁定。只要信号量值为正,锁定它将返回当前值,锁定进程将立即继续执行;信号量将在锁定时递减。释放锁将再次增加信号量。
一旦信号量达到零,下一个试图获取锁的进程将被挂起,直到另一个进程释放它的锁,这将再次增加信号量。
这个库为 Javascript 提供了一个信号量实现,类似于上面描述的互斥体实现。
- 面向对象 6 大原则:依赖倒置
高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则是指模块间的依赖是通过抽象来发生的,实现类之间不发生直接的依赖关系,其依赖关系是通过接口是来实现的,这就是俗称的面向接口编程。
7. 应用场景分析
- 换一种存储格式(YAML)
有些库已经实现此功能 node-red-contrib-yaml-storage
Node-RED 将流文件存储为 JSON 文档。虽然 JSON 是轻量级和通用的,但它不是最容易阅读的格式。Node-RED 在函数、注释和模板节点中存储各种形式的代码,如 JavaScript、HTML、CSS、Markdown 等。这些代码块被表示为 JSON 结构中的一行,这使得在读取流文件时很难进行调试,并且会产生差异 - 按指定的接口将文件存储在服务端
有些库已经实现(@flowforge/nr-storage) - 做数据加密
可以将数据加密后存储到本地或远端,防止拷贝数据 - 远端存储并做授权认证
通过将数据存放在远端,可对用户访问进行授权,限定用户可操作的时间。