Skip to content

Portable 插件

作为对原生插件的补充,可移植插件旨在提供相同的功能,同时允许在更通用的环境中运行并由更多语言创建。与原生插件类似,可移植插件也支持自定义 源、目标 和功能扩展。

架构设计

与原生插件不同,Portable 插件运行时是独立于主程序的进程,其架构入下图所示。插件进程与主进程之间通过 nanomsg 进行通信。每个 Portable 插件可定义任意数量的 source, sink 和函数。插件安装或启动初始化时,主程序会拉起已安装的插件进程,建立控制通道连接(control channel)。当规则使用到插件中的 source/sink/function 时,主程序会建立对应的数据通道(图中的各种 data channel)并通过控制通道通知插件进程运行对应的 source/sink/function 实现。运行时的数据通过数据通道在主程序与插件之间传递。当规则停止时,主程序通过控制通道通知插件进程停止对应的 source/sink 运行,并关闭数据通道。

portable architecture

在 v2.0 及之后的版本中,为了避免难以处理的异步时序问题,系统不再采用插件 lazy load 的方式,插件安装/系统初始化之后即开始运行。已安装的插件会创建插件进程,建立控制通道且在系统运行期间一直保持,直到主程序关闭或者插件删除。source/sink 数据通道会跟随规则进行开关。函数可能是多个规则共用,隐藏数据通道打开后不会主动关闭,会一直运行直到系统关闭为止。

热更新

依托 nanomsg 通道的自动重连能力,Portable 插件支持不重启规则的热更新。插件更新后,使用插件中的 source/sink/function 的规则会自动使用新的版本实现。在内部实现中,插件更新时,插件进程会停止,但已创建的控制通道和数据通道服务端,即主程序端仍然保持。新的插件安装完成后,启动新插件进程即可自动连上原有的通道,从而实现规则不停机的插件更新。

开发

创建插件的步骤与原生插件类似

  1. 使用SDK开发插件。
    1. 通过实现相应的接口来开发每个插件符号(source、sink 和 function)
    2. 开发主程序,将所有交易品种作为一个插件提供服务
  2. 根据编程语言构建或打包插件。
  3. 通过 eKuiper 文件/REST/CLI注册插件

我们的目标是为所有主流语言提供插件. 当前, go SDK and python SDK 已经支持。

与原生插件不同,portable 插件可以捆绑多个 Symbol。每个 Symbol 代表源、Sink 或功能的扩展。一个符号的实现就是实现类似于原生插件的 source、sink 或者 function 的接口。在 portable 插件模式下,就是用选择的语言来实现接口。 然后,用户需要创建一个主程序来定义和服务所有的符号。启动插件时将运行主程序。开发因语言而异,详情请查看 go SDKpython SDK

调试

我们提供了一个 portable 插件测试服务器来模拟 eKuiper 主程序部分,而开发者可以手动启动插件端以支持调试。 您可以在tools/plugin_test_server 中找到该工具。它只支持测试单个插件测试过程。

  1. 编辑 testingPlugin 变量以匹配您的插件元数据。

  2. 启动此服务器,等待握手。

  3. 启动或调试您的插件。确保握手完成。

  4. 发出 startSymbol/stopSymbol REST API 来调试您的插件符号。 REST API 是这样的:

    shell
    POST http://localhost:33333/symbol/start
    Content-Type: application/json
    
    {
      "symbolName": "pyjson",
      "meta": {
        "ruleId": "rule1",
        "opId": "op1",
        "instanceId": 1
      },
      "pluginType": "source",
      "config": {}
    }

打包发布

开发完成后,我们需要将结果打包成zip进行安装。在 zip 文件中,文件结构必须遵循以下约定并使用正确的命名:

  • {pluginName}.json:文件名必须与插件主程序和REST/CLI命令中定义的插件名相同。
  • 插件主程序的可执行文件
  • source/sinks/functions 目录:按类别保存所有已定义符号的 json 或 yaml 文件

或者,我们可以打包其他支持文件,如 install.sh 和依赖项。

在json文件中,我们需要描述这个插件的元数据。该信息必须与插件主程序中的定义相匹配。下面是一个例子:

json
{
  "version": "v1.0.0",
  "language": "go",
  "executable": "mirror",
  "sources": [
    "random"
  ],
   "sinks": [
      "file"
   ],
   "functions": [
      "echo"
   ]
}

一个插件可以包含多个源、目标和函数,在 json 文件中的相应数组中定义它们。插件必须以单一语言实现,并在 language 字段中指定。此外, executable 字段需要指定插件主程序可执行文件。请参考 mirror.zip

使用Python插件时,用户可以通过指定以下属性为 Python 脚本指定一个虚拟环境。

  • virtualEnvType:虚拟环境类型,目前只支持 conda
  • env:要运行的虚拟环境名称。

详情请查看在虚拟环境运行

管理

通过将内容(json、可执行文件和所有支持文件)放在plugins/portables/${pluginName}中,并将配置放在etc 下的相应目录中,可以在启动时自动加载可移植插件。

要在运行时管理可移植插件,我们可以使用 RESTCLI 命令。通过状态 API,可以查看插件进程的进程 pid 等状态。

限制

目前,与原生插件相比,有两个方面的区别:

  1. 支持的 Context 方法较少,例如 State ,Connection API 暂不支持;动态参数解析需要开发者自行计算。而 state 计划在未来得到支持。
  2. 在函数接口中,参数不能通过AST传递,即用户无法验证参数类型。唯一支持的验证可能是参数计数。在 Sink 接口中,collect 函数的数据类型为 json 编码的 []byte,需要开发者自行解码。