如何优化node-opcUA服务器批量创建10万变量的耗时?
如何在node-opcUA中高效批量创建10万个点位
原代码性能瓶颈分析
原实现的核心问题在于:
- 每个点位都单独创建
Object和Variable节点,重复触发节点元数据的初始化与校验 - 不必要的
setTimeout长延迟大幅拉长总耗时 - 未利用node-opcUA的批量节点创建API,导致引擎频繁处理单个节点事务
优化方案
1. 预定义节点类型,复用元数据
先创建可复用的对象类型和变量类型,后续实例化时直接引用,避免重复生成节点元数据:
// 预定义Vessel对象类型 const vesselType = namespace.addObjectType({ browseName: "VesselType" }); // 预定义Pressure变量类型 const pressureVariableType = namespace.addVariableType({ browseName: "PressureType", dataType: "Double", value: { get: () => new opcua.Variant({ dataType: opcua.DataType.Double, value: Math.random() }) } });
2. 使用批量节点创建API
node-opcUA的addressSpace.addNodes方法支持一次性批量添加多个节点,大幅减少引擎内部事务开销:
function createBatch(batchStart, batchEnd) { const nodesToAdd = []; for (let i = batchStart; i <= batchEnd; i++) { // 添加Vessel对象节点 nodesToAdd.push({ nodeClass: opcua.NodeClass.Object, browseName: `Vessel_${i}`, organizedBy: addressSpace.rootFolder.objects, typeDefinition: vesselType.nodeId }); // 添加Pressure变量节点 nodesToAdd.push({ nodeClass: opcua.NodeClass.Variable, nodeId: `ns=1;s=Variable${i}`, browseName: `Pressure_${i}`, dataType: "Double", typeDefinition: pressureVariableType.nodeId, componentOf: `ns=1;s=Vessel_${i}` // 关联到对应Vessel对象 }); } // 批量添加节点 return addressSpace.addNodes(nodesToAdd); }
3. 高效分批处理,移除无意义延迟
使用同步分批+微任务切换,避免阻塞事件循环的同时去掉不必要的长延迟:
async function construct_address_space(server) { const addressSpace = server.engine.addressSpace; const namespace = addressSpace.getOwnNamespace(); // 预定义类型节点 const vesselType = namespace.addObjectType({ browseName: "VesselType" }); const pressureVariableType = namespace.addVariableType({ browseName: "PressureType", dataType: "Double", value: { get: () => new opcua.Variant({ dataType: opcua.DataType.Double, value: Math.random() }) } }); const totalPoints = 100000; const batchSize = 10000; // 增大批次大小,减少批量调用次数 for (let batch = 0; batch < totalPoints / batchSize; batch++) { const start = batch * batchSize + 1; const end = Math.min((batch + 1) * batchSize, totalPoints); await createBatch(start, end); console.log(`已创建 ${end} 个点位`); // 用微任务让出事件循环,避免阻塞 await new Promise(resolve => process.nextTick(resolve)); } }
完整优化代码
const opcua = require("node-opcua"); async function construct_address_space(server) { const addressSpace = server.engine.addressSpace; const namespace = addressSpace.getOwnNamespace(); // 1. 预定义可复用的节点类型 const vesselType = namespace.addObjectType({ browseName: "VesselType" }); const pressureVariableType = namespace.addVariableType({ browseName: "PressureType", dataType: "Double", value: { get: () => new opcua.Variant({ dataType: opcua.DataType.Double, value: Math.random() }) } }); // 2. 批量创建节点的工具函数 async function createBatch(batchStart, batchEnd) { const nodesToAdd = []; for (let i = batchStart; i <= batchEnd; i++) { // 添加Vessel对象节点 nodesToAdd.push({ nodeClass: opcua.NodeClass.Object, browseName: `Vessel_${i}`, organizedBy: addressSpace.rootFolder.objects, typeDefinition: vesselType.nodeId }); // 添加Pressure变量节点 nodesToAdd.push({ nodeClass: opcua.NodeClass.Variable, nodeId: `ns=1;s=Variable${i}`, browseName: `Pressure_${i}`, dataType: "Double", typeDefinition: pressureVariableType.nodeId, componentOf: `ns=1;s=Vessel_${i}` }); } await addressSpace.addNodes(nodesToAdd); } // 3. 高效分批处理 const totalPoints = 100000; const batchSize = 10000; for (let batch = 0; batch < Math.ceil(totalPoints / batchSize); batch++) { const start = batch * batchSize + 1; const end = Math.min((batch + 1) * batchSize, totalPoints); await createBatch(start, end); console.log(`已创建 ${end} 个点位`); // 让出事件循环,避免阻塞服务器初始化 await new Promise(resolve => process.nextTick(resolve)); } } (async () => { try { const server = new opcua.OPCUAServer({ port: 26543, resourcePath: "/UA/MyLittleServer", nodeset_filename: [opcua.nodesets.standard] }); await server.initialize(); console.log("certificateFile = ", server.certificateFile); console.log("privateKeyFile = ", server.privateKeyFile); console.log("rejected folder = ", server.serverCertificateManager.rejectedFolder); console.log("trusted folder = ", server.serverCertificateManager.trustedFolder); await construct_address_space(server); // 改为异步调用,确保创建完成再启动服务器 await server.start(); console.log("Server is now listening ... ( press CTRL+C to stop)"); console.log("port ", server.endpoints[0].port); const endpointUrl = server.endpoints[0].endpointDescriptions()[0].endpointUrl; console.log(" the primary server endpoint url is ", endpointUrl); } catch (err) { console.log("Error = ", err); } })();
额外优化建议
- 若不需要每个Vessel作为独立对象,可将所有Pressure变量直接挂载到同一个父对象下,减少一半节点数量
- 批量创建时尽量增大批次大小(如1万-2万),减少
addNodes调用次数 - 运行时更新变量值时,可批量获取节点后统一更新,避免循环遍历数组
内容的提问来源于stack exchange,提问作者Gaurav Verma
相关产品推荐
相关产品推荐

