
一、介紹 Node-RED 是由 IBM 新興技術(shù)團(tuán)隊(duì)開發(fā)并開源的一款基于流程編程的低代碼開發(fā)工具,專為物聯(lián)網(wǎng)和工業(yè)自動(dòng)化場景設(shè)計(jì) 。· 底層核心:基于 Node.js 構(gòu)建,本質(zhì)是一個(gè)運(yùn)行在服務(wù)器(或邊緣設(shè)備)上的 Web 應(yīng)用程序 。· 運(yùn)行機(jī)制:通過瀏覽器中的可視化編輯器,將預(yù)制的節(jié)點(diǎn)(Node)拖拽到畫布上并連接,形成流(Flow)。數(shù)據(jù)以 JSON 格式在節(jié)點(diǎn)間傳遞,通過配置即可實(shí)現(xiàn)業(yè)務(wù)邏輯,無需大量手寫代碼 ?!?應(yīng)用場景:廣泛用于工業(yè)數(shù)據(jù)采集(支持 Modbus、OPC UA、MQTT 等協(xié)議)、協(xié)議轉(zhuǎn)換、實(shí)時(shí)數(shù)據(jù)處理以及與數(shù)據(jù)庫(如 InfluxDB、TDengine、SQLite)或硬件(如 GPIO)的交互 。 本質(zhì)上,它是一個(gè)在瀏覽器里“畫流程圖”就能編程的工業(yè)物聯(lián)網(wǎng)中間件。二、安裝Node-RED 安裝教程(Linux / Docker 版)2.1、Linux 原生安裝1. 安裝 Node.js(≥18.x)node.js卸載、安裝、配置詳解# Ubuntu/Debian curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs # CentOS/RHEL curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash - sudo yum install -y nodejs# 驗(yàn)證安裝node --version npm --version2. 安裝 Node-REDnode-red:環(huán)境搭建及使用sudo npm install -g --unsafe-perm node-red3. 啟動(dòng)運(yùn)行# 后臺(tái)服務(wù)化(生產(chǎn)用)sudo systemctl enable nodered.service # 設(shè)置開機(jī)自啟 sudo systemctl start nodered.service # 啟動(dòng)服務(wù)4. 訪問地址瀏覽器打開:http://本機(jī)IP:18805、pm2管理node-red1、pm2給應(yīng)用重命名 pm2 restart node-red --name juxinnode 2、設(shè)置應(yīng)用超180M就重啟 pm2 start juxinnode --max-memory-restart 180M 3、docker未設(shè)置掛載的node-red的配置文件在/data/node-red下面 pm2 restart 0 --cron "0 1 * * *" export EDITOR=vi #編輯定時(shí)任務(wù)2.2、Docker 安裝node-red參考資料docker run -u root:node-red -v /home/node3:/data --privileged=true -p 1883:1880 --name node_red3 -d mynodered:1.2 --restart=always 統(tǒng)一時(shí)間: docker cp /usr/share/zoneinfo/Asia/Shanghai node_red4:/etc/localtime 首先進(jìn)入容器內(nèi) docker exec -it ID /bin/bash 安裝插件 npm install node-red-contrib-modbus2.2.1、使用 Docker Compose創(chuàng)建 docker-compose.yml:version: '3' services: nodered: image: nodered/node-red container_name: nodered ports: - "1880:1880" volumes: - ./node-red-data:/data restart: always啟動(dòng):docker-compose up -d訪問地址瀏覽器打開:http://Docker宿主機(jī)IP:18802.2.2、常用命令速查操作 Linux 原生 Docker 啟動(dòng) node-red 或 systemctl start nodered docker start nodered 停止 systemctl stop nodered docker stop nodered 重啟 systemctl restart nodered docker restart nodered 日志 node-red-log 或 journalctl -u nodered docker logs nodered 開機(jī)自啟 systemctl enable nodered restart: always(compose)2.2.3、驗(yàn)證安裝任意瀏覽器訪問 http://服務(wù)器IP:1880,看到可視化編輯界面即成功。三、節(jié)點(diǎn)介紹3.1、influxdb節(jié)點(diǎn)"1、安裝influxdb插件node-red-contrib-influxdb 2、配置Influxdb參數(shù)設(shè)置數(shù)據(jù)庫以及表名稱 3、編寫函數(shù) var object=msg.payload.data; msg.payload = [{ name:""tjn"", datas:object[0], timestamp: new Date()}]; return msg 記住是json的key value的格式就對(duì)了! 然后點(diǎn)擊部署,查看數(shù)據(jù)庫,你就可以發(fā)現(xiàn)插入的數(shù)據(jù)了! 總結(jié):表名稱要寫,不然會(huì)出錯(cuò)!"3.2、sqlserver節(jié)點(diǎn)1、使用node-red-contrib-mssql-plus 這個(gè)節(jié)點(diǎn) 2、雙擊節(jié)點(diǎn),在Query里面輸入SELECT Tray_ID as code,CDT FROM HIS_TCOMMANDS WHERE CDT = DATEADD(MINUTE, -10, GETDATE()) AND PLCType = 2 AND TaskMode = 2 ORDER BY CDT DESC3.3、安裝modbus節(jié)點(diǎn)npm install -g node-red-contrib-modbus --unsafe-perm --build-from-source 若失敗報(bào)錯(cuò) make: g++:命令未找到 make: *** [Release/obj.target/bindings/src/serialport.o] 解決方案 make是gcc的編譯器,VPS買來必定要安裝 安裝: yum -y install gcc automake autoconf libtool make 安裝g++: yum install gcc gcc-c++3.4、fs模塊異步讀寫文件// 使用示例(異步版本) (async function () { try { const filePath = '/home/juxin/test.txt'; // 讀文件 - 使用 await const myContent = await readAndTrimAsync(filePath); node.warn("讀取文件內(nèi)容 " + myContent); // 寫文件 - 使用 await const result1 = await writeFileCoverAsync(filePath, '周受資,清雅絕倫君子面', { log: node.warn, trim: true }); } catch (error) { // 處理錯(cuò)誤 msg.payload = { error: error.message, success: false }; } // 確保返回msg return msg; })(); // ============================ // 異步文件讀取函數(shù) // ============================ async function readAndTrimAsync(filePath) { const fs = global.get('fs'); const fsPromises = fs.promises; // Node.js 10+ 支持 // 基礎(chǔ)驗(yàn)證 if (!fs) throw new Error('fs模塊不可用'); if (!fsPromises) throw new Error('fs.promises不可用,請(qǐng)升級(jí)Node.js到10.0+'); if (!filePath) throw new Error('文件路徑不能為空'); try { // 檢查文件是否存在 - 異步 try { await fsPromises.access(filePath); } catch (error) { throw new Error(`文件不存在或無訪問權(quán)限: ${filePath}`); } // 讀取文件 - 異步 let content = await fsPromises.readFile(filePath, 'utf8'); // 空值檢查 if (content === null || content === undefined || content === '') { throw new Error(`文件內(nèi)容為空: ${filePath}`); } // 去除首尾空格 content = content.trim(); return content; } catch (error) { // 使用node.error記錄錯(cuò)誤 if (typeof node !== 'undefined' node.error) { node.error(`讀取文件失敗 [${filePath}]: ${error.message}`); } else { console.error(`讀取文件失敗 [${filePath}]:`, error); } throw error; // 重新拋出,讓調(diào)用者處理 } } // ============================ // 異步文件覆蓋寫入函數(shù) // ============================ async function writeFileCoverAsync(filePath, content, options = {}) { const fs = global.get('fs'); const fsPromises = fs.promises; const path = global.get('path'); // 基礎(chǔ)驗(yàn)證 if (!fs) throw new Error('fs模塊不可用'); if (!fsPromises) throw new Error('fs.promises不可用,請(qǐng)升級(jí)Node.js到10.0+'); if (!filePath) throw new Error('文件路徑不能為空'); if (content === undefined || content === null) { throw new Error('寫入內(nèi)容不能為空'); } // 默認(rèn)選項(xiàng) const defaults = { encoding: 'utf8', createDir: true, // 自動(dòng)創(chuàng)建目錄 log: null, // 日志函數(shù),如 node.warn backup: false, // 是否備份原文件 backupSuffix: '.bak', // 備份文件后綴 trim: false // 是否自動(dòng)去除內(nèi)容首尾空格 }; const opts = { ...defaults, ...options }; try { // 1. 處理內(nèi)容(可選trim) let processedContent = content; if (opts.trim typeof processedContent === 'string') { processedContent = processedContent.trim(); } // 如果是對(duì)象,轉(zhuǎn)為JSON字符串 if (typeof processedContent === 'object') { processedContent = JSON.stringify(processedContent, null, 2); } // 2. 自動(dòng)創(chuàng)建目錄(如果需要)- 異步 if (opts.createDir path) { const dir = path.dirname(filePath); try { await fsPromises.access(dir); } catch { // 目錄不存在,創(chuàng)建它 await fsPromises.mkdir(dir, { recursive: true }); if (opts.log) opts.log(`?? 已創(chuàng)建目錄: ${dir}`); } } // 3. 備份原文件(如果需要)- 異步 if (opts.backup) { try { await fsPromises.access(filePath); const backupPath = filePath + opts.backupSuffix; await fsPromises.copyFile(filePath, backupPath); if (opts.log) opts.log(`?? 已備份原文件至: ${backupPath}`); } catch { // 原文件不存在,無需備份 } } // 4. 覆蓋寫入文件 - 異步 await fsPromises.writeFile(filePath, processedContent, opts.encoding); // 5. 驗(yàn)證寫入成功 - 異步 const stats = await fsPromises.stat(filePath); const fileSize = stats.size; // 6. 記錄日志 if (opts.log) { opts.log(`? 文件寫入成功: ${filePath}`); opts.log(` 文件大小: ${fileSize} 字節(jié)`); if (typeof content === 'string') { opts.log(` 內(nèi)容長度: ${processedContent.length} 字符`); } } return { success: true, filePath: filePath, size: fileSize, timestamp: new Date().toISOString() }; } catch (error) { const errMsg = `寫入文件失敗 [${filePath}]: ${error.message}`; if (opts.log) { opts.log(`? ${errMsg}`); } throw new Error(errMsg); } } // ============================ // 批量操作輔助函數(shù) // ============================ // 批量讀取多個(gè)文件 async function readMultipleFilesAsync(filePaths) { const readPromises = filePaths.map(filePath = readAndTrimAsync(filePath).catch(error = { // 返回錯(cuò)誤信息而不是拋出,確保其他文件可以繼續(xù)讀取 return { filePath, error: error.message, success: false }; }) ); return await Promise.all(readPromises); } // 批量寫入多個(gè)文件 async function writeMultipleFilesAsync(fileContentMap, options = {}) { const writePromises = Object.entries(fileContentMap).map(([filePath, content]) = writeFileCoverAsync(filePath, content, options).catch(error = { // 返回錯(cuò)誤信息 return { filePath, error: error.message, success: false }; }) ); return await Promise.all(writePromises); }3.5、實(shí)現(xiàn)遠(yuǎn)程服務(wù)器性能監(jiān)測https://blog.csdn.net/weixin_44462773/article/details/149119994?spm=1011.2415.3001.53313.6、監(jiān)測超融合Proxmox VE集群狀態(tài)數(shù)據(jù)https://blog.csdn.net/weixin_44462773/article/details/148919884?spm=1011.2415.3001.53313.7、sqlite節(jié)點(diǎn)node-red-node-sqlite對(duì)比維度核心優(yōu)勢核心劣勢一句話關(guān)鍵結(jié)論部署運(yùn)維成本零后臺(tái)服務(wù)、單文件存儲(chǔ),無需安裝配置賬號(hào)端口,Node-RED 里填路徑就能用,備份遷移直接復(fù)制文件無遠(yuǎn)程運(yùn)維能力,只能本地操作,無自帶主從備份、監(jiān)控告警零運(yùn)維門檻,僅適合單機(jī)本地部署并發(fā)寫入能力單線程寫入無額外開銷,單機(jī)低并發(fā)寫入響應(yīng)極快文件級(jí)鎖,同一時(shí)間僅支持 1 個(gè)寫操作,多并發(fā)寫入直接排隊(duì)阻塞,高頻寫入必卡頓寫并發(fā)是致命短板,超過 1 路高頻寫入必出問題數(shù)據(jù)規(guī)模上限單庫理論最大支持 140TB,容量上限足夠單表百萬級(jí)數(shù)據(jù)后,無索引查詢性能暴跌,無原生分庫分表能力,海量數(shù)據(jù)管理能力弱小數(shù)據(jù)量順滑,大數(shù)據(jù)量拉胯,僅適合十萬級(jí)以內(nèi)單表數(shù)據(jù)讀寫性能純本地文件讀寫,無網(wǎng)絡(luò)開銷,低頻次簡單讀寫延遲極低無專業(yè)緩存機(jī)制,復(fù)雜聯(lián)表、分組統(tǒng)計(jì) CPU 占用飆升,批量寫入不加事務(wù)會(huì)極慢簡單讀寫快,復(fù)雜查詢 / 批量寫入性能遠(yuǎn)低于專業(yè)數(shù)據(jù)庫功能完整性支持標(biāo)準(zhǔn) SQL、事務(wù)、索引,完全覆蓋日常增刪改查、建表需求無用戶權(quán)限、存儲(chǔ)過程、觸發(fā)器能力弱,無主從、集群、分表等企業(yè)級(jí)能力基礎(chǔ)功能夠用,高級(jí)功能幾乎為零數(shù)據(jù)安全與可靠性支持事務(wù)原子性,單文件備份簡單,正常斷電數(shù)據(jù)無丟失寫入中途強(qiáng)制斷電、閃存損壞有小概率文件損壞,無自動(dòng)恢復(fù)機(jī)制正常使用安全,極端故障場景有數(shù)據(jù)丟失風(fēng)險(xiǎn)跨設(shè)備共享能力無僅支持本地進(jìn)程讀寫,無法多設(shè)備遠(yuǎn)程連接共享數(shù)據(jù)庫完全不支持分布式場景,多設(shè)備數(shù)據(jù)匯總只能單機(jī)單獨(dú)存后人工合并硬件適配性超輕量,CPU / 內(nèi)存占用極低,樹莓派、工控機(jī)、網(wǎng)關(guān)等低配硬件完美適配無嵌入式 / 低硬件配置場景適配性拉滿SQLite 是 **「單機(jī)輕量場景的最優(yōu)解,高并發(fā)分布式場景的絕對(duì)禁區(qū)」**,它的所有優(yōu)勢都來自「極簡無服務(wù)」,所有劣勢也都來自「極簡無服務(wù)」,沒有任何中間地帶。?必用場景:單臺(tái)本地網(wǎng)關(guān) / 工控機(jī)、低頻率數(shù)據(jù)存儲(chǔ)(分鐘級(jí)采集、本地日志、配置表)、單機(jī)自動(dòng)化流程、無運(yùn)維需求的小型項(xiàng)目 ?絕對(duì)別用:多設(shè)備高頻并發(fā)上報(bào)、需要多機(jī)共享數(shù)據(jù)庫、百萬級(jí)以上長期數(shù)據(jù)存儲(chǔ)、7×24 小時(shí)不間斷時(shí)序采集避坑核心只要你的場景有「超過 1 個(gè)節(jié)點(diǎn)同時(shí)寫入」,直接放棄 SQLite,必出阻塞、丟數(shù)據(jù)問題;不要試圖用 SQLite 做海量歷史數(shù)據(jù)存儲(chǔ),單表超過 100 萬行后查詢性能會(huì)斷崖式下跌;樹莓派 / SD 卡高頻寫入場景,必須加事務(wù)、定時(shí)歸檔,否則會(huì)加速閃存磨損,大概率出現(xiàn)文件損壞。 4 sqlite全方位性能碾壓記事本[ { "id": "81c6b6105cd12496", "type": "inject", "z": "100b4b115fb10d1e", "name": "創(chuàng)建", "props": [ { "p": "opr", "v": "1", "vt": "num" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 370, "y": 160, "wires": [ [ "0e3b37521a2fd70f" ] ] }, { "id": "0e3b37521a2fd70f", "type": "function", "z": "100b4b115fb10d1e", "name": "插入", "func": "if(msg.opr == 1){\n //創(chuàng)建語句\n msg.topic = `CREATE TABLE IF NOT EXISTS users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n email TEXT NOT NULL)`;\n}\n\nelse if (msg.opr == 2) {\n //插入數(shù)據(jù)(INSERT)\n msg.topic = `INSERT INTO users (name, email) VALUES ($name, $email);`\n msg.payload = [\"alice\", \"alice@163.com\"];\n}\n\nelse if (msg.opr == 3) {\n//更新數(shù)據(jù)(UPDATE)\n msg.topic = \"UPDATE users SET email = $email WHERE name = $name\";\n msg.payload = [\"new_email@1example.com\", \"alice\"];\n}\n\nelse if (msg.opr == 4) {\n //刪除數(shù)據(jù)(DELETE)\n msg.topic = \"DELETE FROM users WHERE id = $id\";\n msg.payload = [1]; // 刪除id為1的記錄\n}\nelse{\n msg.topic = \"SELECT * FROM users;\";\n msg.payload = [];\n}\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 590, "y": 260, "wires": [ [ "fb1339cb98ec300e" ] ] }, { "id": "fb1339cb98ec300e", "type": "sqlite", "z": "100b4b115fb10d1e", "mydb": "e90adb5af2580d03", "sqlquery": "msg.topic", "sql": "", "name": "", "x": 770, "y": 260, "wires": [ [ "41ecf6106bcddffb" ] ] }, { "id": "41ecf6106bcddffb", "type": "debug", "z": "100b4b115fb10d1e", "name": "debug 61", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 960, "y": 260, "wires": [] }, { "id": "6cc14b8c94bd04ae", "type": "inject", "z": "100b4b115fb10d1e", "name": "插入",