数据库主从复制:原理不够用了之后
报表查询把单库 CPU 顶到 90% 以上,读写挤在同一条链路上,慢查询一多,写事务也跟着抖。书上的主从复制原理我早看过,真正动手配 binlog、拉从库、做读写分离,是在这次高可用改造里才串起来的。
为什么需要主从复制
读性能问题
随着业务增长,单库扛不住读了。
解决方案:读写分离,写走主库,读走从库。
高可用问题
单库故障,整个服务不可用。
解决方案:主从复制,主库挂了切换到从库。
数据备份问题
全量备份太慢,影响业务。
解决方案:从库做备份,不影响主库。
MySQL 主从复制
复制原理
MySQL 主从复制基于 binlog:
graph TB
A[主库] -->|binlog| B[从库IO线程]
B -->|中继日志| C[从库SQL线程]
C --> D[从库数据]
style A fill:#FFD700
style B fill:#90EE90
style C fill:#90EE90
style D fill:#87CEEB
- 主库执行 SQL,写入 binlog
- 从库 IO 线程读取 binlog,写入中继日志
- 从库 SQL 线程执行中继日志,同步数据
配置主库
-- 主库配置
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
binlog-do-db = myapp
-- 创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
-- 查看主库状态
SHOW MASTER STATUS;
配置从库
-- 从库配置
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
read-only = 1
-- 连接主库
CHANGE MASTER TO
MASTER_HOST='master-ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
-- 启动复制
START SLAVE;
-- 查看复制状态
SHOW SLAVE STATUS;
PostgreSQL 主从复制
流复制配置
主库配置:
-- postgresql.conf
wal_level = replica
max_wal_senders = 5
wal_keep_segments = 10
-- pg_hba.conf
host replication repl 192.168.1.0/24 md5
从库配置:
# 使用 pg_basebackup 创建从库
pg_basebackup -h master-ip -U repl -D /var/lib/postgresql/data -P -R
# 修改 postgresql.conf
hot_standby = on
MongoDB 主从复制
副本集配置
// 初始化副本集
rs.initiate({
_id: "myapp",
members: [
{ _id: 0, host: "mongodb1:27017" },
{ _id: 1, host: "mongodb2:27017" },
{ _id: 2, host: "mongodb3:27017", arbiterOnly: true }
]
});
// 查看副本集状态
rs.status();
Redis 主从复制
配置主从
# 从库配置
redis.conf
slaveof master-ip 6379
masterauth password
Sentinel 高可用:
# sentinel.conf
sentinel monitor mymaster master-ip 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
读写分离实践
应用层路由
# Python 示例
import random
class DatabaseRouter:
def __init__(self, master_config, slave_configs):
self.master = create_connection(master_config)
self.slaves = [create_connection(c) for c in slave_configs]
def get_read_connection(self):
# 随机选择一个从库
return random.choice(self.slaves)
def get_write_connection(self):
return self.master
def execute_read(self, query):
conn = self.get_read_connection()
return conn.execute(query)
def execute_write(self, query):
conn = self.get_write_connection()
return conn.execute(query)
# 使用
router = DatabaseRouter(
master_config={'host': 'master-ip', 'port': 3306},
slave_configs=[
{'host': 'slave1-ip', 'port': 3306},
{'host': 'slave2-ip', 'port': 3306}
]
)
中间件路由
ProxySQL:
-- 配置 ProxySQL
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (1, 'master-ip', 3306);
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (2, 'slave1-ip', 3306);
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (2, 'slave2-ip', 3306);
-- 配置读写分离规则
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (1, 1, '^SELECT', 2, 1);
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (2, 1, '^INSERT|^UPDATE|^DELETE', 1, 1);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
主从切换
手动切换
# 1. 停止从库复制
STOP SLAVE;
# 2. 提升从库为主库
STOP SLAVE;
RESET MASTER;
# 确保所有数据已同步
# 3. 修改应用配置,连接新主库
# 4. 将原主库配置为从库
CHANGE MASTER TO MASTER_HOST='new-master-ip';
START SLAVE;
自动切换
MHA (Master High Availability):
# 安装 MHA
apt-get install mha4mysql-node mha4mysql-manager
# 配置 MHA
[server default]
user=mha
password=mha
manager_workdir=/var/log/masterha
manager_log=/var/log/masterha/manager.log
[server1]
hostname=master-ip
candidate_master=1
[server2]
hostname=slave1-ip
candidate_master=1
[server3]
hostname=slave2-ip
no_master=1
Orchestrator:
{
"Debug": false,
"ListenAddress": ":3000",
"MySQLTopologyUser": "orchestrator",
"MySQLTopologyPassword": "orchestrator",
"Backend": "sqlite",
"SQLite3DataFile": "/var/lib/orchestrator/orchestrator.sqlite3"
}
踩过的坑
坑一:主从延迟
从库延迟过高,读到了旧数据。
原因:
- 主库写压力大
- 从库配置差
- 网络延迟
解决:
- 增加从库数量
- 升级从库配置
- 使用 GTID 避免数据不一致
- 部分查询直接读主库
-- 查看主从延迟
SHOW SLAVE STATUS;
-- 看 Seconds_Behind_Master 字段
-- 启用 GTID
[mysqld]
gtid_mode = ON
enforce_gtid_consistency = ON
坑二:复制中断
binlog 位置不对,复制失败。
原因:
- 从库执行了写操作
- 主库 binlog 被清理
- 网络问题
解决:
- 从库设为只读
- 配置 binlog 保留时间
- 配置复制重试
-- 配置从库只读
[mysqld]
read-only = 1
super-read-only = 1
-- 配置 binlog 保留
[mysqld]
expire_logs_days = 7
坯三:切换失败
主库挂了,切换失败。
原因:
- 从库数据不完整
- 切换脚本有问题
- 网络分区
解决:
- 定期演练切换
- 使用成熟的高可用方案
- 配置自动切换
# 定期演练切换
0 0 1 * * /usr/local/bin/ha-test.sh
写在最后
主从复制这东西,不只是技术问题,也是架构问题。
解决了:
- 读性能问题
- 高可用问题
- 数据备份问题
带来了:
- 复杂度增加
- 主从延迟
- 数据一致性挑战
实施之前先评估:
- 读写比例
- 可用性要求
- 团队能力
- 运维成本
不是所有场景都需要主从复制,有时候单库加缓存就够用。
这次数据库高可用改造花了一个月,从单库到主从复制,再到自动切换。改造完成后,读性能提升了 3 倍,可用性从 99.5% 提升到 99.9%。
版权声明: 本文首发于 指尖魔法屋-数据库主从复制:原理不够用了之后(https://blog.thinkmoon.cn/post/62-database-master-slave-replication-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。