NodeJS应用在MySQL主从故障切换后无法更新数据库连接
问题根源与解决方案
你的问题确实是Node.js的DNS缓存机制导致的。Node.js默认会缓存DNS解析结果,且默认缓存时长较长(TTL由DNS服务器返回,但Node.js不会主动刷新)。当集群主节点切换后,虽然域名已指向新主,但未重启的Node.js进程仍会使用缓存的旧IP连接只读的旧主节点,从而触发写操作错误。
以下是几种可行的解决方案:
1. 连接池层面的实时校验(推荐生产环境使用)
利用mysql包的连接池配置,在每次获取连接前校验节点是否可写,若检测到只读则丢弃旧连接,强制重新建立新连接(此时会重新解析DNS获取新主IP):
const mysql = require('mysql'); const poolConfig = { host: '你的集群域名', user: '数据库账号', password: '数据库密码', database: '目标库', connectionLimit: 10, testOnBorrow: true, // 从连接池取连接前执行校验 acquireTimeout: 3000, // 自定义校验逻辑 validateConnection: function(conn) { return new Promise((resolve, reject) => { conn.query('SELECT @@global.read_only AS is_read_only', (err, rows) => { if (err) { reject(err); return; } // 如果节点是只读状态,拒绝使用该连接 resolve(rows[0].is_read_only !== 1); }); }); } }; const pool = mysql.createPool(poolConfig);
当校验发现连接指向的是只读节点时,连接池会自动丢弃该连接,重新创建新连接,此时会重新解析DNS拿到新主节点的IP。
2. 捕获只读错误时重置连接池
在应用中捕获"read only"错误,主动清空现有连接池并重新初始化,强制触发DNS重新解析:
// 监听连接池错误 pool.on('error', (err) => { if (err.message.includes('read only')) { // 关闭现有连接池 pool.end(() => { // 重新初始化连接池 pool = mysql.createPool(poolConfig); }); } }); // 或者在查询时捕获错误 function executeQuery(sql) { return new Promise((resolve, reject) => { pool.query(sql, (err, results) => { if (err && err.message.includes('read only')) { // 重置连接池并重试 pool.end(() => { pool = mysql.createPool(poolConfig); pool.query(sql, (retryErr, retryResults) => { retryErr ? reject(retryErr) : resolve(retryResults); }); }); } else { err ? reject(err) : resolve(results); } }); }); }
3. 临时禁用DNS缓存(应急方案)
启动Node.js应用时添加环境变量,强制禁用DNS缓存,每次请求都会重新解析域名:
NODE_DISABLEDNSCACHE=1 node your-app.js
⚠️ 注意:该方案会增加DNS查询开销,影响应用性能,仅建议作为临时应急手段,不适合长期生产环境使用。
4. 更换支持自动故障转移的客户端库
考虑切换到mysql2(mysql包的升级替代)或@mysql/xdevapi,这些库提供了更灵活的连接管理和故障转移支持,更容易实现动态地址切换逻辑。比如mysql2的连接池可以配合自定义的地址解析逻辑,定期刷新DNS结果。
内容的提问来源于stack exchange,提问作者Gagan
相关产品推荐
相关产品推荐

