
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);
}
总结
本进阶指南涵盖了:
- 连接池管理:带健康检查的高效连接池
- 高性能批量写入:批量数据插入的优化技术
- 预编译语句:各种数据类型的高级参数绑定
- 数据订阅:全面的 TMQ 消费者实现
- 错误处理:重试策略和断路器模式
- 性能优化:流式查询、缓存和指标收集
- 生产最佳实践:配置管理、优雅关闭和日志记录
- 监控:实时性能仪表板和调试工具
通过遵循这些模式和实践,你可以使用 Node.js 构建稳健、高性能的 TDengine 应用程序。
其他资源
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。