开发插件:使用插件扩充Kree4X
Created By RV, and licensed with Creative Commons "CC BY-NC-ND 4.0"
目的与场景
Kree4X提供了各种Ports、Providers机制,允许对Kree4X进行动态配置及能力增强。
而 usePlugin() 插件机制,则允许将各种配置代码,集结在一个独立的Plugin插件中,以Plugin为单元进行动态扩展管理。
这一章,将讲解如何开发、封装插件Plugin,并将其注入Kree4X,并动态管理其生命周期。
一. 概念
1. 插件:Plugin
插件,是一个实现了下述3个生命周期钩子函数的普通对象,将其注入Kree4X后,会被Kree4X在合适的时间节点调用,完成插件的初始化、启动、停止:
config(kreex):usePlugin()注册时立即执行,提供装配能力:注册服务、挂拦截器、订阅事件start():kreex.start()时自动调用,插件可在此处完成自身的启动及资源分配、初始化stop():kreex.stop()时自动调用,插件可在此处完成自身的停止及资源清理、释放
config() ,必须提供;start() / stop() 可选。
2. 插件能做什么
plugin.config(kreex) 被调用时,使用传入的 kreex 节点实例完成各种静态初始化。
例如:
kreex.register(name, obj):注册服务kreex.useInInterceptor(...)/kreex.useOutInterceptor(...):挂服务拦截器kreex.transport.grid.on(...):订阅网格事件,感知节点动态加入/离开kreex.ports:使用Kree4X Ports API,扩展节点能力
3. 生命周期时序
kreex.usePlugin(plugin) → 立即执行 plugin.config(kreex)
kreex.start() → 自动执行 plugin.start()
kreex.stop() → 自动执行 plugin.stop()
插件 start() 抛错不会阻断节点启动(仅告警),插件内部须考虑自身启动失败的容错处理。
二. 示例代码
在下边的示例中,我们将编写节点状态上报插件 StatusPlugin,演示三个扩展点:
config:注册status服务,任何节点都能远程查询本节点状态config:挂入站拦截器,打印所有到达本节点的调用start/stop:订阅/退订网格事件,感知其他节点的动态加入
1. 定义插件
// 插件:节点状态上报
class StatusPlugin {
constructor (label) {
this._label = label
}
// 静态配置
config (kreex) {
this._kreex = kreex
// 扩展点1:注册服务,远端节点可直接调用查询本节点状态
kreex.register('status', {
status: () => ({
node: kreex.name,
description: kreex.description,
uptimeSec: Math.round((Date.now() - (this._startedAt ?? Date.now())) / 1000)
})
})
// 扩展点2:入站拦截器,观察所有到达本节点的调用
kreex.useInInterceptor(new StatusLogInterceptor(this._label))
}
// 插件启动
start () {
const grid = this._kreex.transport.grid
this._startedAt = Date.now()
// 扩展点3:订阅网格事件,感知节点动态加入/离开。
this._nodeAddedHandler = (nodeId) => this._onNodeAdded(nodeId)
this._nodeRemovedHandler = (nodeId) => this._onNodeRemoved(nodeId)
grid.on(GridEvent.NodeAdded, this._nodeAddedHandler, this)
grid.on(GridEvent.NodeRemoved, this._nodeRemovedHandler, this)
}
// 插件停止
stop () {
const grid = this._kreex.transport.grid
grid.off(GridEvent.NodeAdded, this._nodeAddedHandler, this)
grid.off(GridEvent.NodeRemoved, this._nodeRemovedHandler, this)
}
_onNodeAdded (nodeId) {
logger.info(`[插件${this._label}] 网格节点加入: ${nodeId}`)
}
_onNodeRemoved (nodeId) {
logger.info(`[插件${this._label}] 网格节点离开: ${nodeId}`)
}
}
2. 注册插件并运行
// node-a:挂载插件,监听TCP
const nodeA = Kree4N.create('node-a', '主机A')
// 注入插件
nodeA.usePlugin(new StatusPlugin('A'))
// node-a 独占注册 greet 服务(StatusPlugin 的 status 每个节点都有,greet 只有 node-a 有)
nodeA.register('greet', {
hello (name) {
return `Hello ${name}`
}
})
nodeA.listen(`tcp://127.0.0.1:8130`)
await nodeA.start()
// node-b:同样挂载插件,挂接node-a
const nodeB = Kree4N.create('node-b', '主机B')
// 注入插件
nodeB.usePlugin(new StatusPlugin('B'))
nodeB.attach(`tcp://127.0.0.1:8130`)
await nodeB.start()
// 1. 调用 status:每个节点都装了 StatusPlugin,service() 按集群选择目标节点(可能落在本地)
const status = await nodeB.service('status').status()
// 2. 调用仅 node-a 注册的 greet:唯一持有者必路由到 node-a(远程),其入站拦截器会打印
const greeting = await nodeB.service('greet').hello('reader')
// node-a 的插件会打印:收到调用: greet.hello
// 3. node-c 加入网格
const nodeC = Kree4N.create('node-c', '临时节点')
nodeC.attach(`tcp://127.0.0.1:8130`)
await nodeC.start()
// node-a/node-b 的插件会打印:网格节点加入: <node-c的id>
三. 须强调的细节
1. config() 在注册时立即执行,usePlugin() 必须在 start() 之前调用
kree4X.usePlugin() 内部,会立即执行 plugin.config(kreex)。
先 start() 再 usePlugin(),插件的start()生命周期方法,不会被调用。
2. plugin.start() 抛错不阻断节点启动
KreeX 对插件 start() 失败只告警(Plugin start failed),继续启动其余插件和节点。
插件内部,应提供恰当的容错处理。
3. plugin.stop() 是清理资源的地方
kreex.stop() 会逐个调用插件的 stop()。
应在 plugin.stop(),完成事件退订、定时器关闭等清理工作。
四. 涉及到的API:
1. Kree4X节点注册插件
/**
* 注册一个插件,配置并增强此KreeX节点。
* 插件必须实现 config(kreex);
* start()/stop() 可选,由KreeX在启动/停止时自动调用。
*
* @param {{
* config: (kreex: KreeX) => void,
* start?: () => void,
* stop?: () => void
* }} plugin - 要注册的插件。
* @returns {KreeX} 当前节点实例,支持链式调用。
*/
usePlugin(plugin)
2. 网格事件常量
const GridEvent = {
NodeAdded: 'node-added', // 节点被网格发现
NodeUpdated: 'node-updated', // 节点信息更新(如TTL刷新)
NodeRemoved: 'node-removed', // 节点从网格移除(信标失联判定)
AdjacentAdded: 'adjacent-added',// 相邻节点直连建立
AdjacentRemoved: 'adjacent-removed' // 相邻节点直连断开
}
// 获取方式
const { GridEvent } = Kree4N.Constants
3. 网格事件订阅
/**
* 订阅网格事件。
*
* @param {string} eventName - 事件名,GridEvent 中的值。
* @param {(nodeId: string) => void} callback - 事件回调,参数为节点ID。
* @param {*} [owner] - 归属标识,用于按owner批量退订。
* @returns {Grid} 网格实例,支持链式调用。
*/
grid.on(eventName, callback, owner)
/**
* 退订网格事件。参数与 on() 一致。
*
* @param {string} eventName - 事件名。
* @param {(nodeId: string) => void} callback - 注册时的回调引用。
* @param {*} [owner] - 注册时的归属标识。
* @returns {Grid} 网格实例,支持链式调用。
*/
grid.off(eventName, callback, owner)
五. 可运行代码
完整示例代码,参见:01-plugin-development.mjs