原理
将对数据库的读写请求分散到不同的节点, master节点处理写请求,slave节点处理读请求。
实现
以MySQL为例子,在单机环境下使用docker搭建一主一从的集群。
1. 配置准备
master
保存到./master/my.cnf
[mysqld]
## 设置server_id,一般设置为IP,注意要唯一
server_id=100
## 复制过滤:也就是指定哪个数据库不用同步(mysql库一般不同步)
binlog-ignore-db=mysql
## 开启二进制日志功能,可以随便取,最好有含义(关键就是这里了)
log-bin=replicas-mysql-bin
## 为每个session分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
## 二进制日志自动删除/过期的天数。默认值为0,表示不自动删除。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
salve
保存到./salve/my.cnf
[mysqld]
## 设置server_id,一般设置为IP,注意要唯一
server_id=101
## 复制过滤:也就是指定哪个数据库不用同步(mysql库一般不同步)
binlog-ignore-db=mysql
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=replicas-mysql-slave1-bin
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
## 二进制日志自动删除/过期的天数。默认值为0,表示不自动删除。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
## relay_log配置中继日志
relay_log=replicas-mysql-relay-bin
## log_slave_updates表示slave将复制事件写进自己的二进制日志
log_slave_updates=1
## 防止改变数据(除了特殊的线程)
read_only=1
docker-compose.yml
需要多从可以继续追加
version: '3'
services:
mysql-master:
image: mysql:5.7
volumes:
- ./master/my.cnf:/etc/mysql/my.cnf
environment:
- "MYSQL_ROOT_PASSWORD=root"
- "MYSQL_DATABASE=replicas_db"
links:
- mysql-slave
ports:
- "33065:3306"
restart: always
hostname: mysql-master
mysql-slave:
image: mysql:5.7
volumes:
- ./slave/my.cnf:/etc/mysql/my.cnf
environment:
- "MYSQL_ROOT_PASSWORD=root"
- "MYSQL_DATABASE=replicas_db"
ports:
- "33066:3306"
restart: always
hostname: mysql-slave
2. 启动服务(节点)
docker-compose up -d
3. 查看master状态
mysql> show master status\G
*************************** 1. row ***************************
File: replicas-mysql-bin.000003
Position: 154
Binlog_Do_DB:
Binlog_Ignore_DB: mysql
Executed_Gtid_Set:
1 row in set (0.00 sec)
4. salve关联master
访问salve节点
sudo docker exec -it mysql_mysql-slave_1 bash
mysql -uroot -proot
关联
change master to MASTER_HOST='mysql-master', MASTER_USER='root', MASTER_PASSWORD='root', MASTER_LOG_FILE='replicas-mysql-bin.000003', MASTER_LOG_POS=154;
5. 重启salve节点
sudo docker restart mysql_mysql-salve_1
6. 验证
在master节点创建一张表,并插入一条数据
use replicas_db;
create table test(
id int auto_increment primary key,
title varchar(255) not null
);
insert into test values(default, 'Hombio');
salve节点查询
use replicas_db;
show tables;
select * from test;
复杂度
读写分离的实现逻辑并不复杂,但有两个细节点将引入设计复杂度:主从复制延迟和分配机制。
1. 主从复制延迟
常见有以下三种解决方案
- 写操作后的读操作指定发给数据库主服务器
缺点:业务侵入性强 - 读从机失败后再读一次主机
缺点:二次读取,增加读压力 - 关键业务读写操作全部指向主机,非关键业务采用读写分离
2. 分配机制
将读写操作区分开来,然后访问不同的数据库服务器,一般有两种方式:程序代码封装和中间件封装。
代码
- 实现简单
- 通用性差,每个编程语言都要实现一次
- 如果发生主从切换,则所有的系统都要修改配置
开源方案:TDDL
中间件
中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。对于业务服务器来说,访问中间件和访问数据库没有区别。
开源方案:MySQL Proxy, MySQL Router, Atlas
小结
读写分离适用单机并发无法支撑并且读的请求更多的情形。在单机数据库情况下,表上加索引一般对查询有优化作用却影响写入速度,读写分离后可以单独对读库进行优化,写库上减少索引,对读写的能力都有提升,且读的提升更多一些。如果并发写入特别高,单机写入无法支撑或者通过缓存技术或者程序优化能够满足要求,读写分离就不太适用了。
本例中的一主一从都存在单点故障问题,如果业务需要保证高可用,还需要分别冗余主从,主服务器可以采用主备结构,从服务器比较简单,多开几个从服务器即可,演变成一主多从。
参考
https://time.geekbang.org/column/article/8269