天天看点

在ECS上自建MySQL手工容灾环境

1.环境介绍

很多云上的客户因为数据库规模小,可能最开始只购买了ecs,而他们也需要数据库服务器,很可能会采用自建mysql来实现。阿里云rds已经提供了完全的数据库运维服务,包括监控、优化、主备高可用等。而对小白用户来说,为了自己业务连续性和学习的需要,也是很有必要了解如何自建mysql主备以及主备切换流程的。在主从复制实施前,单机单实例缺乏实时数据复制方案来保证实例出现故障时提供另一个完好且独立的实例迅速切换,短时间内恢复客户业务。

2.数据库主从复制实施

2.1.主从复制原理

mysql提供简单可靠的主从复制机制供用户使用,基本原理如下图所示,分三个步骤:

(1) master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events);

(2) slave将master的binary log events拷贝到它的中继日志(relay log);

(3) slave重做中继日志中的事件,将改变反映它自己的数据。

在ECS上自建MySQL手工容灾环境

2.2.主从复制规划

mysql提供了主从复制组件,但是并不提供ha的自动监控切换。阿里集团内部有自研工具可以实现ha功能,开源组件keepalived + lvs也提供类似功能。这里我们提一个带slb的方案,自动ha的实现留给读者去研究。假设我们有2个mysql实例,一个实例为a,一个为b,我们考虑使用主从双向复制,即mm(master-master)的方案进行部署,前端挂给slb,在同一个时刻,slb只设置一个主用的实例权重为100,另一个备用实例权重为0,且将该实例的read_only参数打开。在发生故障时,将备用实例的权重设成100,主用实例设成0,这样通过slb来实现手工的ha处理。对应用来说,将出现短时间(1-2分钟)的不可用,当slb的切换完成后,应用通过连接池的重连机制,能重新恢复业务。

2.3.主从复制实施

(1) 生产实例在一个已经存在的ecs上已经准备好,申请一个新的ecs,保证该ecs不与生产实例的ecs在同一个物理机nc。如果生产实例的mysql是安装在系统盘,可以从系统盘创建快照后打镜像,新ecs依旧该自定义镜像进行创建,这样可以省略步骤

(2) 在新的ecs上安装配置mysql,尽可能保证配置与生产实例完全一致;

(3) 编辑生产实例的的/etc/my.cnf文件,加入如下内容:

log-bin=/home/mysql/log/mysql-bin

server_id=1241823306

binlog_cache_size=32k

max_binlog_cache_size=2g

max_binlog_size=500m

binlog-format=row

sync_binlog=1

log-slave-updates=1

expire_logs_days=0

以上内容重点配置binlog,除server_id需要修改成ip地址后两位+端口号以外,其他直接复制。比如示例中的3017441834的含义就是该mysql所在服务器ip地址为:*.*.124.182,mysql占用的端口是3306。

同时,需要保证/home/mysql/log目录存在,mysql的起停用户必须拥有对该目录的权限。

(4) 快速重启mysql生产实例,然后登录mysql查看配置是否生效。

mysql数据库都支持下面语句来停止实例:

mysqladmin -h127.0.0.1 -uroot -p3306 shutdown

查看配置生效与否采用类似下列的语句进行检查:

show variables like ’ log-bin%’

(5) 如果配置在生产实例生效,同样的操作请在备用实例也操作一遍;

(6) 执行flush tables with read lock锁定生产实例;

(7) 将生产实例的数据同步到备用实例,至少有两种方案:对于生产实例与备用实例部署完全一样的,可以直接将datadir目录下所有文件和目录远程复制到备用实例。对于生产实例和备用实例部署并不相同的,可以采用mysqldump将生产实例数据按db导出,然后再导入到备用实例中。整个过程生产实例不能解锁;

(8) 解锁生产实例:unlock tables;

(9) 查询生产实例的master状态,记录下来binlog日志文件名以及对应的position编号;

(10) 在备用实例执行以下语句:

install plugin rpl_semi_sync_master soname 'semisync_master.so';

install plugin rpl_semi_sync_slave soname 'semisync_slave.so';

set global rpl_semi_sync_master_enabled = 1;

set global rpl_semi_sync_master_timeout = 10000;

set global rpl_semi_sync_slave_enabled = 1;

grant replication slave,replication client on . to repuser@'%'identified by 'repuser';

这里,默认repuser的密码也是repuser,但是mysql5.7版本的强制要求,密码复杂度必须比较高,所以密码需要设置成复杂密码。

change master to master_host='生产实例ip',master_port=3306(生产实例端口),master_user='repuser',master_password='repuser', master_log_file=’步骤9记录下的binlog文件名’, master_log_pos=’步骤9记录下的binlog position编号’;

(11) 启动备用实例slave,从生产实例复制数据。执行:start slave

(12) 检查备用实例slave状态:show slave status \g;重点看两个参数:

slave_io_running和slave_sql_running,如果这两个参数值都为yes。则配置正确,已经开始复制;

(13) 接下来配置从备用实例到生产实例的反向数据复制,在备用实例检查master状态:show master status;同样记录下binlog文件名和位点信息;

(14) 在生产实例重复步骤10-12完成反向复制配置。

(15) 结合slb进行业务测试,观察生产实例的数据是否实时同步到备用实例;

(16) 最后需要向所有实例的/etc/my.cnf文件添加以下内容:

rpl_semi_sync_master_enabled=on

rpl_semi_sync_slave_enabled=on

同时,我们将备用实例设成只读状态:

set global read_only=1

3.灾备切换流程

在ECS上自建MySQL手工容灾环境

流程图里a实例代表生产实例,实例b代表备用实例。

(1) 当a实例发生故障时,首先考虑重启或者修复a,如果在短时间(30分钟内)实例a能正常启动,且启动日志和业务侧检查没有问题,那么证明业务恢复正常,紧急处理成功;

(2) 如果实例a紧急重启成功,但是发现日志中有报错,请分析日志报错,判断问题的影响范围和大小,以决定忽略报错或者修复报错。如果在30分钟内完成,依然可以认为业务成功恢复;

(3) 如果紧急重启实例a成功,日志中的报错无法在30分钟内修复,那么我们启动切换流程。即:

将备用实例可读可写打开:set global read_only=0

在slb将a实例的权重从100调成0,将b实例的权重从0调成100。保证业务先恢复起来;

(4) 实例b升成主实例对外提供服务时,我们在后台尽快想办法修复a实例,如果a实例修复成功,检查ba两个实例之间的复制关系是否正常,如果复制恢复正常,我们的紧急处理结束;

(5) 如果实例b生成主实例对外提供服务时,尽管我们修复了实例a。但是两个实例间的复制关系无法再恢复正常,请根据2.3节中的步骤重新配置主从复制;

(6) 如果实例a在发生故障时完全无法恢复,比如ecs已经不能启动。那么直接进入切换流程,将实例b升级成主实例。然后在后台紧急处理和恢复原来的a实例。

4.mysql同步失败修复

4.1.同步初始化失败

在从库上面执行:show slave status\g;

正常情况:

在ECS上自建MySQL手工容灾环境

错误情况:有no出现。

修复方式:

在主库上面执行:

reset master;

在从库上面执行:

stop slave;

reset slave;

change master to master_host='10.101.3.10',master_port=3301,master_user='repuser',master_password='repuser',master_auto_position=1;

4.2.同步了部分数据中断

在从库上面执行 show slave status\g;

例如:

在ECS上自建MySQL手工容灾环境

对于slave_sql_running: no 的情况可以做如下处理:

slave stop;

set global sql_slave_skip_counter = 1;

slave start;

然后通过show slave status\g; 检查是否有两个yes。