You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:57:14