Node.js 语言连接器进阶指南

Node.js 连接器进阶指南

本指南涵盖 TDengine Node.js 连接器(@tdengine/websocket)的高级使用模式、最佳实践和优化技巧。

目录

连接池管理

基础连接池实现

对于生产环境应用,管理连接池对于优化性能和资源利用至关重要。

javascript 复制代码
const taos = require("@tdengine/websocket");

class TDengineConnectionPool {
    constructor(config, poolSize = 10) {
        this.config = config;
        this.poolSize = poolSize;
        this.connections = [];
        this.availableConnections = [];
        this.waitingQueue = [];
        this.initialized = false;
    }

    async initialize() {
        if (this.initialized) return;
        
        try {
            for (let i = 0; i < this.poolSize; i++) {
                const conn = await this.createConnection();
                this.connections.push(conn);
                this.availableConnections.push(conn);
            }
            this.initialized = true;
            console.log(`连接池已初始化,共 ${this.poolSize} 个连接`);
        } catch (error) {
            console.error('初始化连接池失败:', error);
            throw error;
        }
    }

    async createConnection() {
        const wsConfig = new taos.WSConfig(this.config.url);
        wsConfig.setUser(this.config.user);
        wsConfig.setPwd(this.config.password);
        wsConfig.setDb(this.config.database);
        wsConfig.setTimeOut(this.config.timeout || 5000);
        
        return await taos.sqlConnect(wsConfig);
    }

    async getConnection() {
        if (!this.initialized) {
            await this.initialize();
        }

        if (this.availableConnections.length > 0) {
            return this.availableConnections.pop();
        }

        // 等待可用连接
        return new Promise((resolve) => {
            this.waitingQueue.push(resolve);
        });
    }

    releaseConnection(connection) {
        if (this.waitingQueue.length > 0) {
            const resolve = this.waitingQueue.shift();
            resolve(connection);
        } else {
            this.availableConnections.push(connection);
        }
    }

    async executeQuery(sql, reqId) {
        const conn = await this.getConnection();
        try {
            const result = await conn.query(sql, reqId);
            return result;
        } finally {
            this.releaseConnection(conn);
        }
    }

    async execute(sql, reqId) {
        const conn = await this.getConnection();
        try {
            const result = await conn.exec(sql, reqId);
            return result;
        } finally {
            this.releaseConnection(conn);
        }
    }

    async close() {
        for (const conn of this.connections) {
            try {
                await conn.close();
            } catch (error) {
                console.error('关闭连接时出错:', error);
            }
        }
        this.connections = [];
        this.availableConnections = [];
        this.waitingQueue = [];
        this.initialized = false;
    }
}

// 使用示例
async function connectionPoolExample() {
    const pool = new TDengineConnectionPool({
        url: 'ws://localhost:6041',
        user: 'root',
        password: 'taosdata',
        database: 'test',
        timeout: 5000
    }, 10); // 连接池大小为 10

    try {
        await pool.initialize();

        // 并发执行多个查询
        const promises = [];
        for (let i = 0; i < 20; i++) {
            promises.push(pool.executeQuery(`SELECT * FROM meters LIMIT 10`));
        }

        const results = await Promise.all(promises);
        console.log(`成功执行了 ${results.length} 个查询`);
    } catch (error) {
        console.error('连接池错误:', error);
    } finally {
        await pool.close();
    }
}

连接健康检查

实现健康检查以确保连接可靠性:

javascript 复制代码
class HealthCheckPool extends TDengineConnectionPool {
    constructor(config, poolSize = 10, checkInterval = 30000) {
        super(config, poolSize);
        this.checkInterval = checkInterval;
        this.healthCheckTimer = null;
    }

    async initialize() {
        await super.initialize();
        this.startHealthCheck();
    }

    startHealthCheck() {
        this.healthCheckTimer = setInterval(async () => {
            await this.performHealthCheck();
        }, this.checkInterval);
    }

    async performHealthCheck() {
        console.log('正在执行健康检查...');
        const checkPromises = this.connections.map(async (conn, index) => {
            try {
                await conn.exec('SELECT SERVER_VERSION()');
                return { index, healthy: true };
            } catch (error) {
                console.error(`连接 ${index} 健康检查失败:`, error);
                return { index, healthy: false, connection: conn };
            }
        });

        const results = await Promise.all(checkPromises);
        
        // 替换不健康的连接
        for (const result of results) {
            if (!result.healthy) {
                await this.replaceConnection(result.index, result.connection);
            }
        }
    }

    async replaceConnection(index, oldConnection) {
        try {
            await oldConnection.close();
        } catch (error) {
            console.error('关闭不健康连接时出错:', error);
        }

        try {
            const newConnection = await this.createConnection();
            this.connections[index] = newConnection;
            
            // 更新可用连接列表
            const availIndex = this.availableConnections.indexOf(oldConnection);
            if (availIndex !== -1) {
                this.availableConnections[availIndex] = newConnection;
            }
            
            console.log(`已替换索引为 ${index} 的连接`);
        } catch (error) {
            console.error(`替换索引为 ${index} 的连接失败:`, error);
        }
    }

    async close() {
        if (this.healthCheckTimer) {
            clearInterval(this.healthCheckTimer);
            this.healthCheckTimer = null;
        }
        await super.close();
    }
}

高性能批量写入

使用预编译语句进行批量插入

预编译语句(STMT)提供了插入大量数据最高效的方式:

javascript 复制代码
const taos = require("@tdengine/websocket");

async function highPerformanceBatchInsert() {
    let conn = null;
    let stmt = null;

    try {
        // 建立连接
        const wsConfig = new taos.WSConfig('ws://localhost:6041');
        wsConfig.setUser('root');
        wsConfig.setPwd('taosdata');
        wsConfig.setDb('test');
        
        conn = await taos.sqlConnect(wsConfig);

        // 创建数据库和表
        await conn.exec('CREATE DATABASE IF NOT EXISTS test');
        await conn.exec('USE test');
        await conn.exec(`
            CREATE STABLE IF NOT EXISTS meters (
                ts TIMESTAMP,
                current FLOAT,
                voltage INT,
                phase FLOAT
            ) TAGS (
                location NCHAR(64),
                groupId INT
            )
        `);

        // 初始化预编译语句
        stmt = await conn.stmtInit();
        
        // 准备 SQL
        const prepareSql = 'INSERT INTO ? USING meters TAGS(?, ?) VALUES(?, ?, ?, ?)';
        await stmt.prepare(prepareSql);

        const batchSize = 1000;
        const numBatches = 10;
        const startTime = Date.now();

        for (let batch = 0; batch < numBatches; batch++) {
            // 设置表名和标签
            const tableName = `d${batch % 10}`;
            await stmt.setTableName(tableName);
            await stmt.setTags([`location_${batch % 10}`, batch % 10]);

            // 批量绑定数据
            const timestamps = [];
            const currents = [];
            const voltages = [];
            const phases = [];

            const baseTimestamp = BigInt(Date.now() - (batchSize * 1000));
            
            for (let i = 0; i < batchSize; i++) {
                timestamps.push(baseTimestamp + BigInt(i * 1000));
                currents.push(Math.random() * 10);
                voltages.push(Math.floor(Math.random() * 220) + 200);
                phases.push(Math.random() * 360);
            }

            // 绑定列
            await stmt.bindParam([
                timestamps,
                currents,
                voltages,
                phases
            ]);

            // 执行批次
            await stmt.batch();
            
            if ((batch + 1) % 100 === 0) {
                console.log(`已处理 ${(batch + 1) * batchSize} 条记录...`);
            }
        }

        // 最终执行
        await stmt.exec();
        
        const endTime = Date.now();
        const totalRecords = batchSize * numBatches;
        const duration = (endTime - startTime) / 1000;
        const recordsPerSecond = Math.floor(totalRecords / duration);

        console.log(`\n在 ${duration.toFixed(2)} 秒内插入了 ${totalRecords} 条记录`);
        console.log(`性能: ${recordsPerSecond} 条/秒`);

    } catch (error) {
        console.error('批量插入时出错:', error);
        throw error;
    } finally {
        if (stmt) {
            await stmt.close();
        }
        if (conn) {
            await conn.close();
        }
    }
}

并行批量写入

为了获得最大吞吐量,使用多个连接并行写入数据:

javascript 复制代码
async function parallelBatchInsert() {
    const numWorkers = 5;
    const recordsPerWorker = 100000;
    
    const workers = [];
    const startTime = Date.now();

    for (let workerId = 0; workerId < numWorkers; workerId++) {
        workers.push(insertWorker(workerId, recordsPerWorker));
    }

    await Promise.all(workers);

    const endTime = Date.now();
    const totalRecords = numWorkers * recordsPerWorker;
    const duration = (endTime - startTime) / 1000;
    const recordsPerSecond = Math.floor(totalRecords / duration);

    console.log(`\n总计: 在 ${duration.toFixed(2)} 秒内插入了 ${totalRecords} 条记录`);
    console.log(`总体性能: ${recordsPerSecond} 条/秒`);
}

async function insertWorker(workerId, numRecords) {
    let conn = null;
    let stmt = null;

    try {
        const wsConfig = new taos.WSConfig('ws://localhost:6041');
        wsConfig.setUser('root');
        wsConfig.setPwd('taosdata');
        wsConfig.setDb('test');
        
        conn = await taos.sqlConnect(wsConfig);
        stmt = await conn.stmtInit();
        
        const prepareSql = 'INSERT INTO ? USING meters TAGS(?, ?) VALUES(?, ?, ?, ?)';
        await stmt.prepare(prepareSql);

        const batchSize = 1000;
        const numBatches = Math.ceil(numRecords / batchSize);

        for (let batch = 0; batch < numBatches; batch++) {
            const currentBatchSize = Math.min(batchSize, numRecords - batch * batchSize);
            
            const tableName = `d_worker${workerId}_${batch % 10}`;
            await stmt.setTableName(tableName);
            await stmt.setTags([`worker_${workerId}`, workerId]);

            const timestamps = [];
            const currents = [];
            const voltages = [];
            const phases = [];

            const baseTimestamp = BigInt(Date.now() - (currentBatchSize * 1000));
            
            for (let i = 0; i < currentBatchSize; i++) {
                timestamps.push(baseTimestamp + BigInt(i * 1000));
                currents.push(Math.random() * 10);
                voltages.push(Math.floor(Math.random() * 220) + 200);
                phases.push(Math.random() * 360);
            }

            await stmt.bindParam([timestamps, currents, voltages, phases]);
            await stmt.batch();
        }

        await stmt.exec();
        console.log(`工作线程 ${workerId}: 插入了 ${numRecords} 条记录`);

    } catch (error) {
        console.error(`工作线程 ${workerId} 出错:`, error);
        throw error;
    } finally {
        if (stmt) await stmt.close();
        if (conn) await conn.close();
    }
}

无模式写入优化

对于无模式写入,批量处理数据以提高性能:

javascript 复制代码
async function optimizedSchemalessInsert() {
    let conn = null;

    try {
        const wsConfig = new taos.WSConfig('ws://localhost:6041');
        wsConfig.setUser('root');
        wsConfig.setPwd('taosdata');
        wsConfig.setDb('test');
        
        conn = await taos.sqlConnect(wsConfig);
        await conn.exec('CREATE DATABASE IF NOT EXISTS test');

        const batchSize = 1000;
        const lines = [];

        // 生成批量的 InfluxDB 行协议数据
        const now = Date.now() * 1000000; // 转换为纳秒
        
        for (let i = 0; i < batchSize; i++) {
            const timestamp = now + i * 1000000; // 1ms 间隔
            const current = (Math.random() * 10).toFixed(2);
            const voltage = Math.floor(Math.random() * 220) + 200;
            const phase = (Math.random() * 360).toFixed(2);
            
            lines.push(
                `meters,location=California,groupId=1 current=${current},voltage=${voltage}i,phase=${phase} ${timestamp}`
            );
        }

        const startTime = Date.now();
        
        await conn.schemalessInsert(
            lines,
            taos.SchemalessProto.InfluxDBLineProtocol,
            taos.Precision.NANO_SECONDS,
            0 // ttl
        );

        const endTime = Date.now();
        const duration = (endTime - startTime) / 1000;

        console.log(`在 ${duration.toFixed(2)} 秒内插入了 ${batchSize} 条记录`);
        console.log(`性能: ${Math.floor(batchSize / duration)} 条/秒`);

    } catch (error) {
        console.error('无模式插入错误:', error);
        throw error;
    } finally {
        if (conn) await conn.close();
    }
}

预编译语句和参数绑定

高级参数绑定技巧

javascript 复制代码
async function advancedParameterBinding() {
    let conn = null;
    let stmt = null;

    try {
        const wsConfig = new taos.WSConfig('ws://localhost:6041');
        wsConfig.setUser('root');
        wsConfig.setPwd('taosdata');
        wsConfig.setDb('test');
        
        conn = await taos.sqlConnect(wsConfig);

        // 创建包含各种数据类型的表
        await conn.exec('CREATE DATABASE IF NOT EXISTS test');
        await conn.exec('USE test');
        await conn.exec(`
            CREATE STABLE IF NOT EXISTS sensor_data (
                ts TIMESTAMP,
                temperature FLOAT,
                humidity INT,
                pressure DOUBLE,
                status BOOL,
                device_name NCHAR(50),
                raw_data BINARY(100)
            ) TAGS (
                sensor_id NCHAR(50),
                location NCHAR(100),
                install_date TIMESTAMP
            )
        `);

        stmt = await conn.stmtInit();
        
        const prepareSql = `
            INSERT INTO ? USING sensor_data TAGS(?, ?, ?) 
            VALUES(?, ?, ?, ?, ?, ?, ?)
        `;
        await stmt.prepare(prepareSql);

        // 设置表名
        await stmt.setTableName('sensor_001');

        // 设置各种类型的标签
        await stmt.setTags([
            'SENSOR-001',                          // sensor_id (NCHAR)
            'Building A, Floor 3',                 // location (NCHAR)
            BigInt(Date.now() - 86400000 * 30)    // install_date (TIMESTAMP,30天前)
        ]);

        // 绑定多行数据
        const numRows = 10;
        const timestamps = [];
        const temperatures = [];
        const humidities = [];
        const pressures = [];
        const statuses = [];
        const deviceNames = [];
        const rawData = [];

        const baseTimestamp = BigInt(Date.now());

        for (let i = 0; i < numRows; i++) {
            timestamps.push(baseTimestamp + BigInt(i * 1000));
            temperatures.push(20 + Math.random() * 15);
            humidities.push(Math.floor(40 + Math.random() * 40));
            pressures.push(1013.25 + (Math.random() - 0.5) * 20);
            statuses.push(Math.random() > 0.5);
            deviceNames.push(`Device-${i}`);
            rawData.push(`raw_data_${i}`);
        }

        await stmt.bindParam([
            timestamps,
            temperatures,
            humidities,
            pressures,
            statuses,
            deviceNames,
            rawData
        ]);

        await stmt.batch();
        await stmt.exec();

        console.log(`成功插入了 ${numRows} 行各种数据类型的数据`);

    } catch (error) {
        console.error('参数绑定错误:', error);
        throw error;
    } finally {
        if (stmt) await stmt.close();
        if (conn) await conn.close();
    }
}

数据订阅 (TMQ)

高级 TMQ 消费者实现

javascript 复制代码
const taos = require("@tdengine/websocket");

class TDengineTMQConsumer {
    constructor(config) {
        this.config = config;
        this.consumer = null;
        this.running = false;
        this.messageHandlers = [];
    }

    async initialize() {
        try {
            this.consumer = await taos.tmqConnect({
                'group.id': this.config.groupId,
                'client.id': this.config.clientId,
                'td.connect.websocket.scheme': 'ws',
                'td.connect.ip': this.config.host,
                'td.connect.port': this.config.port.toString(),
                'td.connect.user': this.config.user,
                'td.connect.pass': this.config.password,
                'auto.offset.reset': this.config.autoOffsetReset || 'earliest',
                'enable.auto.commit': this.config.enableAutoCommit ? 'true' : 'false',
                'auto.commit.interval.ms': this.config.autoCommitInterval || '5000'
            });

            console.log('TMQ 消费者初始化成功');
        } catch (error) {
            console.error('初始化 TMQ 消费者失败:', error);
            throw error;
        }
    }

    async subscribe(topics) {
        if (!this.consumer) {
            throw new Error('消费者未初始化。请先调用 initialize()。');
        }

        try {
            await this.consumer.subscribe(topics);
            console.log(`已订阅主题: ${topics.join(', ')}`);
        } catch (error) {
            console.error('订阅错误:', error);
            throw error;
        }
    }

    addMessageHandler(handler) {
        this.messageHandlers.push(handler);
    }

    async consume() {
        this.running = true;

        while (this.running) {
            try {
                const message = await this.consumer.poll(1000); // 轮询超时: 1000ms

                if (message) {
                    await this.processMessage(message);
                }
            } catch (error) {
                if (this.running) {
                    console.error('消息消费时出错:', error);
                    await this.sleep(1000); // 重试前等待
                }
            }
        }
    }

    async processMessage(message) {
        try {
            const topicName = message.getTopicName();
            const dbName = message.getDbName();

            // 处理每个数据块
            while (true) {
                const data = await message.fetch();
                if (!data) break;

                const tableName = message.getTableName();
                const fields = data.fields;
                const rows = [];

                // 从数据块中提取所有行
                while (await data.next()) {
                    const row = data.getData();
                    rows.push(row);
                }

                // 调用注册的处理器
                for (const handler of this.messageHandlers) {
                    await handler({
                        topic: topicName,
                        database: dbName,
                        table: tableName,
                        fields: fields,
                        rows: rows
                    });
                }
            }
        } catch (error) {
            console.error('处理消息时出错:', error);
        }
    }

    async commit() {
        if (this.consumer) {
            try {
                await this.consumer.commit();
            } catch (error) {
                console.error('提交错误:', error);
            }
        }
    }

    async stop() {
        this.running = false;
        
        if (this.consumer) {
            try {
                await this.consumer.unsubscribe();
                await this.consumer.close();
                console.log('TMQ 消费者已停止');
            } catch (error) {
                console.error('停止消费者时出错:', error);
            }
        }
    }

    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 使用示例
async function tmqConsumerExample() {
    const consumer = new TDengineTMQConsumer({
        groupId: 'group1',
        clientId: 'client1',
        host: '127.0.0.1',
        port: 6041,
        user: 'root',
        password: 'taosdata',
        autoOffsetReset: 'earliest',
        enableAutoCommit: false,
        autoCommitInterval: 5000
    });

    // 添加消息处理器
    consumer.addMessageHandler(async (message) => {
        console.log(`从主题接收: ${message.topic}`);
        console.log(`数据库: ${message.database}, 表: ${message.table}`);
        console.log(`行数: ${message.rows.length}`);
        
        // 处理行
        for (const row of message.rows) {
            // 自定义业务逻辑
            console.log('行数据:', row);
        }
    });

    try {
        await consumer.initialize();
        await consumer.subscribe(['topic_meters']);
        
        // 开始消费(这将无限期运行)
        await consumer.consume();
    } catch (error) {
        console.error('TMQ 消费者错误:', error);
    } finally {
        await consumer.stop();
    }
}

// 优雅关闭处理
process.on('SIGINT', async () => {
    console.log('收到 SIGINT,正在优雅关闭...');
    // 在这里停止消费者
    process.exit(0);
});

多消费组管理

javascript 复制代码
class TMQConsumerManager {
    constructor() {
        this.consumers = new Map();
    }

    async createConsumer(consumerId, config, topics) {
        const consumer = new TDengineTMQConsumer(config);
        await consumer.initialize();
        await consumer.subscribe(topics);
        
        this.consumers.set(consumerId, consumer);
        
        // 在后台开始消费
        consumer.consume().catch(error => {
            console.error(`消费者 ${consumerId} 出错:`, error);
        });

        return consumer;
    }

    async stopConsumer(consumerId) {
        const consumer = this.consumers.get(consumerId);
        if (consumer) {
            await consumer.stop();
            this.consumers.delete(consumerId);
        }
    }

    async stopAll() {
        const stopPromises = [];
        for (const [consumerId, consumer] of this.consumers.entries()) {
            console.log(`停止消费者: ${consumerId}`);
            stopPromises.push(consumer.stop());
        }
        await Promise.all(stopPromises);
        this.consumers.clear();
    }
}

错误处理和重试策略

综合错误处理

javascript 复制代码
class TDengineErrorHandler {
    static isRetryableError(error) {
        // 检查错误是否与网络相关或是临时性的
        const retryableErrors = [
            'ECONNREFUSED',
            'ETIMEDOUT',
            'ENOTFOUND',
            'ENETUNREACH',
            'Connection timeout',
            'Network error'
        ];

        const errorMessage = error.message || error.toString();
        return retryableErrors.some(msg => errorMessage.includes(msg));
    }

    static async executeWithRetry(operation, maxRetries = 3, delayMs = 1000) {
        let lastError;

        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                return await operation();
            } catch (error) {
                lastError = error;
                console.error(`尝试 ${attempt}/${maxRetries} 失败:`, error.message);

                if (attempt < maxRetries && this.isRetryableError(error)) {
                    const delay = delayMs * Math.pow(2, attempt - 1); // 指数退避
                    console.log(`${delay}ms 后重试...`);
                    await this.sleep(delay);
                } else {
                    break;
                }
            }
        }

        throw lastError;
    }

    static async executeWithCircuitBreaker(operation, config = {}) {
        const {
            failureThreshold = 5,
            successThreshold = 2,
            timeout = 60000,
            resetTimeout = 30000
        } = config;

        if (!this.circuitBreakerState) {
            this.circuitBreakerState = {
                failures: 0,
                successes: 0,
                state: 'CLOSED', // CLOSED, OPEN, HALF_OPEN
                nextAttempt: Date.now()
            };
        }

        const state = this.circuitBreakerState;

        // 检查断路器是否打开
        if (state.state === 'OPEN') {
            if (Date.now() < state.nextAttempt) {
                throw new Error('断路器处于 OPEN 状态');
            }
            // 尝试移至 HALF_OPEN 状态
            state.state = 'HALF_OPEN';
            state.successes = 0;
        }

        try {
            const result = await Promise.race([
                operation(),
                this.timeoutPromise(timeout)
            ]);

            // 操作成功
            if (state.state === 'HALF_OPEN') {
                state.successes++;
                if (state.successes >= successThreshold) {
                    state.state = 'CLOSED';
                    state.failures = 0;
                    console.log('断路器移至 CLOSED 状态');
                }
            } else {
                state.failures = 0;
            }

            return result;
        } catch (error) {
            state.failures++;
            state.successes = 0;

            if (state.failures >= failureThreshold) {
                state.state = 'OPEN';
                state.nextAttempt = Date.now() + resetTimeout;
                console.error(`断路器在 ${state.failures} 次失败后打开`);
            }

            throw error;
        }
    }

    static sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    static timeoutPromise(ms) {
        return new Promise((_, reject) =>
            setTimeout(() => reject(new Error('操作超时')), ms)
        );
    }
}

// 使用示例
async function errorHandlingExample() {
    // 示例 1: 简单重试
    try {
        const result = await TDengineErrorHandler.executeWithRetry(async () => {
            const wsConfig = new taos.WSConfig('ws://localhost:6041');
            wsConfig.setUser('root');
            wsConfig.setPwd('taosdata');
            const conn = await taos.sqlConnect(wsConfig);
            return conn;
        }, 3, 1000);
        
        console.log('连接成功建立');
    } catch (error) {
        console.error('所有重试后失败:', error);
    }

    // 示例 2: 断路器模式
    try {
        const result = await TDengineErrorHandler.executeWithCircuitBreaker(async () => {
            // 你的操作在这里
            const wsConfig = new taos.WSConfig('ws://localhost:6041');
            const conn = await taos.sqlConnect(wsConfig);
            return await conn.query('SELECT * FROM test.meters LIMIT 10');
        }, {
            failureThreshold: 5,
            successThreshold: 2,
            timeout: 5000,
            resetTimeout: 30000
        });
    } catch (error) {
        console.error('断路器错误:', error);
    }
}

性能优化

查询结果流式处理

对于大型结果集,以流式方式处理数据以最小化内存使用:

javascript 复制代码
async function streamingQueryExample() {
    let conn = null;

    try {
        const wsConfig = new taos.WSConfig('ws://localhost:6041');
        wsConfig.setUser('root');
        wsConfig.setPwd('taosdata');
        wsConfig.setDb('test');
        
        conn = await taos.sqlConnect(wsConfig);

        const sql = 'SELECT * FROM meters WHERE ts >= NOW - 1d';
        const wsRows = await conn.query(sql);

        let rowCount = 0;
        let batchCount = 0;
        const batchSize = 1000;
        let batch = [];

        // 流式处理
        while (await wsRows.next()) {
            const row = wsRows.getData();
            batch.push(row);
            rowCount++;

            if (batch.length >= batchSize) {
                // 处理批次
                await processBatch(batch);
                batchCount++;
                batch = [];
                
                if (batchCount % 10 === 0) {
                    console.log(`已处理 ${rowCount} 行...`);
                }
            }
        }

        // 处理剩余行
        if (batch.length > 0) {
            await processBatch(batch);
        }

        await wsRows.close();
        console.log(`总共处理的行数: ${rowCount}`);

    } catch (error) {
        console.error('流式查询错误:', error);
    } finally {
        if (conn) await conn.close();
    }
}

async function processBatch(batch) {
    // 自定义批处理逻辑
    // 例如:聚合、转换或写入另一个系统
    console.log(`处理 ${batch.length} 行的批次`);
}

缓存策略

为频繁访问的数据实现智能缓存:

javascript 复制代码
class TDengineQueryCache {
    constructor(maxSize = 100, ttlMs = 60000) {
        this.cache = new Map();
        this.maxSize = maxSize;
        this.ttlMs = ttlMs;
    }

    generateKey(sql, params = []) {
        return `${sql}:${JSON.stringify(params)}`;
    }

    get(sql, params = []) {
        const key = this.generateKey(sql, params);
        const cached = this.cache.get(key);

        if (!cached) return null;

        // 检查缓存条目是否过期
        if (Date.now() - cached.timestamp > this.ttlMs) {
            this.cache.delete(key);
            return null;
        }

        console.log('查询缓存命中:', sql);
        return cached.data;
    }

    set(sql, params = [], data) {
        const key = this.generateKey(sql, params);

        // 如果缓存已满,实现 LRU 淘汰
        if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }

        this.cache.set(key, {
            data: data,
            timestamp: Date.now()
        });
    }

    clear() {
        this.cache.clear();
    }

    invalidate(pattern) {
        for (const key of this.cache.keys()) {
            if (key.includes(pattern)) {
                this.cache.delete(key);
            }
        }
    }
}

// 与连接池一起使用
class CachedTDenginePool extends TDengineConnectionPool {
    constructor(config, poolSize = 10) {
        super(config, poolSize);
        this.cache = new TDengineQueryCache(100, 60000);
    }

    async executeQuery(sql, reqId, useCache = true) {
        if (useCache) {
            const cachedResult = this.cache.get(sql);
            if (cachedResult) {
                return cachedResult;
            }
        }

        const result = await super.executeQuery(sql, reqId);

        if (useCache) {
            this.cache.set(sql, [], result);
        }

        return result;
    }

    invalidateCache(pattern) {
        this.cache.invalidate(pattern);
    }
}

监控和指标收集

javascript 复制代码
class TDengineMetricsCollector {
    constructor() {
        this.metrics = {
            totalQueries: 0,
            successfulQueries: 0,
            failedQueries: 0,
            totalQueryTime: 0,
            slowQueries: [],
            errorsByType: new Map()
        };
        this.slowQueryThreshold = 1000; // ms
    }

    recordQuery(sql, duration, success, error = null) {
        this.metrics.totalQueries++;

        if (success) {
            this.metrics.successfulQueries++;
        } else {
            this.metrics.failedQueries++;
            
            if (error) {
                const errorType = error.constructor.name;
                this.metrics.errorsByType.set(
                    errorType,
                    (this.metrics.errorsByType.get(errorType) || 0) + 1
                );
            }
        }

        this.metrics.totalQueryTime += duration;

        if (duration > this.slowQueryThreshold) {
            this.metrics.slowQueries.push({
                sql: sql,
                duration: duration,
                timestamp: Date.now()
            });

            // 仅保留最近 100 个慢查询
            if (this.metrics.slowQueries.length > 100) {
                this.metrics.slowQueries.shift();
            }
        }
    }

    getMetrics() {
        const avgQueryTime = this.metrics.totalQueries > 0
            ? this.metrics.totalQueryTime / this.metrics.totalQueries
            : 0;

        return {
            totalQueries: this.metrics.totalQueries,
            successfulQueries: this.metrics.successfulQueries,
            failedQueries: this.metrics.failedQueries,
            successRate: this.metrics.totalQueries > 0
                ? (this.metrics.successfulQueries / this.metrics.totalQueries * 100).toFixed(2) + '%'
                : '0%',
            avgQueryTime: avgQueryTime.toFixed(2) + 'ms',
            slowQueryCount: this.metrics.slowQueries.length,
            errorsByType: Object.fromEntries(this.metrics.errorsByType)
        };
    }

    reset() {
        this.metrics = {
            totalQueries: 0,
            successfulQueries: 0,
            failedQueries: 0,
            totalQueryTime: 0,
            slowQueries: [],
            errorsByType: new Map()
        };
    }

    printReport() {
        const metrics = this.getMetrics();
        console.log('\n=== TDengine 指标报告 ===');
        console.log(`总查询数: ${metrics.totalQueries}`);
        console.log(`成功: ${metrics.successfulQueries}`);
        console.log(`失败: ${metrics.failedQueries}`);
        console.log(`成功率: ${metrics.successRate}`);
        console.log(`平均查询时间: ${metrics.avgQueryTime}`);
        console.log(`慢查询 (>${this.slowQueryThreshold}ms): ${metrics.slowQueryCount}`);
        
        if (Object.keys(metrics.errorsByType).length > 0) {
            console.log('\n按类型分类的错误:');
            for (const [type, count] of Object.entries(metrics.errorsByType)) {
                console.log(`  ${type}: ${count}`);
            }
        }

        if (this.metrics.slowQueries.length > 0) {
            console.log('\n前 5 个最慢的查询:');
            const topSlow = this.metrics.slowQueries
                .sort((a, b) => b.duration - a.duration)
                .slice(0, 5);
            
            topSlow.forEach((query, index) => {
                console.log(`  ${index + 1}. ${query.duration.toFixed(2)}ms - ${query.sql.substring(0, 80)}...`);
            });
        }
        console.log('================================\n');
    }
}

// 与连接池集成
class MonitoredTDenginePool extends TDengineConnectionPool {
    constructor(config, poolSize = 10) {
        super(config, poolSize);
        this.metrics = new TDengineMetricsCollector();
    }

    async executeQuery(sql, reqId) {
        const startTime = Date.now();
        let success = false;
        let error = null;

        try {
            const result = await super.executeQuery(sql, reqId);
            success = true;
            return result;
        } catch (err) {
            error = err;
            throw err;
        } finally {
            const duration = Date.now() - startTime;
            this.metrics.recordQuery(sql, duration, success, error);
        }
    }

    getMetrics() {
        return this.metrics.getMetrics();
    }

    printMetricsReport() {
        this.metrics.printReport();
    }

    resetMetrics() {
        this.metrics.reset();
    }
}

生产环境部署最佳实践

配置管理

javascript 复制代码
// config.js - 集中式配置管理
class TDengineConfig {
    constructor(environment = 'development') {
        this.environment = environment;
        this.config = this.loadConfig();
    }

    loadConfig() {
        const configs = {
            development: {
                url: 'ws://localhost:6041',
                user: 'root',
                password: 'taosdata',
                database: 'test',
                timeout: 5000,
                poolSize: 5,
                enableHealthCheck: true,
                healthCheckInterval: 30000,
                maxRetries: 3,
                retryDelay: 1000,
                enableMetrics: true,
                logLevel: 'debug'
            },
            production: {
                url: process.env.TDENGINE_URL || 'ws://tdengine-server:6041',
                user: process.env.TDENGINE_USER || 'root',
                password: process.env.TDENGINE_PASSWORD,
                database: process.env.TDENGINE_DATABASE || 'production',
                timeout: parseInt(process.env.TDENGINE_TIMEOUT) || 10000,
                poolSize: parseInt(process.env.TDENGINE_POOL_SIZE) || 20,
                enableHealthCheck: true,
                healthCheckInterval: 60000,
                maxRetries: 5,
                retryDelay: 2000,
                enableMetrics: true,
                logLevel: process.env.LOG_LEVEL || 'info',
                enableSSL: process.env.TDENGINE_SSL === 'true'
            },
            test: {
                url: 'ws://localhost:6041',
                user: 'root',
                password: 'taosdata',
                database: 'test',
                timeout: 3000,
                poolSize: 3,
                enableHealthCheck: false,
                maxRetries: 1,
                retryDelay: 500,
                enableMetrics: false,
                logLevel: 'error'
            }
        };

        return configs[this.environment] || configs.development;
    }

    get(key) {
        return this.config[key];
    }

    validate() {
        const required = ['url', 'user', 'password', 'database'];
        const missing = required.filter(key => !this.config[key]);

        if (missing.length > 0) {
            throw new Error(`缺少必需的配置: ${missing.join(', ')}`);
        }
    }
}

module.exports = TDengineConfig;

优雅关闭

javascript 复制代码
class TDengineService {
    constructor(config) {
        this.config = config;
        this.pool = null;
        this.consumers = new Map();
        this.isShuttingDown = false;
    }

    async start() {
        console.log('正在启动 TDengine 服务...');
        
        this.pool = new MonitoredTDenginePool(
            this.config,
            this.config.get('poolSize')
        );

        await this.pool.initialize();
        
        // 注册关闭处理程序
        this.registerShutdownHandlers();
        
        console.log('TDengine 服务启动成功');
    }

    registerShutdownHandlers() {
        const shutdown = async (signal) => {
            if (this.isShuttingDown) {
                console.log('关闭已在进行中...');
                return;
            }

            console.log(`\n收到 ${signal},开始优雅关闭...`);
            this.isShuttingDown = true;

            try {
                // 停止接受新请求
                console.log('停止新请求...');

                // 停止所有 TMQ 消费者
                console.log('停止 TMQ 消费者...');
                for (const [id, consumer] of this.consumers.entries()) {
                    await consumer.stop();
                }

                // 打印最终指标
                if (this.pool && this.pool.printMetricsReport) {
                    this.pool.printMetricsReport();
                }

                // 关闭连接池
                console.log('关闭连接池...');
                if (this.pool) {
                    await this.pool.close();
                }

                console.log('优雅关闭完成');
                process.exit(0);
            } catch (error) {
                console.error('关闭时出错:', error);
                process.exit(1);
            }
        };

        process.on('SIGTERM', () => shutdown('SIGTERM'));
        process.on('SIGINT', () => shutdown('SIGINT'));
        
        // 处理未捕获的异常
        process.on('uncaughtException', (error) => {
            console.error('未捕获的异常:', error);
            shutdown('uncaughtException');
        });

        process.on('unhandledRejection', (reason, promise) => {
            console.error('未处理的拒绝:', promise, '原因:', reason);
            shutdown('unhandledRejection');
        });
    }

    async executeQuery(sql, reqId) {
        if (this.isShuttingDown) {
            throw new Error('服务正在关闭');
        }
        return await this.pool.executeQuery(sql, reqId);
    }
}

// 使用
async function main() {
    const config = new TDengineConfig(process.env.NODE_ENV || 'development');
    config.validate();

    const service = new TDengineService(config);
    await service.start();

    // 你的应用逻辑在这里
}

main().catch(console.error);

日志记录最佳实践

javascript 复制代码
class TDengineLogger {
    constructor(level = 'info') {
        this.levels = {
            error: 0,
            warn: 1,
            info: 2,
            debug: 3
        };
        this.currentLevel = this.levels[level] || this.levels.info;
    }

    formatMessage(level, message, meta = {}) {
        return JSON.stringify({
            timestamp: new Date().toISOString(),
            level: level,
            message: message,
            ...meta
        });
    }

    error(message, meta = {}) {
        if (this.currentLevel >= this.levels.error) {
            console.error(this.formatMessage('ERROR', message, meta));
        }
    }

    warn(message, meta = {}) {
        if (this.currentLevel >= this.levels.warn) {
            console.warn(this.formatMessage('WARN', message, meta));
        }
    }

    info(message, meta = {}) {
        if (this.currentLevel >= this.levels.info) {
            console.log(this.formatMessage('INFO', message, meta));
        }
    }

    debug(message, meta = {}) {
        if (this.currentLevel >= this.levels.debug) {
            console.log(this.formatMessage('DEBUG', message, meta));
        }
    }
}

// 全局日志记录器实例
const logger = new TDengineLogger(process.env.LOG_LEVEL || 'info');

module.exports = logger;

监控和调试

实时性能仪表板

javascript 复制代码
class PerformanceDashboard {
    constructor(pool) {
        this.pool = pool;
        this.updateInterval = 5000; // 每 5 秒更新一次
        this.timer = null;
    }

    start() {
        console.log('启动性能仪表板...');
        this.display();
        
        this.timer = setInterval(() => {
            this.display();
        }, this.updateInterval);
    }

    stop() {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }

    display() {
        console.clear();
        console.log('╔════════════════════════════════════════════════════════════╗');
        console.log('║        TDengine 性能仪表板                                  ║');
        console.log('╠════════════════════════════════════════════════════════════╣');
        
        const metrics = this.pool.getMetrics();
        
        console.log(`║ 总查询数:             ${this.pad(metrics.totalQueries, 30)} ║`);
        console.log(`║ 成功:                 ${this.pad(metrics.successfulQueries, 30)} ║`);
        console.log(`║ 失败:                 ${this.pad(metrics.failedQueries, 30)} ║`);
        console.log(`║ 成功率:               ${this.pad(metrics.successRate, 30)} ║`);
        console.log(`║ 平均查询时间:         ${this.pad(metrics.avgQueryTime, 30)} ║`);
        console.log(`║ 慢查询:               ${this.pad(metrics.slowQueryCount, 30)} ║`);
        
        console.log('╠════════════════════════════════════════════════════════════╣');
        console.log(`║ 时间戳:               ${this.pad(new Date().toISOString(), 30)} ║`);
        console.log('╚════════════════════════════════════════════════════════════╝');
    }

    pad(value, length) {
        const str = String(value);
        return str + ' '.repeat(Math.max(0, length - str.length));
    }
}

// 使用
async function monitoringExample() {
    const config = new TDengineConfig();
    const pool = new MonitoredTDenginePool(config, 10);
    await pool.initialize();

    const dashboard = new PerformanceDashboard(pool);
    dashboard.start();

    // 模拟工作负载
    setInterval(async () => {
        try {
            await pool.executeQuery('SELECT * FROM test.meters LIMIT 10');
        } catch (error) {
            console.error('查询错误:', error);
        }
    }, 100);

    // 1 分钟后停止仪表板
    setTimeout(() => {
        dashboard.stop();
        pool.printMetricsReport();
    }, 60000);
}

总结

本进阶指南涵盖了:

  1. 连接池管理:带健康检查的高效连接池
  2. 高性能批量写入:批量数据插入的优化技术
  3. 预编译语句:各种数据类型的高级参数绑定
  4. 数据订阅:全面的 TMQ 消费者实现
  5. 错误处理:重试策略和断路器模式
  6. 性能优化:流式查询、缓存和指标收集
  7. 生产最佳实践:配置管理、优雅关闭和日志记录
  8. 监控:实时性能仪表板和调试工具

通过遵循这些模式和实践,你可以使用 Node.js 构建稳健、高性能的 TDengine 应用程序。

其他资源

关于 TDengine

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
SongYuLong的博客2 小时前
openwrt 启动脚本
linux·运维·服务器·物联网
山峰哥2 小时前
JOIN - 多表关联的魔法——3000字实战指南
java·大数据·开发语言·数据库·sql·编辑器
龙亘川2 小时前
SL/T830-2024 实操指南:水闸安全应急管理的标准化路径
大数据·人工智能·水闸安全管理应急预案技术导则
banjin3 小时前
轻量化时序数据库新选择:KaiwuDB-Lite 实战体验
数据库·oracle·边缘计算·时序数据库·kaiwudb·kwdb
首席拯救HMI官3 小时前
【拯救HMI】HMI容错设计:如何减少操作失误并快速纠错?
大数据·运维·前端·javascript·网络·学习
zgl_200537793 小时前
源代码:ZGLanguage 解析SQL数据血缘 之 显示 UNION SQL 结构图
大数据·数据库·数据仓库·sql·数据治理·sql解析·数据血缘
柯南小海盗3 小时前
Elasticsearch同义词配置全攻略
大数据·elasticsearch·jenkins
LJ97951113 小时前
告别发布焦虑:用AI开启“轻量化”精准媒体沟通时代
大数据·人工智能
AI营销实验室3 小时前
原圈科技AI CRM系统深度解析:告别单点智能,构建AI协同作战体系
大数据·人工智能