带着问题看源码4-NodeRed中消息路由实现

本文详细阐述了NodeRed中的Hook模块如何影响消息路由,包括其工作原理、NodeRed中消息的流转过程、优势与设计决策。重点讲解了堆栈式与Hook式路由的区别,以及Hook模块在处理、触发和参与者之间的交互作用。
摘要由CSDN通过智能技术生成

博客迁移

不恰饭的小站

NodeRed 提供了一个 Hook 模块,此模块向消息生命周期的节点注册,控制消息的流转。

1. 消息路由是什么? 有几种实现方式 ?

消息路由即通过路由规则动态规划消息的传输路径,使消息按照过滤条件,从消息源路由到目标节点。通过消息路由,可实现对数据路由的灵活控制和提高数据安全性。

主要有两种方式:

  1. 堆栈式
    类 Express 的 middleware,每新增加一个处理模块,会在处理堆栈的顶端,最终形成一个消息处理堆栈。

  2. Hook 式
    以 Hook(钩子)的形式实现,这需要在特定的节点处,增加对事件的回调,在有事件触发时,按序执行回调。

2. NodeRed 中消息如何流转

2.1. NodeRed 消息流转图

message-router-events
从消息发送到处理完毕,有以下几个事件:

  1. onSend

  2. preRoute

  3. preDeliver

  4. postDeliver

  5. onReceive

  6. postReceive

  7. onComplete

2.2. SendEvent object

{
    "msg": "<message object>",
    "source": {
        "id": "<node-id>",
        "node": "<node-object>",
        "port": "<index of port being sent on>",
    },
    "destination": {
        "id": "<node-id>",
        "node": undefined,
    },
    "cloneMessage": "true|false"
}

2.3. ReceiveEvent object

{
    "msg": "<message object>",
    "destination": {
        "id": "<node-id>",
        "node": "<node-object>",
    }
}

2.4. CompleteEvent object

{
    "msg": "<message object>",
    "node": {
        "id": "<node-id>",
        "node": "<node-object>"
    },
    "error": "<error passed to done, otherwise, undefined>"
}

2.5. 有哪些参与者

2.5.1. packages/node_modules/@node-red/util/lib/hooks.js

  1. 新增 Hook(以链表存储 Hook )
function add(hookId, callback) {
    ...
    let tailItem = hooks[id];
    if (tailItem === undefined) {
        hooks[id] = hookItem;
    } else {
        while(tailItem.nextHook !== null) {
            tailItem = tailItem.nextHook
        }
        tailItem.nextHook = hookItem;
        hookItem.previousHook = tailItem;
    }

    if (label) {
        labelledHooks[label] = labelledHooks[label]||{};
        labelledHooks[label][id] = hookItem;
    }

    ...
}
  1. 触发消息流
    支持回调方式,Promise 方式
function trigger(hookId, payload, done) {
    let hookItem = hooks[hookId];
    if (!hookItem) {  //没有钩子执行回调或返回
        if (done) {
            done();
            return;
        } else {
            return Promise.resolve();
        }
    }
    if (!done) { //有钩子没有回调,以微任务方式处理所有Hook
        return new Promise((resolve,reject) => {
            invokeStack(hookItem,payload,function(err) {
                if (err !== undefined && err !== false) {
                    if (!(err instanceof Error)) {
                        err = new Error(err);
                    }
                    err.hook = hookId
                    reject(err);
                } else {
                    resolve(err);
                }
            })
        });
    } else { //有钩子有回调,处理所有Hook
        invokeStack(hookItem,payload,done)
    }
}
  1. 处理 Hook
function invokeStack(hookItem,payload,done) {
    function callNextHook(err) {
        if (!hookItem || err) {
            done(err);
            return;
        }
        if (hookItem.removed) {
            hookItem = hookItem.nextHook;
            callNextHook();
            return;
        }
        const callback = hookItem.cb;
        if (callback.length === 1) { //回调函数一个参数
            try {
                let result = callback(payload);
                if (result === false) {
                    // Halting the flow
                    done(false);
                    return
                }
                if (result && typeof result.then === 'function') {
                    result.then(handleResolve, callNextHook)
                    return;
                }
                hookItem = hookItem.nextHook;
                callNextHook();
            } catch(err) {
                done(err);
                return;
            }
        } else { //回调函数两个参数
            try {
                callback(payload,handleResolve)
            } catch(err) {
                done(err);
                return;
            }
        }
    }
    function handleResolve(result) {
        if (result === undefined) {
            hookItem = hookItem.nextHook;
            callNextHook();
        } else {
            done(result);
        }
    }
    callNextHook();
}

2.5.2. packages/node_modules/@node-red/runtime/lib/flows/Flow.js

2.6. 流程哪些

flow hooks node send消息数组,调用handleOnSend 1 hooks.trigger("onSend" 2 有异常返回异常,无异常继续消息传递 3 hooks.trigger("preRoute" 4 有异常返回异常,无异常继续消息传递 5 拷贝消息,准备路由handlePreDeliver 6 hooks.trigger("preDeliver" 7 拷贝消息,准备路由handlePreDeliver 8 hooks.trigger("postDeliver" 9 有异常返回异常,无异常继续消息传递 10 调用receive方法,发送Input事件 11 hooks.trigger("onReceive" 12 有异常返回异常,无异常继续消息传递 13 hooks.trigger("postReceive" 14 node._complete onComplete 15 有异常返回异常,无异常结束消息传 16 flow hooks node NodeRed中的节点模块管理-初始阶段

3. NodeRed 为什么这么设计,这种设计的优劣有哪些

堆栈式有一个问题,当以动态方式增加处理模块后,在不需要处理模块的情况下,删除增加的处理模块需要恢复原有的堆栈,增加了处理复杂度。以 Hook 方式动态的增加删除 Hook 不会影响原有消息路由。

这种选择也是根据 NodeRed 实际情况而来,在原有消息的传递机制上增加”关键点“的方式好实现。

4. Hook 模块与其他模块的关系

Node、Flow 模块调用 Hook 模块,触发 Hook 流转。
自定义模块调用 Hook 模块,增加 Hook 处理。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值