业内有句话:没有做过运维的程序员不是好的架构师。
不知道是真是假。
MySQL数据库作为各种业务系统的存储介质,在系统中起着非常重要的作用。如果数据库崩溃,读写数据库的操作会受到影响。如果不能快速恢复,对业务的影响会非常大。之前b站不是发生过事故吗?花了2个小时才恢复。详情请看之前写的文章。
b站塌了。总结“高可用性”和“异地多活动”
经过上次折腾ELK日志搜索平台,开发环境可以正常查询日志了。最近在做系统高可用相关的工作。这次分享的是MySQL双主Keepalived的高可用性落地和踩坑。
一文将带你搭建一套ELK栈日志平台。
对于MySQL的高可用性,主要分两步。配置MySQL主模式并保持软件有效。拓扑图如下所示:
两个数据库分别部署在两个服务器上,相互同步数据,但只提供一个数据库供外部访问。当一个数据库宕机时,另一个数据库可以继续提供服务,并且只能在没有keepalived软件帮助的情况下手动切换。
检测和重启:在两台服务器上都部署了keepalived软件,定期检测mysql服务是否正常。如果数据库服务崩溃,keepalived将尝试使用脚本重新启动MySQL服务。备份:两个keepalived服务都提供虚拟IP供客户端使用,但流量只会转移到一个MySQL服务。虚拟IP:配置IP:keepalived后,会有一个虚拟IP。对于客户端来说,连接的是哪个MySQL并不重要,访问虚拟IP即可。流量切换:如果客户端正在访问的MySQL服务崩溃,keepalived会用我们写的脚本自动重启MySQL。如果重启失败,脚本会主动停止keepalived,这样客户端的流量就不会访问这个服务器上的MySQL服务,后续的访问流量会切换到另一个MySQL服务上。检测和重启的原理如下:
所需内容如下:
在两台Ubuntu服务器上启动MySQL容器。配置MySQL主从复制架构。将MySQL主从改为主-主复制架构。两台服务器设置keepalived环境来监控MySQL并自动重启MySQL。
至于MySQL的master架构,其实原理就是两台服务器互为主从,双向复制。复制的原理如下:
主从复制主要包括以下过程:
主库在binlog中记录数据变化;
从库会在一定的时间间隔内检查主库的binlog,如果有变化,会启动一个I/O线程请求读取主库中的Binlog;
同时,主库为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并将其存储在从节点的本地中继日志中。从库将启动SQL线程从中继日志中读取二进制日志并在本地重放,使其数据与主节点一致。最后,I/O线程和SQL线程将进入睡眠状态,等待下一次唤醒。
白话文是:
从库会生成两个线程,一个I/O线程,一个SQL线程;
I/O线程将请求主库的binlog,并将获得的binlog写入本地中继日志文件;
主库将生成一个转储线程,将binlog传递给从库I/O线程;
SQL线程,会读取中继日志文件中的日志,解析成SQL语句逐一执行;
接下来我们先在两台Ubuntu服务器上搭建好MySQL的基础环境,后续的操作都以此为基础。
作为演示,我在这台机器上启动了两台Ubuntu虚拟机,安装了docker。因为我们的测试和生产环境是由Docker运行的,所以我将环境的映像打包并恢复到我的虚拟机上。
保存测试环境的mysql映像
sudo docker save -o mysql.t
ar hcr:5000/hschub/hscmysql:0.0.2sudo chmod 777 mysql.tar两台机器导入镜像
sudo docker load -i mysql.tar
启动容器,需要注意的是需要映射本地文件夹。
sudo docker run -p 3306:3306 --name mysql \-v /home/hss/mysql/data:/var/lib/mysql \-v /home/hss/mysql/etc/mysql:/etc/mysql \-e MYSQL_ROOT_PASSWORD='123456' \-d 46b
-v 代表映射的文件夹,-d 表示后台运行,46b 代表镜像 id。
进入容器,连接 mysql,node1的mysql 密码是 123456,node2 是 123456
# 查询容器 iddocker ps# 进入 mysql 容器docker exec -it <容器 id> /bin/bash# 连接 mysqlmysql -u root -p
接下来我们配置 MySQL 的主从架构,需要注意的是后续搭建的主主架构是基于主从架构来的,区别就是修改了一部分配置。
192.168.56.11 node1,主节点
192.168.56.12 node2,从节点
server_id = 11log_bin = /var/lib/mysql/log/mysql-binbinlog-ignore-db=mysqlbinlog_format= mixedsync_binlog=100log_slave_updates = 1binlog_cache_size=32mmax_binlog_cache_size=64mmax_binlog_size=512mrelay_log = /var/lib/mysql/log/relay-binrelay_log_index = /var/lib/mysql/log/relay-bin.indexmaster_info_repository=TABLErelay-log-info-repository=TABLErelay-log-recovery
创建 /home/hss/mysql/data/log/mysql-bin 文件夹
创建 /home/hss/mysql/data/log/relay-bin 文件夹
给两个文件夹加上 777 权限,然后重启 MySQL 容器。
记住 File 和 Position,后面会用到。这里 File = mysql-bin.000008,Position = 1020。
查看挂载目录下是否有生成 all_databases.sql 文件,如下图所示:
CHANGE MASTER TO MASTER_HOST='192.168.56.11',MASTER_PORT=3306,MASTER_USER='vagrant',MASTER_PASSWORD='vagrant',MASTER_LOG_FILE='mysql-bin.000008',MASTER_LOG_POS=1020;
START salve;
如果 Slave_IO_Running 和 Slave_SQL_Running 显示 Yes,就表示启动同步成功。如下图所示:
在主库上执行以下命令显示当前连接过来的从库线程。
SHOW PROCESSLIST
如下所示,Slave has read all relay log; wating for more updates,说明从库已经同步完了。
使用上面的两个命令,我们可以判断当前的复制情况。
主节点创建 testdb 数据库和 member 表。
刷新下从节点,发现从节点自动创建了 member 表。如下图所示。
然后在主节点插入一条数据,刷新从节点后,发现从节点也自动创建了一条数据。
在节点 node2 上创建复制账户。
查看二进制日志文件和位置信息。
在节点 node1 上设置主从复制的信息,包括 ip,port,用户名,密码,二进制日志文件和位置信息。
node1 开启主从复制,查看主从复制状态
node 1 的 member 表增加一条数据(3,aaa),node2 上同步成功
① 当这台服务器上的 keepalived 发现 MySQL 服务崩了后,立刻停掉这台服务器上 keepalived 自己,这样流量就会自动切到另外一台 keepalived 服务器。② 当这台服务器上的 keepalived 发现 MySQL 服务崩了后,立刻尝试重启 MySQL 服务,如果重启失败,则停掉 keepalived 自己。和第一种方案的区别是会尝试重启 MySQL 服务。这里我配置成第二种功能场景,保障 MySQL 服务的高可用。另外可以配置 MySQL 服务异常时,发送邮件给运维或开发人员,由他们检查服务器的状态。
Keepalived 提供了一个虚拟 IP (简称 VIP),对外提供访问。当客户端连接这个虚拟 IP 后,只会访问其中一个 MySQL。MySQL 节点故障后,keepalived 执行脚本进行重启,如果重启失败,脚本自动停掉 keepalived,备用节点自动切换为主节点。
keepalived 检测和重启的流程图如下:
# 安装依赖sudo apt-get install -y libssl-devsudo apt-get install -y openssl sudo apt-get install -y libpopt-devsudo apt-get install -y libnl-devsudo apt-get install -y libnl-3-devsudo apt-get install -y libnl-genl-3.devsudo apt-get install daemonsudo apt-get install libc-devsudo apt-get install libnfnetlink-devsudo apt-get install gcc# 获取 keepalived 安装包cd /usr/localsudo susudo wget https://www.keepalived.org/software/keepalived-2.2.2.tar.gz# 解压安装包sudo tar -zxvf keepalived-2.2.2.tar.gz # 删除安装包mv keepalived-2.2.2 keepalived
配置 keepalived 软件
cd keepalived./configure --prefix=/usr/local/keepalived --disable-dependency-tracking
执行结果如下所示:
编译 keepalived 软件
sudo make && make install
执行结果如下所示:
对于 Ubuntu ,需要做一点特别的改动,创建链接
mkdir -p /etc/rc.d/init.dln -s /lib/lsb/init-functions /etc/rc.d/init.d/functions
拷贝配置文件
sudo mkdir /etc/sysconfigsudo cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/sudo cp /usr/local/keepalived/keepalived/etc/init.d/keepalived /etc/init.d/sudo cp /usr/local/keepalived/sbin/keepalived /sbin/sudo mkdir /etc/keepalivedsudo cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
修改配置文件 /etc/keepalived/keepalived.conf
daemon keepalived ${KEEPALIVED_OPTIONS}
改为
daemon -- keepalived ${KEEPALIVED_OPTIONS}
ip addr del 192.168.56.88 dev enp0s8:1ifconfig enp0s8:1 192.168.56.88 broadcast 192.168.56.255 netmask 255.255.255.0 uproute add -host 192.168.56.88 dev enp0s8:1
将命令写到 /usr/local/script/vip.sh文件中。最好将 /usr/local/script/vip.sh文件添加到服务器的开机启动项中,将 Keepalived 服务设置为开机自启动(未写)。
sudo mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.backup
修改配置文件
sudo vim /etc/keepalived/keepalived.conf
配置文件的内容如下:
global_defs { router_id MYSQL_HA #当前节点名}vrrp_script restart_mysql { script "/usr/local/keepalived/restart_mysql.sh" #重启 mysql 容器 interval 2 weight 2}vrrp_instance VI_1 { state BACKUP #两台配置节点均为BACKUP interface enp0s8 #绑定虚拟IP的网络接口 virtual_router_id 51 #VRRP组名,两个节点的设置必须一样,以指明各个节点属于同一VRRP组 priority 101 #节点的优先级,另一台优先级改低一点 advert_int 1 #组播信息发送间隔,两个节点设置必须一样 nopreempt #不抢占,只在优先级高的机器上设置即可,优先级低的机器不设置 authentication { #设置验证信息,两个节点必须一致 auth_type PASS auth_pass 123456 } track_script { restart_mysql #检测 mysql 状态,如果失败,则重启 mysql 容器 } virtual_ipaddress { #指定虚拟IP,两个节点设置必须一样 192.168.56.88 }}virtual_server 192.168.56.88 3306 { #linux虚拟服务器(LVS)配置 delay_loop 2 #每个2秒检查一次real_server状态 lb_algo wrr #LVS调度算法,rr|wrr|lc|wlc|lblc|sh|dh lb_kind DR #LVS集群模式 ,NAT|DR|TUN persistence_timeout 60 #会话保持时间 protocol TCP #使用的协议是TCP还是UDP real_server 192.168.56.11 3306 { weight 3 #权重 TCP_CHECK { connect_timeout 10 #连接超时时间 nb_get_retry 3 #重连次数 delay_before_retry 3 #重连间隔时间 connect_port 3306 #健康检查端口 } } }
编写异常处理脚本
sudo vim /usr/local/keepalived/restart_mysql.sh
内容如下,
#!/bin/bash# 定义变量,重启 mysql 容器START_MYSQL="docker restart mysql"# 定义变量,停止 mysql 容器STOP_MYSQL="docker stop mysql"# 定义变量,日志文件路径LOG_FILE="/usr/local/keepalived/logs/mysql-check.log"# 定义变量,检查 mysql 服务是否正常的命令HAPS=`ps -C mysqld --no-header |wc -l`# 打印当前时间到日志文件date "+%Y-%m-%d %H:%M:%S" >> $LOG_FILE# 打印提示信息到日志文件echo "check mysql status" >> $LOG_FILE# 检查数据库状态,如何返回 0,则重启 mysql 容器,然后休眠 3s 后,再次检测 mysql 状态,如果还是返回 0,则停止 keepalived。if < $HAPS -eq 0 >;thenecho $START_MYSQL >> $LOG_FILE$START_MYSQL >> $LOG_FILE 2>&1sleep 3if < `ps -C mysqld --no-header |wc -l` -eq 0 >;thenecho "start mysql failed, killall keepalived" >> $LOG_FILEkillall keepalivedfifi
给脚本分配权限
sudo chmod +x /usr/local/keepalived/restart_mysql.sh
创建 logs 文件夹,给 logs 文件夹分配权限
sudo mkdir /usr/local/keepalived/logssudo chmod +x /usr/local/keepalived/logs -r
重新加载配置文件
sudo systemctl daemon-reload
sudo pkill keepalivedsudo systemctl start keepalivedsudo systemctl status keepalived
启动 node1 节点:
pkilll keepalivedsudo systemctl status keepalived
我们可以通过这个命令查看 keepalived 进程
ps -ef | grep keepalived
查看日志
sudo cat /var/log/syslog
docker stop 8cc
查看 keepalived 状态,提示移除了 mysql 服务。
因为 keepalived 会每 2s 检查一次 MySQL 的状态,发现 MySQL 异常后,就会重启 mysql 容器。所以过几秒后,重新查看容器状态,会看到 mysql 容器重新启动了。
docker ps
查看 keepalived 状态,执行 restart_mysql 成功
查看执行日志
问题:每 2s 会打印一次,文件可能会很大。需要执行定期删除。
首先用 mysql 客户端工具 navicat 连接虚拟 ip 地址,账号和密码就是 node 1 和 node2 的 mysql 账号密码(root/123456)
可以连接上,然后执行以下命令,查看当前虚拟 ip 连接的是哪个数据库
SHOW VARIABLES LIKE '%hostname%'
可以看到连接的是 node2 的容器的 id,说明 keepalived 已经通过虚拟 ip 连接到 node2 的 mysql 了,是正常工作的,node2 现在是作为主节点,node1 作为备用节点。
由于本地环境重新启动 MySQL 都是成功的,不会停掉 keepalived 服务。出于演示目的,我就直接停掉 keepalived 服务。
pkill keepalived
执行下面这个命令可以查看 keepalived 进程,发现已经没有了。(控制台显示的 grep --color=auto keepalived 表示是查找命令)
ps -ef | grep keepalived
重新查询客户端的连接信息,发现已经切换到 92b (node1)机器上的 mysql 了。
SHOW VARIABLES LIKE '%hostname%'
再次查看 node1 上 keepalived 上的状态,再发送信息给
sudo systemctl status keepalived
apt-get updateapt install vim
修改 mysql 配置文件
vim /etc/mysql/my.cnf
添加一行配置,跳过 mysql 密码验证
skip-grant-tables
重启容器
docker restart 9e6
重新计入 mysql 容器,连接 mysql,不需要密码就可以连接上 mysql。
mysql
修改登录密码
update mysql.user set authentication_string=PASSWORD('123456') where User='root';
重启容器
拷贝 mysql 文件夹
解决方案:更新包
sudo apt-get -y update
解决方案:
修改配置文件
sudo mv /etc/apt/sources.list /etc/apt/sources.list.backupsudo vim /etc/apt/sources.list
配置内容如下:
deb http://archive.ubuntu.com/ubuntu/ trusty main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-updates main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-proposed main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted universe multiverse
执行更新
sudo apt-get update
解决方案:
按照这个报错信息来进行降级,等号后面就是提示信息里括号的版本信息。
sudo apt-get install libnl-3-200=3.2.21-1ubuntu4.1sudo apt-get install libnl-genl-3-200=3.2.21-1ubuntu4.1
解决方案:
systemctl unmask sshd
再次启动,提示另外一个错误。
解决方案,因为 ubuntu 没有这个命令 /etc/rc.d/init.d/functions,所以需要添加一个命令链接
mkdir -p /etc/rc.d/init.dln -s /lib/lsb/init-functions /etc/rc.d/init.d/functions
sudo mv /etc/apt/sources.list /etc/apt/sources.list.backup2sudo mv /etc/apt/sources.list.backup /etc/apt/sources.listsudo apt-get updatesudo apt-get install gcc
解决方案:
配置文件的 global_defs 配置里面增加 script_user root
global_defs { script_user rot}
给脚本添加权限。