为什么要搭建一套MySQL的主从复制架构?
在MySQL真正的生产环境中,他一定不是一个单机版的架构,因为单机版的MySQL一般仅能用于本地开发环境和测试环境,是绝对不可能运用于生产环境的。
那么生产环境的MySQL架构应该是什么样子的呢?简单来说,MySQL在生产环境中,必须要搭建一套主从复制的架构,同时可以基于一些工具实现高可用架构。
另外如果有需求,还需要基于一些中间件实现读写分离架构,最后就是如果数据量很大,还必须可以实现分库分表的架构。
我们先来看看MySQL的主从复制架构,这个主从复制架构,顾名思义,就是部署两台服务器,每台服务器上都得有一个MySQL,其中一个MySQL是master(主节点),另外一个MySQL是slave(从节点)。然后我们的系统平时连接到master节点写入数据,当然也可以从里面查询了,就跟你用一个单机版的MySQL是一样的,但是master节点会把写入的数据自动复制到slave节点去,让slave节点可以跟master节点有一模一样的数据。
如果你的MySQL就单机部署,那么一旦他宕机了,岂不是你的数据库就完蛋了?数据库完蛋了,你的Java业务系统是不是也就完蛋了?所以说,真正生产架构里,MySQL必须得做高可用架构。那么高可用架构怎么做呢?他的一个先决条件就是主从复制架构。你必须得让主节点可以复制数据到从节点,保证主从数据是一致的,接着万一你的主节点宕机了,此时可以让你的Java业务系统连接到从节点上去执行SQL语句,写入数据和查询数据,因为主从数据是一致的,所以这是没问题的。如果实现这样的一个效果,自然就实现了MySQL的高可用了,他单机宕机不影响你的Java业务系统的运行。但是大家也得注意,这里其实是没这么简单的,因为实际哪怕这套架构运用到生产环境,也是有大量的问题要解决的。
比如主从进行数据复制的时候,其实从节点通常都会落后一些,所以数据不完全一致。另外,主节点宕机后,要能自动切换从节点对外提供服务,这个也需要一些中间件的支持,也没那么容易。
那么我们如果做了这个MySQL主从复制架构之后,除了这个高可用之外,还有什么作用呢?其实这就得说到大名鼎鼎的读写分离架构了!这个读写分离架构,也是依赖于MySQL的主从复制架构的。
读写分离架构的意思就是,你的Java业务系统可以往主节点写入数据,但是从从节点去查询数据,把读写操作做一个分离,分离到两台MySQL服务器上去,一台服务器专门让你写入数据,然后复制数据到从节点,另外一台服务器专门让你查询数据。可是好端端的,我们吃饱了没事儿,为什么要做读写分离呢?难道就为了好玩儿吗?当然不是了!因为假设我们的MySQL单机服务器配置是8核16GB,然后每秒最多能抗4000读写请求,现在假设你真实的业务负载已经达到了,每秒有2500写请求+2500读请求,也就是每秒5000读写请求了,那么你觉得如果都放一台MySQL服务器,能抗的住吗?必然不行啊!所以此时如果你可以利用主从复制架构,搭建起来读写分离架构,就可以让每秒2500写请求落到主节点那台服务器,2500读请求落到从节点那台服务器,用2台服务器来抗下你每秒5000的读写请求。
接着现在问题来了,大家都知道,其他大部分Java业务系统都是读多写少,读请求远远多于写请求,那么接着发现随着系统日益发展,读请求越来越多,每秒可能有6000读请求了,此时一个从节点服务器也抗不下来啊,那怎么办呢?
简单!因为MySQL的主从复制架构,是支持一主多从的,所以此时你可以再在一台服务器上部署一个从节点,去从主节点复制数据过来,此时你就有2个从节点了,然后你每秒6000读请求不就可以落到2个从节点上去了,每台服务器主要接受每秒3000的读请求。Java业务系统每秒以2500的TPS写入主库,然后主库会复制数据到两个从库,接着你每秒6000 QPS的读请求分散在两个从库上,一切似乎很完美,这就是主从复制架构的另外一个经典的应用场景,就是读写分离,通过读写分离,可以让你抗下很高的读请求。
而且在上述架构之下,还可以融合高可用架构进去,因为你有多个从库,所以当你主库宕机的时候,可以通过中间件把一个从库切换为主库,此时你的Java业务系统可以继续运行,在实现读写分离的场景下,还可以同时实现高可用。不过其实一般在项目中,高可用架构是必须做的,但是读写分离架构并不是必须的,因为对于大多数公司来说,读请求QPS并没那么高,远远达不到每秒几千那么夸张,但是高可用你是必须得做的,因为你必须保证主库宕机后,有另外一个从库可以接管提供服务,避免Java业务系统中断运行。
除此之外,这个从库其实还有很多其他的应用场景,比如你可以挂一个从库,专门用来跑一些报表SQL语句,那种SQL语句往往是上百行之多,运行要好几秒,所以可以专门给他一个从库来跑。也可以是专门部署一个从库,让你去进行数据同步之类的操作。
接着我们来说一下MySQL实现主从复制的一个基本的工作原理。首先呢,MySQL自己在执行增删改的时候会记录binlog日志,所以这个binlog日志里就记录了所有数据增删改的操作。然后从库上有一个IO线程,这个IO线程会负责跟主库建立一个TCP连接,接着请求主库传输binlog日志给自己,这个时候主库上有一个IO dump线程,就会负责通过这个TCP连接把binlog日志传输给从库的IO线程。接着从库的IO线程会把读取到的binlog日志数据写入到自己本地的relay日志文件中去,然后从库上另外有一个SQL线程会读取relay日志里的内容,进行日志重做,把所有在主库执行过的增删改操作,在从库上做一遍,达到一个还原数据的过程。
简单来说,你只要给主节点挂上一个从节点,从节点的IO线程就会跟主节点建立网络连接,然后请求主节点传输binlog日志,主节点的IO dump线程就负责传输binlog日志给从节点,从节点收到日志后就可以回放增删改操作恢复数据。在这个基础之上,就可以实现MySQL主从节点的数据复制以及基本一致,进而可以实现高可用架构以及读写分离架构。
如何为MySQL搭建一套主从复制架构?
首先呢,要确保主库和从库的server-id是不同的,这个是必然的,其次就是主库必须打开binlog功能,你必须打开binlog功能主库才会写binlog到本地磁盘,接着就可以按如下步骤在主库上执行一通操作了。
首先在主库上要创建一个用于主从复制的账号:
create user ‘backup_user’@’192.168.31.%’ identified by ‘backup_123’;
grant replication slave on . to ‘backup_user’@’192.168.31.%’;
flush privileges;
接着你要考虑一个问题,假设你主库都跑了一段时间了,现在要挂一个从库,那从库总不能把你主库从0开始的所有binlog都拉一遍吧!这是不对的,此时你就应该在凌晨的时候,在公司里直接让系统对外不可用,说是维护状态,然后对主库和从库做一个数据备份和导入。可以使用如下的mysqldump工具把主库在这个时刻的数据做一个全量备份,但是此时一定是不能允许系统操作主库了,主库的数据此时是不能有变动的。
/usr/local/mysql/bin/mysqldump —single-transaction -uroot -proot —master-data=2 -A > backup.sql
注意,mysqldump工具就在你的MySQL安装目录的bin目录下,然后用上述命令就可以对你主库所有的数据都做一个备份,备份会以SQL语句的方式进入指定的backup.sql文件,只要执行这个backup.sql文件,就可以恢复出来跟主库一样的数据。至于上面命令里的—master-data=2,意思就是说备份SQL文件里,要记录一下此时主库的binlog文件和position号,这是为主从复制做准备的。
接着你可以通过scp之类的命令把这个backup.sql文件拷贝到你的从库服务器上去就行了。
接着操作步骤转移到从库上去执行,在从库上执行如下命令,把backup.sql文件里的语句都执行一遍,这就相当于把主库所有的数据都还原到从库上去了,主库上的所有database、table以及数据,在从库里全部都有了。
接着在从库上执行下面的命令去指定从主库进行复制。
CHANGE MASTER TO MASTER_HOST=’192.168.31.229’, MASTER_USER=’backup_user’,MASTER_PASSWORD=’backup_123’,MASTER_LOG_FILE=’mysql-bin.000015’,MASTER_LOG_POS=1689;
上面的master机器的ip地址我们是知道的,master上用于执行复制的用户名和密码是我们自己创建的,也没问题,但是master的binlog文件和position是怎么知道的?这不就是之前我们mysqldump导出的backup.sql里就有,大家在执行上述命令前,打开那个backup.sql就可以看到如下内容:
MASTER_LOG_FILE=’mysql-bin.000015’,MASTER_LOG_POS=1689
然后你就把上述内容写入到主从复制的命令里去了。
接着执行一个开始进行主从复制的命令:start slave,再用show slave status查看一下主从复制的状态,主要看到Slave_IO_Running和Slave_SQL_Running都是Yes就说明一切正常了,主从开始复制了。
接着就可以在主库插入一条数据,然后在从库查询这条数据,只要能够在从库查到这条数据,就说明主从复制已经成功了。
这仅仅是最简单的一种主从复制,就是异步复制,就是之前讲过的那种原理,从库是异步拉取binlog来同步的,所以肯定会出现短暂的主从不一致的问题的,比如你在主库刚插入数据,结果在从库立马查询,可能是查不到的。
只要你搭建出来主从复制架构,就可以实现读写分离了。比如可以用mycat或者sharding-sphere之类的中间件,就可以实现你的系统写入主库,从从库去读取了。
但是现在搭建出来的主从复制架构有一个问题,那就是之前那种搭建方式他默认是一种异步的复制方式,也就是说,主库把日志写入binlog文件,接着自己就提交事务返回了,他也不管从库到底收到日志没有。那万一此时要是主库的binlog还没同步到从库,结果主库宕机了,此时数据不就丢失了么?即使你做了高可用自动切换,一下子把从库切换为主库,但是里面是没有刚才写入的数据的,所以这种方式是有问题的。因此一般来说搭建主从复制,都是采取半同步的复制方式的,这个半同步的意思,就是说,你主库写入数据,日志进入binlog之后,起码得确保 binlog日志复制到从库了,你再告诉客户端说本次写入事务成本了是不是?这样起码你主库突然崩了,他之前写入成功的数据的binlog日志都是到从库了,从库切换为主库,数据也不会丢的,这就是所谓的半同步的意思。
这个半同步复制,有两种方式,第一种叫做AFTER_COMMIT方式,他不是默认的,他的意思是说,主库写入日志到binlog,等待binlog复制到从库了,主库就提交自己的本地事务,接着等待从库返回给自己一个成功的响应,然后主库返回提交事务成功的响应给客户端。另外一种是现在MySQL 5.7默认的方式,主库把日志写入binlog,并且复制给从库,然后开始等待从库的响应,从库返回说成功给主库了,主库再提交事务,接着返回提交事务成功的响应给客户端。总而言之,这种方式可以保证你每个事务提交成功之前,binlog日志一定都复制到从库了,所以只要事务提交成功,就可以认为数据在从库也有一份了,那么主库崩溃,已经提交的事务的数据绝对不会丢失的。
搭建半同步复制也很简单,在之前搭建好异步复制的基础之上,安装一下半同步复制插件就可以了,先在主库中安装半同步复制插件,同时还得开启半同步复制功能:
install plugin rpl_semi_sync_master soname ‘semisync_master.so’;
set global rpl_semi_sync_master_enabled=on;
show plugins;
可以看到你安装了这个插件,那就ok了。接着在从库也是安装这个插件以及开启半同步复制功能:
install plugin rpl_semi_sync_slave soname ‘semisync_slave.so’;
set global rpl_semi_sync_slave_enabled=on;
show plugins;
接着要重启从库的IO线程:stop slave io_thread; start slave io_thread;
然后在主库上检查一下半同步复制是否正常运行:show global status like ‘%semi%’;,如果看到了Rpl_semi_sync_master_status的状态是ON,那么就可以了。到此半同步复制就开启成功了,其实一般来说主从复制都建议做成半同步复制,因为这样配合高可用切换机制,就可以保证数据库有一个在线的从库热备份主库的数据了,而且主要主库宕机,从库立马切换为主库,数据不丢失,数据库还高可用。
上面是MySQL传统的主从复制搭建方式,其实一般大家在生产中都会采用半同步的复制模式,但是其实除了那种传统搭建方式之外,还有一种更加简便一些的搭建方式,就是GTID搭建方式。
首先在主库进行配置:
gtid_mode=on
enforce_gtid_consistency=on
log_bin=on
server_id=单独设置一个
binlog_format=row
接着在从库进行配置:
gtid_mode=on
enforce_gtid_consistency=on
log_slave_updates=1
server_id=单独设置一个
接着按照之前的步骤在主库创建好用于复制的账号之后,就可以跟之前一样进行操作了,比如在主库dump出来一份数据,在从库里导入这份数据,利用mysqldump备份工具做的导出,备份文件里会有SET @@GLOBAL.GTID_PURGED=**一类的字样,可以照着执行一下就可以了。接着其余步骤都是跟之前类似的,最后执行一下show master status,可以看到executed_gtid_set,里面记录的是执行过的GTID,接着执行一下SQL:select from gtid_executed,可以查询到,对比一下,就会发现对应上了。
其实无论是GTID复制,还是传统复制,都不难,很简单,往往这就是比较典型的MySQL主从复制的搭建方式了,然后MyCat中间件或者是Sharding-Sphere的官方文档,其实也都不难,照着文档做,整合到Java代码里去,就可以做出来基于主从复制的读写分离的效果了。那些中间件都是支持读写分离模式的,可以仅仅往主库去写,从从库去读,这都没问题的。如果落地到项目里,那么就完成了一个主从架构以及读写分离的架构了,此时按照我们之前所说的,如果说你的数据库之前对一个库的读写请求每秒总共是2000,此时读写分离后,也许就对主库每秒写TPS才几百,从库的读QPS是1000多。
那么万一你要是从库的读QPS越来越大,达到了每秒几千,此时你是不是会压力很大?没关系,这个时候你可以给主库做更多的从库,搭建从库,给他挂到主库上去,每次都在凌晨搞,先让系统停机,对外不使用,数据不更新。接着对主库做个dump,导出数据,到从库导入数据,做一堆配置,然后让从库开始接着某个时间点开始继续从主库复制就可以了,一旦搭建完毕,就等于给主库挂了一个新的从库上去,此时继续放开系统的对外限制,继续使用就可以了,整个过程基本在1小时以内。如果在凌晨比如2点停机1小时,基本对业务是没有影响的。
主从复制架构中的数据延迟问题,应该如何解决?
主从复制可能会有较大的延迟。这个延迟是什么意思呢?就是说主库可能你都写入了100条数据了,结果从库才复制过去了50条数据,那么从库就比主库落后了50条数据。这就是所谓的主从延迟的问题。
可是为什么会产生这个主从延迟的问题呢?也很简单,其实你主库是多线程并发写入的,这个大家都知道的,所以主库写入数据的速度可能是很快的,但是从库是单个线程缓慢拉取数据的,所以才会导致从库复制数据的速度是比较慢的。那自然会导致主从之间的延迟问题了。
那么这个主从之间到底延迟了多少时间呢?这个可以用一个工具来进行监控,比较推荐的是percona-toolkit工具集里的pt-heartbeat工具,他会在主库里创建一个heartbeat表,然后会有一个线程定时更新这个表里的时间戳字段,从库上就有一个monitor线程会负责检查从库同步过来的heartbeat表里的时间戳。把时间戳跟当前时间戳比较一下,其实就知道主从之间同步落后了多长时间了,主从之间延迟了多长时间,我们这里实际上是可以看到的。
那么这个主从同步延迟的问题,会导致一些什么样的不良情况呢?如果你做了读写分离架构,写都往主库写,读都从从库读,那么会不会你的系统刚写入一条数据到主库,接着代码里立即就在从库里读取,可能此时从库复制有延迟,你会读不到刚写入进去的数据!没错,就是这个问题。另外就是有可能你的从库同步数据太慢了,导致你从库读取的数据都是落后和过期的,也可能会导致你的系统产生一定的业务上的bug。
所以针对这个问题,首先你应该做的,是尽可能缩小主从同步的延迟时间,那么怎么做呢?其实就是让从库也用多线程并行复制数据就可以了,这样从库复制数据的速度快了,延迟就会很低了。
MySQL 5.7就已经支持并行复制了,可以在从库里设置slave_parallel_workers>0,然后把slave_parallel_type设置为LOGICAL_CLOCK,就ok了。
另外,如果你觉得还是要求刚写入的数据你立马强制必须一定可以读到,那么此时你可以使用一个办法,就是在类似MyCat或者Sharding-Sphere之类的中间件里设置强制读写都从主库走,这样你写入主库的数据,强制从主库里读取,一定立即可以读到的。
总体而言就是这样了,在落实读写分离架构的时候,要注意一下复制方式,是异步还是半同步?如果说你对数据丢失并不是强要求不能丢失的话,可以用异步模式来复制,再配合一下从库的并行复制机制。如果说你要对MySQL做高可用保证数据绝对不丢失的话,建议还是用半同步机制比较好一些,同理最好是配合从库的并行复制机制。
数据库高可用:基于主从复制实现故障转移
那么读写分离的模式确定了,接着就可以来考虑一下数据库的高可用架构了,所谓的高可用就是说,如果数据库突然宕机了一台机器,比如说主库或者从库宕机了,那么数据库还能正常使用吗?其实如果从库宕机了影响并不是很大,因为大不了就是让所有的读流量都从主库去读就可以了,但是如果主库宕机了呢?那就真的麻烦了,因为主库一旦宕机,你就没法写入数据了,从库毕竟是不允许写入的,只允许读取。
所以有没有一种办法,可以在主库宕机之后,就立马把从库切换为主库呢,然后所有人都对从库切换为的主库去写入和读取呢?如果能实现这样的一个效果,那数据库不就实现高可用了吗?没错,就这么简单,这就是数据库的高可用架构。一般生产环境里用于进行数据库高可用架构管理的工具是MHA,也就是Master High Availability Manager and Tools for MySQL,是日本人写的,用perl脚本写的一个工具,这个工具就是专门用于监控主库的状态,如果感觉不对劲,可以把从库切换为主库。
这个MHA自己也是需要单独部署的,分为两种节点,一个是Manager节点,一个是Node节点,Manager节点一般是单独部署一台机器的,Node节点一般是部署在每台MySQL机器上的,因为Node节点得通过解析各个MySQL的日志来进行一些操作。Manager节点会通过探测集群里的Node节点去判断各个Node所在机器上的MySQL运行是否正常,如果发现某个Master故障了,就直接把他的一个Slave提升为Master,然后让其他Slave都挂到新的Master上去,完全透明。
