如何动态选择连接的MySQL服务器?垂直扩展与读写分离方案对比
方案可行性与选型建议
代码实现方案评估
你倾向的方案2(预创建两条固定连接,按请求类型路由)是完全可行的,也是当前场景下的最优实现,相比方案1优势明显:
- 避免了动态创建连接的重复连接、资源泄漏风险
- 实现逻辑简单,对现有业务代码侵入性极低
- 连接初始化在服务启动阶段完成,不会增加请求链路耗时
参考实现代码
你可以把原有的单连接配置修改为读写双连接导出:
const mysql = require('mysql2'); const Promise = require('bluebird'); // 写节点(Source)连接配置 const writeConnection = mysql.createConnection({ host: 'SOURCE_DB_HOST', port: 3306, user: '****', password: '*****', database: 'qandapi', }); const writeDb = Promise.promisifyAll(writeConnection, { multiArgs: true }); writeDb.connectAsync().then(() => console.log(`Connected to Source DB as ID ${writeDb.threadId}`) ); // 读节点(Replica)连接配置 const readConnection = mysql.createConnection({ host: 'REPLICA_DB_HOST', port: 3306, user: '****', password: '*****', database: 'qandapi', }); const readDb = Promise.promisifyAll(readConnection, { multiArgs: true }); readDb.connectAsync().then(() => console.log(`Connected to Replica DB as ID ${readDb.threadId}`) ); // 导出两个连接实例 module.exports = { writeDb, readDb };
再添加一个简易Express中间件挂载对应数据库实例到请求对象:
// 数据库路由中间件 const dbRouter = (req, res, next) => { if (req.method === 'POST' || req.method === 'PUT' || req.method === 'DELETE') { req.db = writeDb; } else { req.db = readDb; } next(); } // 全局注册中间件 app.use(dbRouter);
后续业务代码直接使用req.db操作数据库即可,不需要额外修改逻辑。
扩展方案选型对比
垂直扩展(升级数据库EC2规格)
- 适用场景:当前数据库瓶颈不严重、写请求占比超过20%、业务有强读写一致需求(无法接受主从同步延迟)
- 优势:零代码改造、无架构复杂度增加、生效极快
- 劣势:长期来看升配性价比会逐步降低,单实例规格有上限,无法应对持续增长的读流量
水平扩展(Source/Replica读写分离)
- 适用场景:读请求占比超过70%、单数据库实例CPU负载主要由读请求产生、业务可接受毫秒级的主从同步延迟
- 优势:长期扩展性更强,读流量增长时只需新增Replica节点即可,成本可控
- 劣势:需要少量代码改造,需要处理主从同步延迟导致的读写一致问题
性能对比结论
只要配置正确,两种方案的性能都远优于当前的单t2.mysql实例,不会出现性能更差的情况:
- 如果是读占比极高的场景,读写分离的性能表现会优于同成本的垂直扩展方案
- 如果写占比高,垂直扩展的性价比更高,也不会有主从同步的额外开销
落地建议
优先尝试垂直扩展:先将MySQL实例升级到t3.medium规格,成本增加极低,不需要修改任何代码即可快速验证性能是否满足需求。如果升级后仍然出现读请求打满CPU的情况,再落地读写分离方案即可。
内容的提问来源于stack exchange,提问作者Nate Schroeder
相关产品推荐
相关产品推荐

