开发插件:使用插件扩充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

results matching ""

    No results matching ""