共计 7681 个字符,预计需要花费 20 分钟才能阅读完成。
是否为 MySQL 线上库的备份而烦恼过,这里提供一个完整的备份从属数据库的备份方案,亲测可用。
说明:
- 备份从库, 按周计, 每周进行一次全备
- 每周一的早上六点进行全备, 其他时间备份中继日志
- 在从库上启用 rsync 服务, 用于异地备份
- 在本地服务器使用 rsync 命令定时同步数据库的备份
- 此备份可用于为 Master 添加新的 Slave, 也可以用于还原 Master
一、服务器端配置
1、Python 编写的备份脚本
root@DBSlave:~# cat /scripts/mysql_slave_backup.py | |
#!/usr/bin/env python | |
#-*- coding:utf-8 -*- | |
import os | |
import datetime,time | |
# 请在 linux 系统中安装 zip 和 unzip | |
# 备份策略示例 | |
''' | |
1. 每周进行一次全备, 其他都是备份中继日志 | |
2. 每周一凌晨 6:00 数据库全备份 | |
3. 周二至周日, 每天中午 12:00, 下午 18:00, 早上 6:00, 备份中继日志 | |
''' | |
# 规划备份目录 | |
# 备份目录以周为单位进行创建 | |
# "%W": 一年中的第几周,以周一为每星期第一天(00-53) | |
Date_Time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") # %F: 年月日 | |
Week_Date = datetime.datetime.now().strftime("%Y-%W") # 年 / 当前是本年的第几周 | |
Dir = "/data/backup" | |
Backup_Dir = Dir+ '/' + Week_Date | |
# -- 创建备份目录 , 每周生成一个目录, 因为每周做一次全备 | |
if os.path.isdir(Backup_Dir) is False: | |
os.makedirs(Backup_Dir) | |
# 设置数据库连接信息 | |
#mysqldump 选项 | |
# --skip-tz-utc : 保持和表导出前的时区是一样的 | |
# --master-data=2 : 备份时写入 "change master to" 语句并且注释, 等于 1 时, 则不会注释 | |
# --dump-slave=2 : 备份 slave 的数据库,为 master 新增 slave 时使用. | |
# --quick : 一次从行中的服务器检索表的行, 作用是加快导出表 | |
# --routines : 导出存储过程 | |
# --triggers : 导出触发器 | |
# --set-gtid-purged=OFF : 防止备份数据导入新的实例时与其 GTID 发生冲突, 所以在备份数据时不添加 GTID 信息 | |
# --single-transaction : 在从服务器转储数据之前发出 BEGIN SQL 语句, 尽量保证数据的一致性, 但是这个参数只适用于 innodb 这样的存储引擎 | |
# --dump-slave=2 : 备份时写入从库连接主库的 change 语句并且注释, 等于 1 时, 则不会注释 | |
# 设置数据库备份信息 | |
DB = '-uroot -p123456' # 指定登录账号和密码 | |
ARG = '--dump-slave=2 --skip-tz-utc --routines --triggers --quick --default-character-set=utf8 --single-transaction' # 指定备份参数 | |
DB_NAME = "dbname" # 数据库名称 | |
Back_DBNAME = DB_NAME + '_' + Date_Time + '.sql' # 数据库备份名称 | |
Logs_File = Backup_Dir + '/logs' # 指定备份时日志输出的文件 | |
Mysql_Bin = "/usr/bin/mysql" # 指定 [mysql] 命令所在路径 | |
MysqlDump_Bin = "/usr/bin/mysqldump" # 指定 [mysqldump] 命令所在路径 | |
Relay_Log_Dir = "/data/logs/relay_log" #指定中继日志 | |
Relay_Log_Info = "/data/logs/relay_log/relay-bin.info" # 用于获取当前正在使用的中继日志 | |
# 定义删除旧备份 | |
def Del_Old(): | |
'''删除 36 天前的旧备份''' | |
OLD_Files = os.popen("find %s -type f -mtime +36"%(Dir)).readlines() | |
if len(OLD_Files) > 0: | |
for OLD_FIle in OLD_Files: | |
FileName = OLD_FIle.split("\n")[0] | |
os.system("rm -f %s"%(FileName)) | |
# 删除空目录 | |
All_Dir = os.popen("find %s -type d"%(Dir + '/*')).readlines() | |
for Path_Dir in All_Dir: | |
Path_Dir = Path_Dir.split("\n")[0] | |
Terms = os.popen("ls %s | wc -l"%(Path_Dir)).read() | |
if int(Terms) == 0: | |
os.system("rm -rf %s"%(Path_Dir)) | |
# 备份已经同步完成的中继日志文件 | |
def ZIP_And_Del_Existed(): | |
''' | |
压缩已经同步完成的日志文件并删除, | |
为防止中继日志还没有同步完成, 就被删除, 这里作一个判断, 只压缩和删除已经同步过的中继日志 | |
''' | |
# 获取所有的中继日志 | |
Relog_List = os.popen("ls %s | grep \"^relay-bin.*\"| grep -v \"relay-bin.in*\"" % (Relay_Log_Dir)).readlines() | |
# 获取当前正在使用的中继日志文件 | |
CurRelay = os.popen("cat %s | head -n 1" % (Relay_Log_Info)).readline().split("\n")[0] | |
CurRelay_MTime = os.path.getmtime(CurRelay) # 获取当前正在使用的文件的最后修改时间 | |
# 循环所有的中继日志文件, 通过和中继日志的最后修改时间进行对比, 得到需要备份的中继日志 | |
Need_ZIP_FName = [] # 定义需要压缩和删除的文件名 | |
for FileName in Relog_List: | |
'''将修改时间小于 [当前正在使用的中继日志] 文件的文件, 加入到 列表 [Need_ZIP_FName] 中, 用于备份 / 删除.''' | |
FName = FileName.split("\n")[0] | |
FName_MTime = os.path.getmtime("%s/%s"%(Relay_Log_Dir,FName)) | |
if FName_MTime < CurRelay_MTime: | |
Need_ZIP_FName.append("%s/%s"%(Relay_Log_Dir,FName)) | |
os.system("zip -j %s/Relay_log_%s.zip %s" % (Backup_Dir, Date_Time," ".join(Need_ZIP_FName))) | |
# 获取已经压缩的中继日志文件, 然后删除 | |
for Relay_Log in Need_ZIP_FName: | |
os.system("rm -f %s"%(Relay_Log)) | |
# 开始执行备份.(判断, 如果今天是星期一则进行全备, 不是星期一则增量备份) | |
IF_Week = datetime.datetime.now().strftime('%w') | |
if int(IF_Week) == 1: | |
# 匹配是否已经存在全备 | |
Test = os.popen('ls %s | grep -E \"^%s.*([0-9]{2}-[0-9]{2}-[0-9]{2}).sql.zip\"| wc -l'%(Backup_Dir,DB_NAME)).readline() | |
if int(Test) == 0: | |
# 如果星期一已经进行全备, 则开始增量备份 | |
with open(Logs_File,'a+') as file: | |
file.writelines("####---------- 分界线 ----------####\n") | |
file.writelines("###start >>> 全备 datetime : %s\n"%(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) | |
file.writelines("### 今天是周 %s\n"%(IF_Week)) | |
file.writelines("### stop slave\n") | |
os.system("%s %s -e \"stop slave\""%(Mysql_Bin,DB)) | |
file.writelines("### status slave\n") | |
Show_Slave = os.popen("%s %s -e \"show slave status\G\""%(Mysql_Bin,DB)).readlines() | |
file.writelines(Show_Slave) | |
file.writelines("### backup\n") | |
os.system("%s %s %s %s > %s/%s"%(MysqlDump_Bin,DB,ARG,DB_NAME,Backup_Dir,Back_DBNAME)) | |
file.writelines("### backup done && start slave | datetime : %s\n"%(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) | |
os.system("%s %s -e \"start slave;\""%(Mysql_Bin,DB)) | |
time.sleep(5) | |
file.writelines("### slave status\n") | |
Show_Slave = os.popen("%s %s -e \"show slave status\G\"" % (Mysql_Bin, DB)).readlines() | |
file.writelines(Show_Slave) | |
file.writelines("###done >>> 全备完成 \n") | |
os.system("zip -j %s/%s.zip %s/%s"%(Backup_Dir,Back_DBNAME,Backup_Dir,Back_DBNAME)) | |
os.system("rm %s/%s"%(Backup_Dir,Back_DBNAME)) | |
file.writelines("\n\n\n\n\n") | |
Del_Old() | |
else: | |
with open(Logs_File,'a+') as file: | |
file.writelines("####---------- 分界线 ----------####\n") | |
file.writelines("###start >>> 增量备份 datetime : %s\n"%(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) | |
file.writelines("### 今天是周 %s\n"%(IF_Week)) | |
file.writelines("### stop slave\n") | |
os.system("%s %s -e \"stop slave\""%(Mysql_Bin,DB)) | |
file.writelines("### status slave\n") | |
Show_Slave = os.popen("%s %s -e \"show slave status\G\""%(Mysql_Bin,DB)).readlines() | |
file.writelines(Show_Slave) | |
file.writelines("### backup\n") | |
ZIP_And_Del_Existed() | |
file.writelines("### backup done && start slave | datetime : %s\n"%(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) | |
os.system("%s %s -e \"start slave;\""%(Mysql_Bin,DB)) | |
time.sleep(5) | |
file.writelines("### slave status\n") | |
Show_Slave = os.popen("%s %s -e \"show slave status\G\"" % (Mysql_Bin, DB)).readlines() | |
file.writelines(Show_Slave) | |
file.writelines("###done >>> 增量备份完成 \n") | |
file.writelines("\n\n\n\n\n") | |
Del_Old() | |
else: | |
with open(Logs_File, 'a+') as file: | |
file.writelines("####---------- 分界线 ----------####\n") | |
file.writelines("###start >>> 增量备份 datetime : %s\n" % (datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) | |
file.writelines("### 今天是周 %s\n" % (IF_Week)) | |
file.writelines("### stop slave\n") | |
os.system("%s %s -e \"stop slave\"" % (Mysql_Bin, DB)) | |
file.writelines("### status slave\n") | |
Show_Slave = os.popen("%s %s -e \"show slave status\G\"" % (Mysql_Bin, DB)).readlines() | |
file.writelines(Show_Slave) | |
file.writelines("### backup\n") | |
ZIP_And_Del_Existed() | |
file.writelines("### backup done && start slave | datetime : %s\n" % (datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) | |
os.system("%s %s -e \"start slave;\"" % (Mysql_Bin, DB)) | |
time.sleep(5) | |
file.writelines("### slave status\n") | |
Show_Slave = os.popen("%s %s -e \"show slave status\G\"" % (Mysql_Bin, DB)).readlines() | |
file.writelines(Show_Slave) | |
file.writelines("###done >>> 增量备份完成 \n") | |
file.writelines("\n\n\n\n\n") | |
Del_Old() |
2、计划任务
root@DBSlave:~# cat /etc/cron.d/general | |
#mysql backup | |
0 6 * * * root python /scripts/mysql_slave_backup.py | |
0 12 * * * root python /scripts/mysql_slave_backup.py | |
0 18 * * * root python /scripts/mysql_slave_backup.py |
3、rsync 配置
root@DBSlave:~# cat /etc/rsyncd.conf | |
uid = 0 | |
gid = 0 | |
use chroot = yes | |
address = "当前主机公网地址" | |
port = 8638 | |
log file = /var/log/rsync.log | |
pid file = /var/run/rsync.pid | |
hosts allow = "只允许某个 IP 连接" | |
[databases] | |
path = /data/backup/ | |
comment = databases | |
read only = yes | |
dont compress = *.gz *.bz2 *.zip | |
# 只允许 remoteuser 用户 | |
auth users = remoteuser | |
secrets file = /etc/rsyncd_users.db |
root@DBSlave:~# cat /etc/rsyncd_users.db | |
# 格式: 用户名: 密码 | |
remoteuser:password |
二、本地备份主机配置
1、创建 rsync 密码文件
root@localhost:~# cat /etc/server.pass | |
remoteuser:password |
2、同步脚本
root@localhost:~# cat /scripts/backup.sh | |
#!/bin/bash | |
SSH=$(which ssh) | |
Logs_Dir="/Backup/logs.txt" | |
Rsync=$(which rsync) | |
Project="databases" | |
Dest="/Backup/" | |
IF_DEL_FILE=$(find ${Dest} -type f -mtime +36 -name "*" | wc -l) | |
DEL_FILE=$(find ${Dest} -type f -mtime +36 -name "*") | |
# 删除旧备份 | |
RMOLD(){if [ "${IF_DEL_FILE}" -gt "0" ] | |
then | |
for filename in ${DEL_FILE} | |
do | |
rm -f ${filename} | |
done | |
rmdir ${Dest}* # 删除空目录 | |
fi | |
} | |
# 执行同步命令 | |
Backup(){echo "### ---------- datetime : `date +%F-%H-%M-%S` ---------- ###" >> ${Logs_Dir} | |
echo "# start rsync" >> ${Logs_Dir} | |
${Rsync} -azH --password-file=/etc/server.pass --bwlimit=300 --port=8638 remoteuser@数据库 rsync 监听的 IP 地址::${Project} ${Dest} &>> ${Logs_Dir} | |
echo "### end rsync ---------- dateime : `date +%F-%H-%M-%S` ---------- ###" >> ${Logs_Dir} | |
echo -e "\n\n\n\n\n" >> ${Logs_Dir} | |
RMOLD | |
} | |
# 判断如果当前正在同步则不再执行同步命令 | |
IFProcess(){ps -ef | grep "${Rsync} -azH --password-file=/etc/server.pass --bwlimit=300 --port=8638 remoteuser@数据库 rsync 监听的 IP 地址::${Project}" | grep -v "grep" &> /dev/null | |
if [["$?" == 0]] | |
then | |
exit 0 | |
else | |
Backup | |
fi | |
} | |
IFProcess |
3、计划任务
root@localhost:~# cat /etc/cron.d/general | |
01 23 * * * root /bin/sh /scripts/backup.sh |
:
正文完
星哥玩云-微信公众号
