读写分离与负载均衡的核心概念与目的
读写分离与负载均衡是数据库优化的关键策略,二者目标不同但紧密关联
读写分离的核心是将数据库的读操作(select)与写操作(insert/update/delete)分离
- 写操作仅在主库执行
- 读操作则分配到从库
负载均衡则解决读操作在多个从库间的分配问题,避免单点压力
主从复制配置的核心目的是分担主库读负载——大多数应用中,读负载远高于写负载
写操作必须在主库执行以保证数据一致性,而读操作可在主库或从库执行,读写分离通过减少主库读压力,使其专注写操作,从而优化性能,完成读写分离后,读操作需在多个从服务器间分配,这需要读负载均衡策略
读写分离的必要性与实现逻辑
读写分离的必要性:
- 写操作不可分担:写操作必须在主库执行,以保证数据一致性
- 读操作灵活性:读操作可在主库或从库执行
- 优化主库性能:减少主库读压力,使其专注写操作,提升整体吞吐量
实现逻辑:
- 操作需明确区分:写操作路由至主库,读操作尽量路由至从库
- 主从复制延迟(毫秒级)对实时性敏感查询影响显著,此类查询需在主库执行
读写分离的实现方式及优缺点
1 ) 方式一:程序层实现读写分离
优点:
- 灵活控制:开发人员可精准判断查询路由(例如,实时性敏感查询强制走主库)
- 性能无损:直连数据库,无额外性能损耗,架构简单易维护,无新故障点
- 故障点少:不引入新中间层,降低系统复杂度
缺点:
- 开发成本高:需维护多数据库连接,增加代码复杂度及工作量
- 人为错误风险:开发人员可能误用数据库连接(如读操作误路由至主库)
- 维护困难:修改连接配置需重启应用,影响业务连续性
2 ) 方式二:中间件实现读写分离
常用工具:
- mysql proxy(官方实验性工具,性能稳定性差,不推荐生产使用)
- maxscale(mariadb 提供,免费且兼容 mysql,支持读写分离与读负载均衡)
优点:
- 对程序透明:应用仅连接中间件,无需修改代码,降低迁移成本
- 自动化路由:通过 sql 语法分析(如 select 路由至从库,非 select 路由至主库)
- 集成负载均衡:支持多从库读请求分配(如轮询、权重策略)
缺点:
- 性能损耗:中间件需解析 sql 及权限认证,qps 可能下降 50%~70%(需基准测试验证)
- 延迟敏感查询处理不足:无法自动识别实时性敏感查询(如存储过程),需程序添加路由提示,需通过sql提示关键字(如
/*master*/)强制路由,但需修改程序 - 依赖中间件稳定性:高并发下可能成为性能瓶颈
- 存储过程限制:无法解析存储过程内的操作类型,所有存储过程默认路由到主库执行,增加主库负载
原生sql示例(存储过程导致的读写分离局限)
-- 存储过程无法通过语法区分读写,只能路由至主库 delimiter // create procedure getorderdetails(in orderid int) begin select * from orders where id = orderid; -- 读操作,但中间件无法识别 update orders set status = 'processed' where id = orderid; -- 写操作 end // delimiter ;
选型建议:中间件上线前需严格基准测试(如sysbench压测),避免性能瓶颈。读写分离需与读负载均衡协同设计:
- 读负载均衡策略包括程序轮询(需手动调整从库数量)或专用软件(如lvs、haproxy)
- maxscale可同时实现两类功能,降低架构复杂度
关键总结:
- 程序层方案适合定制化需求高、开发资源充足的项目
- 中间件方案(如 maxscale)适合快速迁移旧系统,但需严格性能测试
读负载均衡的实现策略
目标:将读请求均匀分配到多个从库
实现方式:
- 程序轮询:
- 开发人员手动分配查询到不同从库
- 缺点:增减从库需修改程序配置,灵活性差
- 专用软硬件:
- 软件方案:lvs、haproxy、maxscale(支持自动负载均衡)
- 硬件方案:f5 等设备
- 核心要求:生产前必须进行基准测试,验证负载分配效率
maxscale实践指南
maxscale作为高效中间件,同时提供读写分离和读负载均衡功能:
- 核心机制:通过路由模块(如
readwritesplit)解析sql,写操作路由至主库,读操作按配置策略(如加权轮询)分发至从库 - 部署建议:
- 配置多从库时,需定义
[slave_servers]列表并设置健康检查 - 为规避主从延迟,可在查询中添加注释强制主库执行(如
select /* master */ ...)
- 配置多从库时,需定义
核心功能:
- 自动读写分离(基于 sql 语法分析)
- 从库读负载均衡(支持权重分配)
maxscale配置示例
# maxscale 配置文件 (maxscale.cnf) # 定义主库 [server1] type=server address=192.168.1.101 port=3306 protocol=mysqlbackend # 定义从库 [server2] type=server address=192.168.1.102 port=3306 protocol=mysqlbackend # 定义从库 [server3] type=server address=192.168.1.103 port=3306 protocol=mysqlbackend # 读写分离路由 [readwritesplit] type=service router=readwritesplit servers=server1,server2,server3 user=maxscale password=maxscale_pass # 负载均衡策略 [readonlyservice] type=service router=readconnroute router_options=slave servers=server2,server3 # 监控主从状态 [replicationmonitor] type=monitor module=mariadbmon servers=server1, server2, server3 user=monitor_user password=monitor_password
常见的protocol选项
| protocol 名称 | 描述 | 适用场景 |
|---|---|---|
mysqlbackend | 用于连接mysql或mariadb数据库的标准协议 | 最常用的选项,适用于绝大多数mysql/mariadb环境 |
mariadbbackend | 类似于mysqlbackend,但在某些新版本中可能有不同的行为或优化 | 新版本maxscale推荐用于mariadb |
galerabackend | 专为galera集群设计 | 用于galera集群复制 |
kafkabackend | 用于连接kafka消息队列 | 特殊用途,如日志转发等 |
注意:以上列表基于maxscale官方文档和常见实践整理,具体可用选项取决于你安装的maxscale版本
关键问题与解决方案
1 )主从延迟敏感查询:
- 程序控制方案:开发人员显式指定主库连接执行实时查询
- 中间件方案:在sql中添加注释提示(如
/*route_to_master*/ select...),但需代码适配
2 )从库扩展性:
- 程序轮询方式增减从库需修改配置;中间件(如maxscale)支持动态注册新节点
3 )存储过程处理:
- 建议将存储过程拆分为独立读写单元,或强制主库执行
关键技术细节补充与代码示例
1 ) 主从复制延迟的影响
- 问题:主从延迟导致从库数据短暂不一致,实时性敏感查询需强制走主库
- 解决方案:在查询中添加路由提示(如
/* force_master */)
2 ) 原生 sql 配置示例
主从复制基础配置:
-- 主库配置 (my.cnf) [mysqld] server-id=1 log-bin=mysql-bin binlog-format=row binlog-do-db=your_database -- 从库配置 (my.cnf) [mysqld] server-id=2 relay-log=mysql-relay-bin read-only=1
从库同步命令:
change master to master_host='master_ip', master_user='replica_user', master_password='password', master_log_file='mysql-bin.000001', master_log_pos=107; start slave;
3 ) nestjs 实现程序层读写分离
使用 typeorm 多数据源配置:
// database.providers.ts
import { datasource } from 'typeorm';
export const databaseproviders = [
{
provide: 'master_database',
usefactory: () => new datasource({
type: 'mysql',
host: 'master_host',
port: 3306,
username: 'master_user',
password: 'master_password',
database: 'your_db',
entities: [__dirname + '/..//*.entity{.ts,.js}'],
synchronize: false,
}).initialize(),
},
{
provide: 'slave_database',
usefactory: () => new datasource({
type: 'mysql',
host: 'slave_host',
port: 3306,
username: 'slave_user',
password: 'slave_password',
database: 'your_db',
entities: [__dirname + '/..//*.entity{.ts,.js}'],
synchronize: false,
}).initialize(),
},
];
// user.service.ts
import { injectable, inject } from '@nestjs/common';
import { injectdatasource } from '@nestjs/typeorm';
import { datasource } from 'typeorm';
@injectable()
export class userservice {
constructor(
@inject('master_database') private masterdatasource: datasource,
@inject('slave_database') private slavedatasource: datasource,
) {}
// 写操作强制使用主库
async createuser(userdata: any) {
return this.masterdatasource.query(
`insert into users (name, email) values (?, ?)`,
[userdata.name, userdata.email],
);
}
// 读操作使用从库(非实时敏感查询)
async getusers() {
return this.slavedatasource.query(`select * from users`);
}
// 实时敏感查询使用主库
async getrealtimedata() {
return this.masterdatasource.query(
`/* force_master */ select * from transactions where id = ?`,
[transactionid],
);
}
} 或
// database.providers.ts - 定义主从库连接池
import { createpool, pool } from 'mysql2/promise';
const masterpool: pool = createpool({
host: 'master_db_host',
user: 'write_user',
password: 'write_password',
database: 'app_db',
connectionlimit: 10,
});
const slavepool: pool = createpool({
host: 'slave_db_host', // 可扩展为数组实现负载均衡
user: 'read_user',
password: 'read_password',
database: 'app_db',
connectionlimit: 20,
});
// query.service.ts - 根据操作类型路由查询
import { injectable } from '@nestjs/common';
@injectable()
export class queryservice {
async executequery(sql: string, iswrite: boolean = false) {
const pool = iswrite ? masterpool : slavepool;
const [rows] = await pool.query(sql);
return rows;
}
// 实时性敏感查询显式指定主库
async criticalquery(sql: string) {
return this.executequery(`/*master*/ ${sql}`, true);
}
}
// user.controller.ts - 业务层调用示例
@controller('users')
export class usercontroller {
constructor(private queryservice: queryservice) {}
@get(':id')
async getuser(@param('id') id: string) {
// 读操作路由到从库
return this.queryservice.executequery(
`select * from users where id = ${id}`
);
}
@post()
async createuser(@body() userdata) {
// 写操作路由到主库
return this.queryservice.executequery(
`insert into users (name) values ('${userdata.name}')`,
true
);
}
}或
// 配置主库和从库连接
const masterdbconfig = { host: 'master-db', user: 'admin', password: 'pass' };
const slavedbconfigs = [
{ host: 'slave-db1', user: 'readonly', password: 'pass' },
{ host: 'slave-db2', user: 'readonly', password: 'pass' }
];
// 手动路由读写操作
import { datasource } from 'typeorm';
const masterdatasource = new datasource({ ...masterdbconfig, type: 'mysql' });
const slavedatasources = slavedbconfigs.map(config => new datasource({ ...config, type: 'mysql' }));
// 写操作路由至主库
async function executewrite(query: string) {
await masterdatasource.query(query);
}
// 读操作轮询路由至从库(简单负载均衡)
let currentslaveindex = 0;
async function executeread(query: string) {
const datasource = slavedatasources[currentslaveindex];
currentslaveindex = (currentslaveindex + 1) % slavedatasources.length;
return datasource.query(query);
}读写分离架构优化建议
1 ) 监控与告警:
- 部署prometheus + grafana监控主从延迟(
show slave status中的seconds_behind_master)
2 ) 故障转移:
- 使用keepalived实现中间件高可用,避免单点故障
3 ) 分库分表协同:
- 读写分离可与shardingsphere等分库分表中间件集成,应对海量数据场景
4 ) 连接池优化:
- 配置从库连接池大小为主库的2-3倍(读密集型场景)
5 ) 总结:
- 读写分离是减轻主库读压力的核心手段,负载均衡是分散从库查询负载的必备策略
- 程序控制方案灵活但维护成本高;中间件方案(如maxscale)便捷但需性能验证
- 生产环境需结合基准测试、实时监控与代码规范,平衡性能与复杂度
关键缺陷与应对方案
1 ) 虚拟ip管理缺失
- ❌ 原生不支持自动分配vip
- ✅ 解决方案:
# vip切换脚本示例 (python) import subprocess def assign_vip(new_master): subprocess.call(f"ssh {new_master} 'ifconfig eth0:1 192.168.1.100/24 up'", shell=true)
或集成keepalived(需牺牲自动选主功能,增加架构复杂度)
2 ) 复制链路监控盲区
- 仅启动/切换时检查主从状态,运行期无法检测复制中断或延迟
- 对比mmm缺乏从库故障自动剔除机制
3 ) 安全风险集中
- 强制要求全节点ssh免密互信
- 单点凭证泄露将威胁整个集群,多集群监控场景风险指数级放大
4 ) 读负载均衡缺位
需额外引入代理层(如proxysql/maxscale)实现读流量分发:
-- proxysql 读分组配置示例 insert into mysql_servers (hostgroup_id, hostname, port) values (10, 'slave1', 3306), (10, 'slave2', 3306); update mysql_query_rules set destination_hostgroup=10 where match_pattern='^select'; load mysql servers to runtime;
技术强化建议
1 ) gtid+半同步最佳实践
-- 启用gtid与半同步 set global gtid_mode=on; set global enforce_gtid_consistency=on; install plugin rpl_semi_sync_master soname 'semisync_master.so';
原生sql半同步复制配置
-- 主库配置 install plugin rpl_semi_sync_master soname 'semisync_master.so'; set global rpl_semi_sync_master_enabled = 1; -- 从库配置 install plugin rpl_semi_sync_slave soname 'semisync_slave.so'; set global rpl_semi_sync_slave_enabled = 1;
2 ) nestjs健康检查集成
// mha状态监控端点 (nestjs)
@get('/mha-status')
async checkmha() {
const ssh = await exec('masterha_check_status --conf=/etc/mha.cnf');
return { status: ssh.includes('alive') ? 'ok' : 'down' };
} 3 ) 安全加固方案
- 使用证书代理跳板机替代直接ssh连接
- 通过ansible vault加密管理凭据
4 ) 架构决策关键点:
- 数据强一致场景:优先采用 gtid+半同步+mha
- 高可用扩展需求:vip管理需定制开发或整合keepalived+自定义选主逻辑
- 安全合规环境:必须部署ssh证书中继和网络隔离策略
mha架构的深度剖析与优化实践
核心优势
1 ) 开源灵活性与扩展性
- mha采用perl脚本语言开发,提供完整的源代码访问权限和脚本接口
- 支持开发者使用python等语言二次开发扩展功能(如虚拟ip配置、邮件通知),只需确保参数与返回值格式兼容即可调用
2 ) 全面支持gtid复制
- 与早期mmm工具不同,mha完美适配mysql的gtid全局事务标识复制机制,通过唯一事务id确保数据一致性,显著降低主从切换时数据丢失风险
3 ) 智能故障切换策略
- 自动筛选数据最完备的从节点提升为主库(基于日志应用进度)
- 最大化保留原主库的二进制日志
- 结合半同步复制可保障已提交事务零丢失
4 ) 集群化监控能力
- 单监控节点可管理多组主从集群,支持差异化切换策略,显著降低服务器资源消耗
总结与建议
- 读写分离必要性:减轻主库压力是核心目标,尤其在高读负载场景
- 方案选择:
- 优先中间件(如 maxscale)快速实现,但严格测试性能损耗
- 程序层方案更灵活,但需投入开发资源
- 负载均衡关键:结合软硬件(如 haproxy + maxscale)实现动态扩展
- 性能保障:所有方案上线前必须进行 基准测试,模拟高并发场景验证 qps 与延迟
到此这篇关于mysql数据库读写分离与负载均衡的实现方式及深度分析的文章就介绍到这了,更多相关mysql数据库读写分离内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论