MySQL的主从复制和MySQL的读写分离两者有着紧密联系,首先要部署主从复制,只有主从复制完成了,才能在此基础上进行数据的读写分离。
(1)MySQL支持复制的类型。
1)基于语句的复制。MySQL默认采用基于语句的复制,效率比较高。
2)基于行的复制。把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
3)混合类型的复制。默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。
(2)MySQL复制的工作过程

1)在每个事务更新数据完成之前,Master在二进制日志记录这些改变。写入二进制日志完成后,Master通知存储引擎提交事务。
2)Slave将Master的Binary log复制到其中继日志。首先,Slave开始一个工作线程——I/O线程,I/O线程在Master上打开一个普通的链接,然后开始Binlog dump process。Binlog dump process从Master的二进制日志中读取事件,如果已经跟上Master,它会睡眠并等待Master产生新的事件。I/O线程将这些事件写入中继日志。
3)SQL slave thred(SQL从线程)处理该过程的最后一步。SQL线程从中继日志读取事件,并重放其中的事件而更新Slave的数据,使其与Master中的数据一致。只要该线程与I/O线程保持一致,中继日志通常会位于OS的缓存中,所以中继日志的开销很小。
复制过程中有一个很重要的限制,即复制在Slave上是串行化的,也就是说Master上的并行更新操作不能在Slave上并行操作。
2.MySQL读写分离原理
简单来说,读写分离(见图所示)就是只在主服务器上写,只在从服务器上读。基本原理是让主数据库处理事务性查询,而从数据库处理select查询。数据库复制被用来把事务性查询导致的变更同步到群集中的从数据库。

基于中间代理层实现:代理一般位于客户端和服务器之间,代理服务器接到客户端请求通过判断后转发到后端数据库。
(1)Mysql复制优点:
1)通过添加从服务器来提高数据平台的可靠性,增加数据频台的高性能
2)提高数据的安全性,可以作轮询
3)缓解数据库的性能压力
(2)Mysql复制类型:
1)异步复制:mysql默认是异步复制,主库不关心从收没收到数据,会直接返回给客户端,速度快,如果主库坏掉了,如果此时强制把从库提升为主库,有可能从库数据不完整
2)全同步复制:主库执行一个事务,会等待从库完事,才会返回给客户端,但是会导致效率慢,
3)半同步:主库执行完事务后,会等待一个从库完成事务后,在返回给客护端,
(3)半同步复制的潜在问题:
客户端在存储引擎提交后,在得到从库确认过程中,主库死掉了,此时可能的情况有两种
-
事务还没发送到从库上,
此时,客户端会收到事务提交失败的信息,客户端会重新提交该事务到新的主上,当宕机的主库重新启动,会发现,该事务在从库被提交两次,一次是之前作为主的提交,一次是被新主同步过来的
-
事务已经发送到从库上
此时,从库已经收到该事务并应用了该事务,但是客户端仍会收到事务提交失败的信息,重新提交该事务到新的主上,
解决方案,mysql 5.7 引入了一种新的半同步解决方案,
(4)Mysql 支持的复制方式
-
(SBR)基于SQL 语句的复制:在主服务器上执行的SQL语句,在从服务器上同样执行
-
(RBR)基于行的复制: 主服务器把表的行变化作为事件写入到二进制日志中,主服务器把代表了行的变化事件复制到从服务中,
-
(MBR)混合模式复制,先采用基于语句的复制,一旦发现基于语句无法精确复制时,在采用行。
Mysql在 5.6 采用基于事务的复制
RBR(行级复制)的优点:
-
任何情况都可以复制,这对复制来说是最安全可靠的
-
更少的行级缩表
-
和其他大多数数据库系统的复制技术一样
-
多数情况下,从服务器的表有主键的话,复制就会快很多
RBR(行级复制)的缺点
-
binlog文件较大
-
复杂的回滚时,binlog中会包含大量的数据
-
主服务器上执行多个UPDATE语句时,所有发生变化的记录都会写道binlog中,而且只写一个操作事务,这会导致频繁发生binlog的并发写问题。
-
不能通过查看日志文件来审计执行过的sql语句,
SBR(sql语句复制)的优点
-
历史悠久,技术成熟,binlog文件较小
-
Binlog中包含了数据库所有的更改信息
-
Binlog可以用于实时还原,而不仅仅用于复制
-
主从版本可以不一样,从服务器版本可以比主服务器版本高
SBR(sql语句复制)的缺点
-
不是所有的语句都能复制,尤其时包含不确定操作时
-
复制要求全表扫描
-
对于一些复杂的操作,在从服务器上资源消耗会更严重

3.主从配置
(1)建立时间同步环境,在主节点上搭建时间同步服务器。
1)安装NTP。
[root@localhost ~]*# yum install ntp -y*
2)配置NTP。
[root@localhost ~]*# vim /etc/ntp.conf*
server 127.127.1.0 //本地是时钟源//
fudge 127.127.1.0 stratum 8 //设置时间层级为8(限制在15内)//
3)重启服务。
[root@localhost ~]*# systemctl restart ntpd.service*
(2)在从节点服务器上进行时间同步。
[root@localhost ~]*# yum install ntpdate -y*
[root@localhost ~]*# ntpdate 192.168.**200**.1**11* *//同步主服务器的时间//
同步网络上的时间:
[root@localhost ~]# crontab -l*
(3)在每台服务器上关闭firewalld防火墙。
[root@localhost ~]*# systemctl stop firewalld.service //关闭防火墙//*
[root@localhost ~]*# setenforce 0
(4)安装MySQL数据库。在Master、Slave1、Slave2上安装
(5)配置MySQL Master主服务器。
1)在/etc/my.cnf中修改或者增加以下内容。
[root@localhost mysql]*# vim /etc/my.cnf*
[mysqld]
server-id = 11
log-bin=master-bin //主服务器日志文件//
log-slave-updates=true //从服务器更新二进制日志//
2)重启MySQL服务。
[root@localhost ~]*# systemctl restart mysqld.service*
3)登录MySQL程序,给服务器授权。
[root@localhost ~]# mysql -uroot -p
MariaDB [(none)]> grant replication slave on *.* to 'slv'@'192.168.23.201' identified by '000000';
MariaDB [(none)]> flush privileges;
mysql> show master status;
+-------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| master-bin.000001 | 604 | | | |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
其中File列显示日志名,position列显示偏移量。
备份原有的数据:
Mysqldump -uroot -p123123 --all-databses > /root/alldbbackup.sql
Scp /root/alldbbackup.sql root@192.168.23.201.:/root/
Scp /root/alldbbackup.sql root@192.168.23.201:/root
导入备份脚本:
Mysql -u root -p < /root/alldbackup.sql
从库连接主库
Mysql -u slv -p 000000 -h 192.168.200.111
(6)配置从服务器。 1)在/etc/my.cnf中修改或者增加以下内容。
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
server-id = 22
relay-log=relay-log-bin #从主服务器上同步日志文件记录到本地//
relay-log-index=slave-relay-bin.index #定义relay-log的位置和名称//
# 这里要注意server-id不能与主服务器相同。
2)重启mysql服务。
[root@localhost ~]# systemctl restart mysqld.service
3)登录mysql,配置同步。
按主服务器结果更改下面命令中的master_log_file和master_log_pos的参数。
从:
[root@localhost ~]# mysql -u root -p
MariaDB [(none)]> stop slave;
Query OK, 0 rows affected, 1 warning (0.00 sec)
MariaDB [(none)]> change master to
-> master_host='192.168.23.100',
-> master_user='backup',
-> master_password='000000',
-> master_log_file='mysql-bin.000001',
-> master_log_pos=604;
Query OK, 0 rows affected (0.01 sec)
4)启动同步。
4)启动同步。
MariaDB [(none)]> start slave;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> start slave;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> reset slave;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> show slave status\G;
*************************** 1. row ***************************
....
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
......
1 row in set (0.00 sec)
Django配置读写分离
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "blog002",
"USER": "root",
"PASSWORD": '000000',
"HOST": "192.168.23.100",
"PORT": 3306,
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
},
'default_write': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "blog002",
"USER": "root",
"PASSWORD": '000000',
"HOST": "192.168.23.201",
"PORT": 3306,
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
}
}
DATABASE_ROUTERS = ['utils.mysql_router.Router',]
# models.py
class Author(models.Model):
name = models.CharField(max_length=15, null=False)
age = models.SmallIntegerField(null=False)
class Meta:
app_label = "app01"
# mysql_router.py
# encoding=utf-8
class Router:
def db_for_read(self, model, **hints):
# 读库
return 'default_write'
def db_for_write(self, model, **hints):
# 写库
return 'default'
# 一主多从
class Router:
def db_for_read(self, model, **hints):
"""
读取时随机选择一个数据库
"""
import random
return random.choice(['db2', 'db3', 'db4'])
def db_for_write(self, model, **hints):
"""
写入时选择主库
"""
return 'default'
# 分库分表
class Router:
def db_for_read(self, model, **hints):
if model._meta.app_label == 'app01':
return 'db1'
if model._meta.app_label == 'app02':
return 'db2'
def db_for_write(self, model, **hints):
if model._meta.app_label == 'app01':
return 'db1'
if model._meta.app_label == 'app02':