介绍和背景
背景和目标
eKuiper 的主要目标是在边缘端提供一个流媒体软件框架(类似于 Apache Flink)
Ekuiper 规则引擎概述
基本原理和设计思想
LF Edge eKuiper 是物联网数据分析和流式计算引擎。它是一个通用的边缘计算服务或中间件,为资源有限的边缘网关或设备而设计
eKuiper 采用 Go 语言编写,其架构如下图所示:
使用 eKuiper,一般需要完成以下三个步骤。
- 创建流,就是你定义数据源的地方
- 写规则
- 为数据分析写 SQL
- 指定一个保存分析结果的目标
- 部署,并且运行规则
源从其他系统(如 EdgeX foundry)将数据输入到 eKuiper,这些系统被定义为流
应用优势
作为规则引擎,用户可以通过 REST-API 或 CLI 提交计算作业即规则。eKuiper 规则/SQL 解析器或图规则解析器将解析、规划和优化规则,使其成为一系列算子的流程,如果需要的话,算子可以利用流式运行时和状态存储
算子之间是松耦合的,通过 Go 通道进行异步通信。受益于 Go 的并发模型,规则运行时可以做到:
- 以异步和非阻塞的方式进行通信。
- 充分利用多核计算。
- 算子层可伸缩。
- 规则之间相互隔离。
这些有助于 eKuiper 实现低延迟和高吞吐量的数据处理。
应用案例和实际用途
快速上手
- 拉取镜像
从 https://hub.docker.com/r/lfedge/ekuiper/tags
拉取 eKuiper 的 Docker 镜像
- 设置 eKuiper 源为一个 MQTT 服务器。本例使用位于
tcp://broker.emqx.io:1883
的 MQTT 服务器,broker.emqx.io
是一个由 EMQ 提供的公有 MQTT 服务器
docker run -p 9081:9081 -d --name kuiper -e MQTT_SOURCE__DEFAULT__SERVER="tcp://broker.emqx.io:1883" lfedge/ekuiper:$tag
- 创建流(stream)- 流式数据的结构定义,类似于数据库中的表格类型定义。比如说要发送温度与湿度的数据到
broker.emqx.io
,这些数据将会被在本地运行的 eKuiper docker 实例中处理
- 以下的步骤将创建一个名字为
demo
的流,并且数据将会被发送至devices/device_001/messages
主题,这里的device_001
可以是别的设备,比如device_002
,所有的这些数据会被demo
流订阅并处理
-- In host
docker exec -it kuiper /bin/sh
-- In docker instance
-- 创建流
bin/kuiper create stream demo '(temperature float, humidity bigint) WITH (FORMAT="JSON", DATASOURCE="devices/+/messages")'
Connecting to 127.0.0.1:20498...
Stream demo is created.
bin/kuiper query
Connecting to 127.0.0.1:20498...
kuiper > select * from demo where temperature > 30;
Query was submit successfully.
- 使用 MQTT X 来发布传感器数据到服务器
tcp://broker.emqx.io:1883
的主题devices/device_001/messages
当且仅当如果我们输入的 temperature > 30 时才会显示对应的输出,< 30 的数据就会被自动过滤掉
使用 Ekuiper 管理控制台
以一个实际例子来说明如何使用管理控制台对 eKuiper 节点进行操作与管理。文中将订阅来自于 MQTT 服务器的数据,通过 eKuiper 写好的规则,经过处理后发送到指定的文件中,演示说明如下:
- 通过管理控制台创建一个 eKuiper 节点
- 创建一个流,用于订阅 MQTT 服务器中的数据,本例演示订阅 MQTT 服务器,相关信息如下所示。
- 地址为:
tcp://broker.emqx.io:1883
, - 主题为:
devices/device_001/messages
, - 数据为:
{"temperature": 40, "humidity" : 20}
- 地址为:
- 创建一个规则,用于计算订阅到的数据,并将数据写入目标 (sink) 端「本例演示将订阅到的消息写入到文件中」
- eKuiper 目前已经支持多种源和目标。用户只需安装相对应的插件,便能实现对应的功能「本例的源为 MQTT 源是内置支持,无需安装;目标为文件 (file)」
Ekuiper-manager 架构设计
- UI 端:可视化的界面,便于用户操作
- Kuiper-manager:管理控制台,本质是一个反向 HTTP 代理服务,提供用户管理,权限验证等服务。既可以部署在云端,也可以部署在边缘端
- eKuiper 实例,被管理的 eKuiper 节点实例,eKuiper-manager 可以同时管理多个 eKuiper 节点
使用 docker-compose 进行安装
services:
manager:
image: emqx/ekuiper-manager
container_name: ekuiper-manager
ports:
- "9082:9082"
restart: unless-stopped
environment:
# setting default eKuiper service, works since 1.8.0
DEFAULT_EKUIPER_ENDPOINT: "http://10.37.18.97:9081"
ekuiper:
image: lfedge/ekuiper
ports:
- "9081:9081"
- "10.37.18.97:20498:20498"
container_name: ekuiper
hostname: ekuiper
restart: unless-stopped
user: root
volumes:
- /tmp/data:/kuiper/data
- /tmp/log:/kuiper/log
environment:
MQTT_SOURCE__DEFAULT__SERVER: "tcp://broker.emqx.io:1883"
KUIPER__BASIC__CONSOLELOG: "true"
KUIPER__BASIC__IGNORECASE: "false"
注意上面高亮的地方换成自己部署 ekuiper 的主机地址
性能和扩展性
与EdgeX集成
运行容器
下载 docker-compose 文件 edgex-compose/docker-compose-no-secty.yml at main · edgexfoundry/edgex-compose
启动docker-compose容器
docker-compose -f ./docker-compose-no-secty.yml up -d --build
查看容器状态
docker-compose -p edgex stats
edgex 默认使用 redis 作为消息总线,我们进入到 redis 容器
订阅redis的所有channel查看消费的消息
PSUBSCRIBE edgex.events.*
输出结果如下;4) 即为我们设备产生的数据
1) "pmessage"
2) "edgex.events.*"
3) "edgex.events.device.device-virtual.Random-Float-Device.Random-Float-Device.Float64"
4) "{\"apiVersion\":\"v3\",\"receivedTopic\":\"\",\"correlationID\":\"a86e1157-4212-4a3a-8829-7dcd7c479f11\",\"requestID\":\"\",\"errorCode\":0,\"payload\":\"eyJhcGlWZXJzaW9uIjoidjMiLCJyZXF1ZXN0SWQiOiJiM2RlZTM0YS0yYWMxLTQ0OTQtYjVjMy1hZTI3MGQ5YTA4MDUiLCJldmVudCI6eyJhcGlWZXJzaW9uIjoidjMiLCJpZCI6IjA4Yjk3Zjg3LTY5NjEtNDYzZi1hYzg0LTRjMTBkM2NjZmM5NiIsImRldmljZU5hbWUiOiJSYW5kb20tRmxvYXQtRGV2aWNlIiwicHJvZmlsZU5hbWUiOiJSYW5kb20tRmxvYXQtRGV2aWNlIiwic291cmNlTmFtZSI6IkZsb2F0NjQiLCJvcmlnaW4iOjE3MTY3MDgxNzM5MzAyMDMxMzIsInJlYWRpbmdzIjpbeyJpZCI6ImJiNjI5Y2JmLWI5ZDktNGRhOS1iZDY1LWJkZGNmNzA1Yzk3NSIsIm9yaWdpbiI6MTcxNjcwODE3MzkzMDIwMzEzMiwiZGV2aWNlTmFtZSI6IlJhbmRvbS1GbG9hdC1EZXZpY2UiLCJyZXNvdXJjZU5hbWUiOiJGbG9hdDY0IiwicHJvZmlsZU5hbWUiOiJSYW5kb20tRmxvYXQtRGV2aWNlIiwidmFsdWVUeXBlIjoiRmxvYXQ2NCIsInZhbHVlIjoiLTcuMzg3MzI0ZSszMDYifV19fQ==\",\"contentType\":\"application/json\"}"
1) "pmessage"
2) "edgex.events.*"
3) "edgex.events.device.device-virtual.Random-Boolean-Device.Random-Boolean-Device.Bool"
4) "{\"apiVersion\":\"v3\",\"receivedTopic\":\"\",\"correlationID\":\"592546ca-85ed-48f0-ac4d-78a2ea32799d\",\"requestID\":\"\",\"errorCode\":0,\"payload\":\"eyJhcGlWZXJzaW9uIjoidjMiLCJyZXF1ZXN0SWQiOiI0ODNmMTViMi05MmRhLTRiZDMtOTg1My0yZDRjYWQwMjIzNWIiLCJldmVudCI6eyJhcGlWZXJzaW9uIjoidjMiLCJpZCI6ImMxZDU3MTFkLTc3MDMtNDJhYy1hNzhmLTFiNzIwNTI3ZDAwOSIsImRldmljZU5hbWUiOiJSYW5kb20tQm9vbGVhbi1EZXZpY2UiLCJwcm9maWxlTmFtZSI6IlJhbmRvbS1Cb29sZWFuLURldmljZSIsInNvdXJjZU5hbWUiOiJCb29sIiwib3JpZ2luIjoxNzE2NzA4MTczOTM3MDgwNjMwLCJyZWFkaW5ncyI6W3siaWQiOiJkODg2M2U0ZC02YmY1LTRhMmItYTkwNC03NDc4Y2IwMGEyZWYiLCJvcmlnaW4iOjE3MTY3MDgxNzM5MzcwODA2MzAsImRldmljZU5hbWUiOiJSYW5kb20tQm9vbGVhbi1EZXZpY2UiLCJyZXNvdXJjZU5hbWUiOiJCb29sIiwicHJvZmlsZU5hbWUiOiJSYW5kb20tQm9vbGVhbi1EZXZpY2UiLCJ2YWx1ZVR5cGUiOiJCb29sIiwidmFsdWUiOiJmYWxzZSJ9XX19\",\"contentType\":\"application/json\"}"
仔细观察上面的事件以及实际的设备的名称
访问 curl http://127.0.0.1:59882/api/v2/device/all 获取到下面的设备所有信息
可以发现事件的后缀就是对应着设备的 deviceName
对 payload 进行 base64 反编码可以得出如下结果
{
"apiVersion": "v3",
"requestId": "b3dee34a-2ac1-4494-b5c3-ae270d9a0805",
"event": {
"apiVersion": "v3",
"id": "08b97f87-6961-463f-ac84-4c10d3ccfc96",
"deviceName": "Random-Float-Device",
"profileName": "Random-Float-Device",
"sourceName": "Float64",
"origin": 1716708173930203132,
"readings": [
{
"id": "bb629cbf-b9d9-4da9-bd65-bddcf705c975",
"origin": 1716708173930203132,
"deviceName": "Random-Float-Device",
"resourceName": "Float64",
"profileName": "Random-Float-Device",
"valueType": "Float64",
"value": "-7.387324e+306"
}
]
}
}
{
"apiVersion": "v3",
"requestId": "483f15b2-92da-4bd3-9853-2d4cad02235b",
"event": {
"apiVersion": "v3",
"id": "c1d5711d-7703-42ac-a78f-1b720527d009",
"deviceName": "Random-Boolean-Device",
"profileName": "Random-Boolean-Device",
"sourceName": "Bool",
"origin": 1716708173937080630,
"readings": [
{
"id": "d8863e4d-6bf5-4a2b-a904-7478cb00a2ef",
"origin": 1716708173937080630,
"deviceName": "Random-Boolean-Device",
"resourceName": "Bool",
"profileName": "Random-Boolean-Device",
"valueType": "Bool",
"value": "false"
}
]
}
}
获取 ekuiper 的容器 ip
➜ ~ docker inspect 503cf110e559 | grep -i ipaddr
"SecondaryIPAddresses": null,
"IPAddress": "",
"IPAddress": "172.24.0.12",
案例一:直接上报设备产生的数据
创建流
请将 $eKuiper_server
替换为本地运行的 eKuiper 实例的地址
实际上上面docker-compose里面都做了端口映射,可直接将 eKuiper_server
定义成 127.0.0.1
即可
curl -X POST \
http://$eKuiper_server:59720/streams \
-H 'Content-Type: application/json' \
-d '{
"sql": "create stream demo() WITH (FORMAT=\"JSON\", TYPE=\"edgex\")"
}'
进入到 ekuiper 里面查看创建的流 stream
/kuiper/bin $ ./kuiper show streams
Connecting to 127.0.0.1:20498...
demo
现在流已经创建好了,但是你可能好奇 eKuiper 是如何知道消息总线的地址和端口,因为此类信息在 CREATE STREAM
并未指定。实际上这些信息是在配置文件 etc/sources/edgex.yaml
中指定的,你可以在命令行窗口中输入 cat etc/sources/edgex.yaml
来查看文件的内容。如果你有不同的服务器、端口和服务的地址,请更新相应的配置。正如之前提到的,这些配置选项可以在容器启动的时候进行重写
/kuiper/etc/sources $ cat edgex.yaml
application_conf:
port: 5571
protocol: tcp
server: localhost
topic: application
default:
port: 6379
protocol: redis
server: edgex-redis
topic: edgex/rules-events
type: redis
mqtt_conf:
optional:
ClientId: client1
port: 1883
protocol: tcp
server: 127.0.0.1
topic: events
type: mqtt
创建规则
我们创建一条规则,将分析结果发送至 MQTT 服务器
以下例子将选出所有 events
主题上所有的数据,分析结果将被
- 发布到公共的 MQTT 服务器
broker.emqx.io
的主题result
上; - 打印至日志文件
curl -X POST \
http://$eKuiper_server:59720/rules \
-H 'Content-Type: application/json' \
-d '{
"id": "rule1",
"sql": "SELECT * FROM demo",
"actions": [
{
"mqtt": {
"server": "tcp://broker.emqx.io:1883",
"topic": "result",
"clientId": "demo_001"
}
},
{
"log":{}
}
]
}'
查看规则
/kuiper/bin $ ./kuiper show rules
Connecting to 127.0.0.1:20498...
[
{
"id": "rule1",
"status": "Running"
}
]
/kuiper/bin $ ./kuiper describe rule rule1
Connecting to 127.0.0.1:20498...
{
"triggered": true,
"id": "rule1",
"sql": "SELECT * FROM demo",
"actions": [
{
"mqtt": {
"clientId": "demo_001",
"server": "tcp://broker.emqx.io:1883", 定义的设备数据上报地址
"topic": "result" 定义的MQTT的topic
}
},
{
"log": {}
}
],
"options": {
"isEventTime": false,
"lateTolerance": 1000,
"concurrency": 1,
"bufferLength": 1024,
"sendMetaToSink": false,
"sendError": true,
"qos": 0,
"checkpointInterval": 300000
}
}
docker logs 查看 ekuiper 的日志信息
可观察到相关的数据
监控分析结果
上面我们定义的设备的数据上报地址为 mqtt 的地址
使用 mosquitto_sub 进行 MQTT 消息订阅就能看到我们设备经过 ekuiper 发送到 MQTT 的消息
➜ ~ mosquitto_sub -h broker.emqx.io -t result
[{"Float64":1.658207e+307}]
[{"Float32":-8.653245654362261e+37}]
[{"Int8":-69}]
[{"Int64":-5707572273003489628}]
[{"Int32":-1320144333}]
[{"Int16":11173}]
[{"Bool":true}]
[{"Uint8":68}]
[{"Uint16":63161}]
[{"Uint32":3342819840}]
[{"Uint64":11984961581557470757}]
[{"Bool":true}]
可以看到我们这里显示的消息类型其实有所有设备的消息,这是因为我们规则引擎里面定义了对应的 topic 为 edgex 的 "edgex/events/#"
EdgeX v2 消息总线具有多级主题,以便消费者可以高效地按主题过滤消息。 参考docs.edgexfoundry.org
如果我们只想规只考虑来自 Random-Boolean-Device 的数据,我们可以修改规则引擎的 docker compose 文件来更改主题并将消息类型修改为 request
,如下所示
...
...
rulesengine:
...
environment:
...
EDGEX__DEFAULT__TOPIC: edgex/events/#/Random-Boolean-Device/#
EDGEX__DEFAULT__MESSAGETYPE: request
...
上面的 MESSAGETYPE 代表 EdgeX 的消息类型
再次订阅消息就只会有 bool 类型的消息
➜ ~ mosquitto_sub -h broker.emqx.io -t result
[{"Bool":true}]
[{"Bool":false}]
[{"Bool":false}]
[{"Bool":true}]