MST

星途 面试题库

面试题:Node.js 数据库连接池异常处理与高可用设计

假设在 Node.js 应用中使用数据库连接池时,出现了数据库服务器短暂不可用、连接泄露等异常情况。请详细说明如何设计一套完善的异常处理机制,确保应用的高可用性,同时描述在分布式系统中,如何协同多个 Node.js 实例的数据库连接池,避免资源竞争。
23.9万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

处理数据库服务器短暂不可用

  1. 重试机制
    • 当检测到数据库连接失败(由于服务器短暂不可用)时,采用指数退避算法进行重试。例如:
    const mysql = require('mysql');
    const pool = mysql.createPool({
      host: 'your - host',
      user: 'your - user',
      password: 'your - password',
      database: 'your - database'
    });
    
    function queryWithRetry(sql, values, retries = 3, delay = 1000) {
      return new Promise((resolve, reject) => {
        const attempt = () => {
          pool.getConnection((err, connection) => {
            if (err) {
              if (retries > 0) {
                setTimeout(() => {
                  attempt();
                }, delay);
                delay = delay * 2;
                retries--;
              } else {
                reject(err);
              }
            } else {
              connection.query(sql, values, (queryErr, results) => {
                connection.release();
                if (queryErr) {
                  reject(queryErr);
                } else {
                  resolve(results);
                }
              });
            }
          });
        };
        attempt();
      });
    }
    
  2. 健康检查
    • 定期(例如每隔一定时间间隔,如10秒)通过执行简单的SQL查询(如SELECT 1)来检查数据库服务器的可用性。如果查询失败,标记数据库为不可用,并触发重试机制。
    setInterval(() => {
      pool.getConnection((err, connection) => {
        if (err) {
          console.error('Database connection error during health check:', err);
          return;
        }
        connection.query('SELECT 1', (queryErr, results) => {
          connection.release();
          if (queryErr) {
            console.error('Database seems unavailable during health check:', queryErr);
          } else {
            console.log('Database is available.');
          }
        });
      });
    }, 10000);
    

处理连接泄露

  1. 连接超时设置
    • 在连接池配置中设置连接的最大使用时间(例如10分钟)。如果一个连接使用时间超过这个限制,强制回收该连接。
    const pool = mysql.createPool({
      host: 'your - host',
      user: 'your - user',
      password: 'your - password',
      database: 'your - database',
      connectionLimit: 10,
      acquireTimeout: 600000 // 10分钟
    });
    
  2. 监控与日志记录
    • 实现一个监控系统,记录每个连接的获取时间、释放时间和使用时长。如果发现有连接长时间未释放,通过日志输出详细信息,以便排查问题。
    const connectionMap = new Map();
    pool.getConnection((err, connection) => {
      if (err) {
        console.error('Error getting connection:', err);
      } else {
        const startTime = new Date();
        connectionMap.set(connection, startTime);
        connection.on('release', () => {
          const endTime = new Date();
          const duration = endTime - startTime;
          console.log(`Connection used for ${duration} ms`);
          connectionMap.delete(connection);
        });
      }
    });
    

分布式系统中多 Node.js 实例连接池协同避免资源竞争

  1. 集中式配置管理
    • 使用一个集中式的配置管理工具(如Consul、Etcd等)来存储数据库连接池的配置信息,包括数据库地址、用户名、密码、连接池大小等。所有Node.js实例从这个集中式配置中心获取配置,确保配置的一致性。
  2. 分布式锁
    • 利用分布式锁(如基于Redis的分布式锁)来控制数据库连接资源的获取。在每个Node.js实例获取数据库连接之前,先尝试获取分布式锁。如果获取成功,则可以安全地获取数据库连接;如果获取失败,则等待一段时间后重试。
    • 例如,使用ioredis库实现基于Redis的分布式锁:
    const Redis = require('ioredis');
    const redis = new Redis();
    
    async function getConnectionWithLock(pool) {
      const lockKey = 'database - connection - lock';
      const lockValue = Date.now();
      const lockAcquired = await redis.set(lockKey, lockValue, 'NX', 'EX', 10);
      if (lockAcquired) {
        try {
          return await new Promise((resolve, reject) => {
            pool.getConnection((err, connection) => {
              if (err) {
                reject(err);
              } else {
                resolve(connection);
              }
            });
          });
        } finally {
          await redis.del(lockKey);
        }
      } else {
        await new Promise(resolve => setTimeout(resolve, 100));
        return getConnectionWithLock(pool);
      }
    }
    
  3. 连接池分区
    • 根据一定的规则(如按数据分片、按业务模块等)对数据库连接池进行分区。每个Node.js实例负责管理和使用特定分区的连接池,避免不同实例之间对连接资源的竞争。例如,对于一个分库分表的数据库,可以让不同的Node.js实例负责不同库或表的连接管理。