菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
228
0

python脚本:在Ubuntu16系统上基于xtrabackup2.4和mysql5.7实现数据库数据的自动化备份和恢复,亲测有效!

原创
05/13 14:22
阅读数 33352

生产环境究竟是使用mysqldump还是xtrabackup来备份与恢复数据库?

一个合格的运维工程师或者dba工程师,如果有从事数据库方面的话,首先需要做的就是备份,如果没有备份,出现问题的话,你的业务就会出问题,你的工作甚至会。。。

所以备份是重要的,但光有备份还不行,备份后如果出现问题,你还得使用备份数据来恢复,但恢复数据的时间一般都是很长的,不符合业务需求,所以一个快速备份与恢复的软件就很有必要。

之前我在维护mysql数据库的时候,使用mysqldump来进行备份与恢复,在备份的时候锁住表,然后全部备份,在数据少的时候没问题,但如果数据很多,不允许锁表,同时需要恢复数据块的情况,mysqldump就不适合了,我在恢复一个4G数据文件的数据库的时候,恢复的数据是使用mysqldump的数据,恢复了3个小时还没有反应,造成的影响很严重,所以我开始寻找其他的软件来满足以上的需求,幸好找到了,就是使用xtrabackup来进行备份与恢复,恢复4G数据文件的数据库,仅需要14秒,同时在备份的时候不会锁表,而且支持增量备份,所以把我的比较分享给大家,希望对大家有益!


 使用xtrabackup完成数据库的自动备份和恢复过程如下:

(备注:代码写得繁琐了些,后期精简)

0 前提

ubuntu虚拟机上已安装mysql5.7


 

1 xtrabackup安装教程

官网安装教程:https://www.percona.com/doc/percona-xtrabackup/2.4/installation/apt_repo.html

安装2.3版本

root@ubuntu190:/opt/xtrabackup# wget https://repo.percona.com/apt/percona-release_0.1-6.$(lsb_release -sc)_all.deb

root@ubuntu190:/opt/xtrabackup# sudo dpkg -i percona-release_0.1-6.$(lsb_release -sc)_all.deb

此时检查是否自动生成配置文件

root@ubuntu190:/opt/xtrabackup# cat /etc/apt/sources.list.d/percona-release.list

该文件有内容则再执行

root@ubuntu190:/opt/xtrabackup# sudo apt-get update

root@ubuntu190:/opt/xtrabackup# sudo apt-get install percona-xtrabackup -y

报错

Reading package lists... Done

Building dependency tree

Reading state information... Done

You might want to run 'apt-get -f install' to correct these:

The following packages have unmet dependencies:

percona-xtrabackup-80 : Depends: libgcrypt20 (>= 1.7.0) but 1.6.5-2ubuntu0.3 is to be installed

Depends: libssl1.1 (>= 1.1.0) but it is not installable

Depends: libstdc++6 (>= 6) but 5.4.0-6ubuntu1~16.04.11 is to be installed

Conflicts: percona-xtrabackup but 2.3.10-1.xenial is to be installed

percona-xtrabackup-dbg-80 : Depends: percona-xtrabackup-80 (= 8.0.11-1.xenial) but 8.0.4-1.stretch is to be installed

E: Unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

解决

root@ubuntu190:/opt/xtrabackup# apt-get -f install -y

再次安装

root@ubuntu190:/opt/xtrabackup# sudo apt-get install percona-xtrabackup -y

查看版本

root@ubuntu190:/opt/xtrabackup# xtrabackup -version

xtrabackup version 2.3.10 based on MySQL server 5.6.24 Linux (x86_64) (revision id: bd0d4403f36)

You have new mail in /var/mail/root

此时表示安装成功。

-----------------------------------------------------------

补充(安装2.4版本):

我在后面使用2.3.10版本的xtrabackup执行备份时,提示我以下错误

root@ubuntu190:~# sudo innobackupex --defaults-file=/etc/mysql/my.cnf --user=backup --password=yayun --socket=/var/run/mysqld/mysqld.sock /data/backup/

200527 14:21:47 innobackupex: Starting the backup operation

 

IMPORTANT: Please check that the backup run completes successfully.

At the end of a successful backup run innobackupex

prints "completed OK!".

 

200527 14:21:48 version_check Connecting to MySQL server with DSN 'dbi:mysql:;mysql_read_default_group=xtrabackup;mysql_socket=/var/run/mysqld/mysqld.sock' as 'backup' (using password: YES).

200527 14:21:48 version_check Connected to MySQL server

200527 14:21:48 version_check Executing a version check against the server...

200527 14:21:48 version_check Done.

200527 14:21:48 Connecting to MySQL server host: localhost, user: backup, password: set, port: not set, socket: /var/run/mysqld/mysqld.sock

Error: Unsupported server version: '5.7.27-0ubuntu0.16.04.1-log'. Please report a bug at https://bugs.launchpad.net/percona-xtrabackup

root@ubuntu190:~#

原因是xtrabackup版本太老,2.3版本与mysql5.7不匹配,最好重新安装2.4版。如下:

root@ubuntu190:/opt/xtrabackup# wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb

root@ubuntu190:/opt/xtrabackup# sudo dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb

root@ubuntu190:/opt/xtrabackup# cat /etc/apt/sources.list.d/percona-release.list

cat: /etc/apt/sources.list.d/percona-release.list: No such file or directory

此时发现该文件中无任何内容,只能手动加入源

root@ubuntu190:/opt/xtrabackup# nano /etc/apt/sources.list.d/percona-release.list

加入以下内容

#
# Percona releases, stable
#
deb http://repo.percona.com/apt xenial main
deb-src http://repo.percona.com/apt xenial main
#
# Testing & pre-release packages
#
#deb http://repo.percona.com/apt xenial testing
#deb-src http://repo.percona.com/apt xenial testing
#
# Experimental packages, use with caution!
#
#deb http://repo.percona.com/apt xenial experimental
#deb-src http://repo.percona.com/apt xenial experimental

卸载2.3版本

root@ubuntu190:/opt/xtrabackup# sudo apt-get remove percona-xtrabackup -y

安装2.4版本

root@ubuntu190:/opt/xtrabackup# sudo apt-get update

root@ubuntu190:/opt/xtrabackup# sudo apt-get install percona-xtrabackup-24 -y

查看版本

root@ubuntu190:/opt/xtrabackup# xtrabackup -version

xtrabackup: recognized server arguments: --datadir=/var/lib/mysql --tmpdir=/tmp --server-id=1 --log_bin=/var/log/mysql/mysql_bin.log

xtrabackup version 2.4.20 based on MySQL server 5.7.26 Linux (x86_64) (revision id: c8b4056)


 2 自动化备份和恢复脚本

必须先清楚xtrabackup的原理和备份流程才能看懂以下代码,原理和备份流程参考:

MySQL入门篇(七)之Xtrabackup备份与恢复

xtrabackup 详解

流程图见

 

 

脚本见:

  1 import os
  2 
  3 import time
  4 
  5 from automatic_backup_and_recovery_mysql.tools.Ssh import Ssh
  6 
  7 
  8 class AutoBakAndRecMysql():
  9     '''
 10     目的:自动备份和恢复远程目标服务器上的数据库集群的数据
 11     备份及运行策略: 该脚本每天(24h为一个循环周期)执行一次,一共备份"bakTotalTimes"次,第1次全备份,
 12                     剩下"bakTotalTimes-1"次都是增量备份,每天完成"bakTotalTimes"次备份后,
 13                     将"bakTotalTimes-1"份增量备份目录(按照备份时间升序排列)依次恢复到当天的全备份目录,
 14                     得到一个总的全备份目录,该目录即为当天所有数据的备份。然后将其放入另一目录下备用,该目录最多放置”totalBakDirNum“个总的全备份目录。
 15                     然后根据代码提示,可以选择立即恢复数据库数据,或者不操作,默认不恢复,然后再自动进入下一个备份周期。程序永不停止运行!
 16                     放心,所有相关的备份目录都做了目录数量限制,不会影响硬盘容量。
 17     运行方式:此脚本运行在堡垒机192.168.1.190上,通过ssh远程操控目标服务器
 18     '''
 19 
 20     def __init__(self, ip, sshUsername, sshPasswd, sshPort, mysqlUsername, mysqlPasswd, mysqlPort,
 21                  backupDir="/data/backup/",
 22                  fullBackupDir="/data/backup/full/",
 23                  incrementalBackupDir="/data/backup/incremental/",
 24                  totalBackupDir="/data/backup/last/",
 25                  totalFullBakDirNum=4,
 26                  totalIncBakDirNum=10,
 27                  totalBakDirNum=5,
 28                  mysqlDataDirBakNum=5,
 29                  bakInterval=1,
 30                  bakTotalTimes=6,
 31                  mysqlSocketFile="/var/run/mysqld/mysqld.sock",
 32                  mysqlCnfFile="/etc/mysql/my.cnf",
 33                  mysqlDataDir="/var/lib/mysql",
 34                  mysqlDataDirBak="/var/lib/mysql-bak",
 35                  ):
 36         self.ip = ip
 37         # ssh连接信息
 38         self.sshUsername = sshUsername
 39         self.sshPasswd = sshPasswd
 40         self.sshPort = sshPort
 41         # 数据库连接信息
 42         self.mysqlUsername = mysqlUsername
 43         self.mysqlPasswd = mysqlPasswd
 44         self.mysqlPort = mysqlPort
 45 
 46         # 数据库配置信息
 47         self.mysqlDataDir = mysqlDataDir  # 数据库数据存储目录
 48         self.mysqlSocketFile = mysqlSocketFile  # 数据库主配置文件
 49         self.mysqlCnfFile = mysqlCnfFile  # 数据库sock文件
 50         # 备份
 51         self.backupDir = backupDir  # 备份主目录
 52         self.fullBackupDir = fullBackupDir  # 全量备份目录
 53         self.incrementalBackupDir = incrementalBackupDir  # 增量备份目录
 54         self.totalBackupDir = totalBackupDir  # 将每天得到的总的全备份目录放入该目录中
 55         self.mysqlDataDirBak = mysqlDataDirBak  # 用来存放执行数据恢复之前的数据库原数据存储目录的备份,以免自动恢复失败时还把原有的数据也丢失了
 56 
 57         self.totalFullBakDirNum = totalFullBakDirNum  # 保留几个全量备份目录
 58         self.totalIncBakDirNum = totalIncBakDirNum  # 保留几个增量备份目录
 59         self.totalBakDirNum = totalBakDirNum  # 保留几个总的全备份目录
 60         self.mysqlDataDirBakNum = mysqlDataDirBakNum  # 保留几个数据库原有数据目录的备份目录
 61 
 62         self.bakInterval = bakInterval  # 备份时间间隔,单位分钟
 63         self.bakTotalTimes = bakTotalTimes  # 总备份次数
 64 
 65     def getSsh(self):
 66         '''
 67         获取ssh操作对象
 68         :return:
 69         '''
 70         ssh = Ssh(ip=self.ip, username=self.sshUsername, password=self.sshPasswd, sshPort=self.sshPort)
 71         return ssh
 72 
 73     def judgeMysqlVersion(self):
 74         '''
 75         判断数据库版本,要求版本>=5.7,返回True表示数据库满足要求,前提:通过apt方式安装的mysql
 76         :return:
 77         '''
 78         cmd = "mysql -V | awk -F',' '{print $1}' | awk '{print $5}' |awk -F'.' '{print $1,$2}'"
 79         res = self.getSsh().runCommandToString(cmd=cmd)
 80         res = str(res, encoding="utf-8")
 81         res = res.strip("\n").split()
 82         res = "".join(res)
 83         res = int(res)  # 将版本大小转换成整型数字
 84         if res >= 57:
 85             print("%s 数据库版本>=5.7,满足要求,可以使用xtrabackup2.4" % self.ip)
 86             return True
 87         else:
 88             print("%s 数据库版本<=5.7,不满足要求,无法使用xtrabackup2.4" % self.ip)
 89             return False
 90 
 91     def isMysqlRun(self):
 92         '''
 93         检测数据库是否在运行,返回True表示运行。前提:通过apt方式安装的mysql
 94         :return:
 95         '''
 96         cmd = "service mysql status  |grep 'Active'"
 97         res = self.getSsh().runCommandToString(cmd)
 98         res = str(res, encoding="utf-8").strip("\n").split(":")
 99         isActive = res[1].strip().startswith("active")
100         return isActive
101 
102     def countDown(self):
103         '''
104         指定时间的倒计时
105         :return:
106         '''
107         for i in range(self.bakInterval, 0, -1):
108             msg = u"\r%s 系统将在 " % self.ip + str(i) + " 分钟后进行下一次备份操作"
109             print(msg, end="")
110             time.sleep(60)  # 休眠60秒
111         end_msg = "开始备份" + "  " * (len(msg) - len("开始备份"))  # 如果单纯只用“开始备份”4个字,无法完全覆盖之前的内容
112         print(u"\r" + end_msg)
113 
114     def timeoutDefault(self, timeout, default):
115         '''
116         输入超时设置,超过规定时间不输入,则自动返回默认值
117         :param timeout: 超时时间 ,单位分钟
118                default:默认值
119         :return:
120         '''
121         import signal
122         class InputTimeoutError(Exception):
123             pass
124 
125         def interrupted(signum, frame):
126             raise InputTimeoutError
127 
128         signal.signal(signal.SIGALRM, interrupted)
129         signal.alarm(timeout * 60)
130 
131         try:
132             choice = input("%s 是否恢复数据库数据?(y/n)" % self.ip)
133         except InputTimeoutError:
134             print("\n%s 在 %s 分钟内未输入内容,则默认输入'%s'" % (self.ip, timeout, default))
135             choice = default  # 设置input的默认值
136         signal.alarm(0)  # 读到输入的话重置信号
137 
138         return choice
139 
140     def installXtrabackup(self):
141         '''
142         在Ubuntu上安装xtrabackup2.4版
143         :return:
144         '''
145         checkXtrabackup = self.getSsh().runCommandToStringList(" xtrabackup -version")
146         if checkXtrabackup[-1].find("2.4") != -1:
147             print("%s xtrabackup已安装" % self.ip)
148         else:
149             self.getSsh().runCommandToString(
150                     "wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb")
151             self.getSsh().runCommandToString("sudo dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb")
152             res = self.getSsh().runCommandToStringList("cat /etc/apt/sources.list.d/percona-release.list")
153 
154             if "No such file or directory" in res[0]:
155                 print("%s 更新xtrabackup源文件失败" % self.ip)
156                 # 此时手动加入源
157                 rootFilePath = os.path.dirname(__file__) + "/tools/percona-release.list"  # 堡垒机上的文件
158                 targetDir = "/etc/apt/sources.list.d/"  # 目标服务器上的目录
159                 os.system("apt install sshpass -y")
160                 os.system("sshpass -p  %s  scp -r  %s   %s@%s:%s" % (
161                     self.sshPasswd, rootFilePath, self.sshUsername, self.ip, targetDir))  # 复制源文件到目标服务器
162                 res2 = self.getSsh().runCommandToStringList("cat /etc/apt/sources.list.d/percona-release.list")
163                 if len(res2) <= 1 and "No such file or directory" in res[0]:
164                     print("%s 创建xtrabackup源文件失败" % self.ip)
165                 else:
166                     print("%s 创建xtrabackup源文件成功" % self.ip)
167                     self.getSsh().runCommandToString(
168                             "sudo apt-get update &&  sudo apt-get install percona-xtrabackup-24  -y")
169                     res3 = self.getSsh().runCommandToStringList(" xtrabackup -version")
170                     if res3[-1].find("2.4") != -1:  # 检测xtrabackup版本
171                         print("%s xtrabackup安装成功" % self.ip)
172                     else:
173                         print("%s xtrabackup安装失败" % self.ip)
174 
175     def bakMysqlOriginalData(self):
176         '''
177         将数据库原有的数据目录转移到其他目录,以便在自动恢复失败时能手动恢复数据。
178         :return:
179         '''
180         res = self.getSsh().runCommandToString("mkdir -p  %s" % self.mysqlDataDirBak)
181         print("%s 数据库原有数据的备份目录 %s 创建成功" % (self.ip, self.mysqlDataDirBak))
182         now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))  # 当前时间,格式2020-06-02-10-44-43
183         self.getSsh().runCommandToString("mkdir %s/mysql-%s" % (self.mysqlDataDirBak, str(now)))
184         self.getSsh().runCommandToString("mv   %s   %s/mysql-%s" % (self.mysqlDataDir, self.mysqlDataDirBak, str(now)))
185         self.getSsh().runCommandToString(
186                 " chown -R mysql.mysql  %s" % self.mysqlDataDirBak)  # 转移过去的目录和文件的所属组和所属用户都得手动修改为mysql
187         print("%s 数据库原有数据目录 %s 备份成功" % (self.ip, self.mysqlDataDir))
188 
189     def limitDirNums(self, flag):
190         '''
191         限制指定目录的子目录数量,按照创建时间降序排列,只保留时间最新的指定个数的子目录
192         :param flag: 标志位,为0则限制全量备份目录,为1则限制增量备份目录,为2则限制总的全量备份目录,为3则限制数据库原有数据目录的备份目录
193         :return:
194         '''
195         if flag == 0:
196             cmd = """find  %s  -mindepth 1 -maxdepth 1 -type d -printf "%%P\n" | sort -nr | head -%s""" % (
197                 self.fullBackupDir, self.totalFullBakDirNum)
198             res = self.getSsh().runCommandToStringList(cmd)  # 需要保留的目录
199             allDirPath = self.getSsh().runCommandToStringList("ls %s" % self.fullBackupDir)
200             dirDelete = [path for path in allDirPath if path not in res]  # 需要被删除的目录
201             dirRootDelete = [self.fullBackupDir + path + "/" for path in dirDelete]
202             [self.getSsh().runCommandToString("rm -r %s" % path) for path in dirRootDelete]
203             print("%s 上的全量备份目录 %s 的子目录数量被限制为 %s 个" % (self.ip, self.fullBackupDir, self.totalFullBakDirNum))
204 
205         elif flag == 1:
206             cmd2 = """find  %s  -mindepth 1 -maxdepth 1 -type d -printf "%%P\n" | sort -nr | head -%s""" % (
207                 self.incrementalBackupDir, self.totalIncBakDirNum)
208             res2 = self.getSsh().runCommandToStringList(cmd2)  # 需要保留的目录
209             allDirPath2 = self.getSsh().runCommandToStringList("ls %s" % self.incrementalBackupDir)
210             dirDelete2 = [path for path in allDirPath2 if path not in res2]  # 需要被删除的目录
211             dirRootDelete2 = [self.incrementalBackupDir + path + "/" for path in dirDelete2]
212             [self.getSsh().runCommandToString("rm -r %s" % path) for path in dirRootDelete2]
213             print("%s 上的增量备份目录 %s 的子目录数量被限制为 %s 个" % (self.ip, self.incrementalBackupDir, self.totalIncBakDirNum))
214 
215         elif flag == 2:
216             cmd3 = """find  %s  -mindepth 1 -maxdepth 1 -type d -printf "%%P\n" | sort -nr | head -%s""" % (
217                 self.totalBackupDir, self.totalBakDirNum)
218             res3 = self.getSsh().runCommandToStringList(cmd3)  # 需要保留的目录
219             allDirPath3 = self.getSsh().runCommandToStringList("ls %s" % self.totalBackupDir)
220             dirDelete3 = [path for path in allDirPath3 if path not in res3]  # 需要被删除的目录
221             dirRootDelete3 = [self.totalBackupDir + path + "/" for path in dirDelete3]
222             [self.getSsh().runCommandToString("rm -r %s" % path) for path in dirRootDelete3]
223             print("%s 上的总的全量备份目录 %s 的子目录数量被限制为 %s 个" % (self.ip, self.totalBackupDir, self.totalBakDirNum))
224 
225         elif flag == 3:
226             cmd4 = """find  %s  -mindepth 1 -maxdepth 1 -type d -printf "%%P\n" | sort -nr | head -%s""" % (
227                 self.mysqlDataDirBak, self.mysqlDataDirBakNum)
228             res4 = self.getSsh().runCommandToStringList(cmd4)  # 需要保留的目录
229             allDirPath4 = self.getSsh().runCommandToStringList("ls %s" % self.mysqlDataDirBak)
230             dirDelete4 = [path for path in allDirPath4 if path not in res4]  # 需要被删除的目录
231             dirRootDelete4 = [self.mysqlDataDirBak + path + "/" for path in dirDelete4]
232             [self.getSsh().runCommandToString("rm -r %s" % path) for path in dirRootDelete4]
233             print("%s 上的数据库原有数据目录的备份目录 %s 的子目录数量被限制为 %s 个" % (self.ip, self.mysqlDataDirBak, self.mysqlDataDirBakNum))
234 
235     def singleIncrementalBak(self, baseBakDir):
236         '''
237         单次自动增量备份,得到一个增量备份目录
238         :param baseBakDir: 备份基础目录
239         :return:
240         '''
241         res = self.getSsh().runCommandToString(
242                 " innobackupex --defaults-file=%s  --user=%s  --password=%s  --host=%s  --port=%s  --socket=%s  --incremental %s  --incremental-basedir=%s  --parallel=2" % (
243                     self.mysqlCnfFile, self.mysqlUsername, self.mysqlPasswd, self.ip, self.mysqlPort,
244                     self.mysqlSocketFile,
245                     self.incrementalBackupDir, baseBakDir))
246         res = str(res, encoding="utf-8")
247         if "completed OK" in res:  # 增量备份成功
248             newestIncBakDir = self.getSsh().runCommandToString(
249                     "find  %s   -mindepth 1 -maxdepth 1 -type d -printf '%%P\n' | sort -nr | head -1" % self.incrementalBackupDir)  # 最新备份目录
250             newestIncBakDir = str(newestIncBakDir, encoding="utf-8").strip("\n")
251             newestBakRootDir = self.incrementalBackupDir + newestIncBakDir  # 增量备份目录全路径
252             print("%s 剩余备份次数:%s,本次增量备份成功,目录为:%s" % (self.ip, self.bakTotalTimes - 1, newestBakRootDir))
253             self.bakTotalTimes -= 1
254         return newestBakRootDir
255 
256     def autoFullBak(self):
257         '''
258         自动全量备份
259         :return:
260         '''
261         # 先检测数据库是否运行
262         isRun = self.isMysqlRun()
263         if isRun:
264             res = self.getSsh().runCommandToString("mkdir -p  %s" % self.fullBackupDir)
265             print("%s 全量备份目录 %s 创建成功" % (self.ip, self.fullBackupDir))
266 
267             res = self.getSsh().runCommandToString(
268                     "innobackupex   --defaults-file=%s   --user=%s  --password=%s   --host=%s  --port=%s    --socket=%s   %s" % (
269                         self.mysqlCnfFile, self.mysqlUsername, self.mysqlPasswd, self.ip, self.mysqlPort,
270                         self.mysqlSocketFile, self.fullBackupDir))
271             res = str(res, encoding="utf-8")
272             if "completed OK" in res:  # 备份成功
273                 newestFullBakDir = self.getSsh().runCommandToString(
274                         "find  %s   -mindepth 1 -maxdepth 1 -type d -printf '%%P\n' | sort -nr | head -1" % self.fullBackupDir)  # 最新备份目录
275                 newestFullBakDir = str(newestFullBakDir, encoding="utf-8").strip("\n")
276                 newestFullBakRootDir = self.fullBackupDir + newestFullBakDir  # 最新全量备份目录的全路径
277                 # print("%s 全量备份成功,最新备份目录为:%s" % (self.ip, newestFullBakRootDir))
278                 return newestFullBakRootDir
279             else:
280                 print("%s 全量备份失败" % (self.ip))
281                 return None
282         else:
283             print("%s 数据库未运行" % self.ip)
284             return None
285 
286     def autoIncrementalBak(self):
287         '''
288         总的自动增量备份,得到单个全量备份目录和多个增量备份目录
289         :return:
290         '''
291         bakDirList = list()  # 存放一次循环中的全量备份目录和所有增量备份目录
292         res = self.getSsh().runCommandToString("mkdir -p  %s" % self.incrementalBackupDir)
293         print("%s 增量备份目录 %s 创建成功" % (self.ip, self.incrementalBackupDir))
294 
295         while self.bakTotalTimes:
296             newestFullBakDir = self.autoFullBak()  # 进行首次增量备份之前都先要来一次全量备份,因首次增量备份的基础是全量备份
297             if newestFullBakDir is not None:  # 全量备份成功时
298                 print("%s 剩余备份次数:%s,本次全量备份成功,目录为:%s" % (self.ip, self.bakTotalTimes - 1, newestFullBakDir))
299                 bakDirList.append(newestFullBakDir)
300                 self.bakTotalTimes -= 1
301 
302                 self.countDown()
303                 # 第1次增量备份
304                 newestBakRootDir1 = self.singleIncrementalBak(newestFullBakDir)
305                 bakDirList.append(newestBakRootDir1)
306 
307                 self.countDown()
308                 # 第2次增量备份
309                 newestBakRootDir2 = self.singleIncrementalBak(newestBakRootDir1)
310                 bakDirList.append(newestBakRootDir2)
311 
312                 self.countDown()
313                 # 第3次增量备份
314                 newestBakRootDir3 = self.singleIncrementalBak(newestBakRootDir2)
315                 bakDirList.append(newestBakRootDir3)
316 
317                 self.countDown()
318                 # 第4次增量备份
319                 newestBakRootDir4 = self.singleIncrementalBak(newestBakRootDir3)
320                 bakDirList.append(newestBakRootDir4)
321 
322                 self.countDown()
323                 # 第5次增量备份
324                 newestBakRootDir5 = self.singleIncrementalBak(newestBakRootDir4)
325                 bakDirList.append(newestBakRootDir5)
326 
327                 return bakDirList
328             else:
329                 print("%s 全量备份失败,所以无法进行增量备份" % self.ip)
330                 return None
331 
332     def getLastFullBakDir(self):
333         '''
334         得到最新的总的全量备份目录
335         :return:
336         '''
337         res = self.getSsh().runCommandToString("mkdir -p  %s" % self.totalBackupDir)
338         print("%s 存放总的全量备份目录的目录 %s 创建成功" % (self.ip, self.totalBackupDir))
339         bakDirList = self.autoIncrementalBak()
340         if bakDirList is not None:
341             fullBak = bakDirList[0]  # 全量备份目录
342             incBakList = bakDirList[1:-1]  # 增量备份目录列表,按照时间先后升序排列,不包含最后一个增量备份目录
343             incBakLast = bakDirList[-1]  # 最后一个增量备份目录,需要去掉--redo-only参数
344 
345             res2 = self.getSsh().runCommandToString(" innobackupex --apply-log --redo-only  %s" % fullBak)
346             res2 = str(res2, encoding="utf-8")
347             if "completed OK" in res2:
348                 print("%s 恢复全量备份目录 %s 成功" % (self.ip, fullBak))
349             else:
350                 print("%s 恢复全量备份目录 %s 失败" % (self.ip, fullBak))
351 
352             for path in incBakList:
353                 result = self.getSsh().runCommandToString(
354                         " innobackupex --apply-log --redo-only  %s  --incremental-dir=%s" % (fullBak, path))
355                 result = str(result, encoding="utf-8")
356                 if "completed OK" in result:
357                     print("%s 将增量备份目录 %s 应用到全量备份目录 %s 成功" % (self.ip, path, fullBak))
358                 else:
359                     print("%s 将增量备份目录 %s 应用到全量备份目录 %s 失败" % (self.ip, path, fullBak))
360 
361             res3 = self.getSsh().runCommandToString(
362                     " innobackupex --apply-log  %s  --incremental-dir=%s" % (fullBak, path))
363             res3 = str(res3, encoding="utf-8")
364             if "completed OK" in res3:
365                 print("%s 将最后一个增量备份目录 %s 应用到全量备份目录 %s 成功" % (self.ip, incBakLast, fullBak))
366             else:
367                 print("%s 将最后一个增量备份目录 %s 应用到全量备份目录 %s 失败" % (self.ip, incBakLast, fullBak))
368 
369             res4 = self.getSsh().runCommandToString(" innobackupex --apply-log   %s" % fullBak)
370             res4 = str(res4, encoding="utf-8")
371             if "completed OK" in res4:
372                 print("%s 将总的全量备份目录 %s 再进行一次apply操作,用以回滚未提交的数据 成功" % (self.ip, fullBak))
373             else:
374                 print("%s 将总的全量备份目录 %s 再进行一次apply操作,用以回滚未提交的数据 失败" % (self.ip, fullBak))
375 
376             self.getSsh().runCommandToString(
377                     "cp -r  %s   %s" % (fullBak, self.totalBackupDir))  # 得到总的全量备份目录之后,将该目录复制到指定目录备用
378 
379             newestLastFullBakDir = self.getSsh().runCommandToString(
380                     "find  %s   -mindepth 1 -maxdepth 1 -type d -printf '%%P\n' | sort -nr | head -1" % self.totalBackupDir)  # 最新的总的全量备份目录
381             newestLastFullBakDir = str(newestLastFullBakDir, encoding="utf-8").strip("\n")
382             newestLastFullBakDir = self.totalBackupDir + newestLastFullBakDir  # 最新的总的全量备份目录全路径
383             return newestLastFullBakDir
384 
385         else:
386             print("%s 无法得到最新的总的全量备份目录" % self.ip)
387 
388     def autoRecMysql(self):
389         '''
390         自动恢复数据库数据
391         :return:
392         '''
393         newestLastFullBakDir = self.getLastFullBakDir()
394         dirSize = self.getSsh().runCommandToString("du  -sh  %s  | awk '{print $1}' " % newestLastFullBakDir)
395         dirSize = str(dirSize, encoding="utf-8")
396         print("%s 上最新的总的全量备份目录为 %s 目录大小:%s" % (self.ip, newestLastFullBakDir, dirSize))
397         # choice = input("%s 是否恢复数据库数据?(y/n)" % self.ip)
398         choice = self.timeoutDefault(timeout=1, default="y")
399         if str(choice).lower() == "y":
400             self.getSsh().runCommandToString("service mysql stop")
401             isMysqlRun = self.isMysqlRun()
402             if not isMysqlRun:  # 数据库停止服务,恢复数据之前必须关闭数据库
403                 print("%s 关闭 mysql 成功,开始恢复数据" % self.ip)
404 
405                 # now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))  # 当前时间,格式2020-06-02-10-44-43
406                 # self.getSsh().runCommandToString(" mv  %s  %s-%s" % (self.mysqlDataDir, self.mysqlDataDir, now))#保留数据库数据
407                 self.bakMysqlOriginalData()
408 
409                 self.getSsh().runCommandToString(" mkdir %s" % self.mysqlDataDir)
410                 self.getSsh().runCommandToString(" innobackupex --defaults-file=%s  --copy-back --rsync  %s" % (
411                     self.mysqlCnfFile, newestLastFullBakDir))  # 恢复数据
412                 self.getSsh().runCommandToString("chown -R mysql.mysql  %s" % self.mysqlDataDir)  # 文件和目录的所属组改为mysql
413                 self.getSsh().runCommandToString("service mysql start")
414                 isMysqlRun2 = self.isMysqlRun()
415                 if isMysqlRun2:
416                     print("%s 数据库恢复成功" % self.ip)
417                 else:
418                     print("%s 数据库恢复失败" % self.ip)
419             else:
420                 print("关闭 %s mysql 失败,无法恢复数据" % self.ip)
421         elif str(choice).lower() == "n":
422             print("%s 已放弃恢复数据库数据" % self.ip)
423 
424     def Main(self):
425         '''
426         数据库数据备份和恢复主方法
427         :return:
428         '''
429         while True:
430             # try:
431             if self.judgeMysqlVersion():
432                 self.installXtrabackup()
433                 self.autoRecMysql()
434 
435                 # 此时总备份次数为0,为了循环执行,此处要手动赋值
436                 self.bakTotalTimes += 6
437 
438                 # 限制备份相关目录的子目录数量
439                 self.limitDirNums(flag=0)
440                 self.limitDirNums(flag=1)
441                 self.limitDirNums(flag=2)
442                 self.limitDirNums(flag=3)
443 
444                 # 休眠指定时间间隔后开始下一次数据备份和恢复操作
445                 self.countDown()
446 
447                 # except Exception as e:
448                 #     print(e)
449 
450 
451 if __name__ == '__main__':
452     autoBakAndRecMysql = AutoBakAndRecMysql(ip="192.168.1.190", sshUsername="root", sshPasswd="**", sshPort=22,
453                                             mysqlUsername="root", mysqlPasswd="**", mysqlPort="3306", )
454     # autoBakAndRecMysql.judgeMysqlVersion()
455     # autoBakAndRecMysql.isMysqlRun()
456     # autoBakAndRecMysql.installXtrabackup()
457     # autoBakAndRecMysql.autoFullBak()
458     # autoBakAndRecMysql.limitDirNums(flag=1)
459     # autoBakAndRecMysql.autoIncrementalBak()
460     # autoBakAndRecMysql.countDown()
461     # autoBakAndRecMysql.getLastFullBakDir()
462     # res = autoBakAndRecMysql.timeoutDefault(timeout=1)
463     # print(res)
464     autoBakAndRecMysql.Main()

上面代码中用到的工具类如下:

import os
import sys

import time


class Ssh():
    '''
    ssh远程连接工具
    '''

    def __init__(self, ip, username, password, sshPort):
        '''
        ssh远程连接信息,即目标服务器的ssh登录信息
        :param ip:
        :param username:
        :param password:
        :param sshPort:
        :return:
        '''
        self.ip = ip
        self.username = username
        self.passwprd = password
        self.sshPort = sshPort

    def runCommandToString(self, cmd):
        '''
        远程执行单条命令,输入参数为需要执行的单条命令,输出为结果的字符串形式
        '''
        # 检测虚拟机上是否安装了模块paramiko
        try:
            import paramiko
        except ImportError:
            try:
                command1 = "apt-get update && apt install python-pip && pip install --upgrade pip"
                command2 = "pip install paramiko "
                os.system(command1)
                time.sleep(5)
                os.system(command2)
                print("paramiko install Fail")
            except:
                print("Can not install paramiko, Aborted!")
                sys.exit(1)
            finally:
                import paramiko
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostname=self.ip, username=self.username, password=self.passwprd, port=self.sshPort, timeout=10,
                        allow_agent=False, look_for_keys=False)
            std_in, std_out, std_err = ssh.exec_command(cmd)
            res, err = std_out.read(), std_err.read()  # 获取命令结果,字符串形式
            if res:
                # if isinstance(res,bytes):
                #     res=str(res,encoding="utf-8").strip()
                #     result = res
                result=res
            elif err:
                result = err
            else:  # 如果执行的命令无返回值,则手动将返回结果置为空字符串
                result = " "
                # print("%s执行命令成功,但返回值为空:%s \n" %(self.ip ,str(cmd)) ,end="")
            ssh.close()

            # print("%s执行命令成功:%s \n" %(self.ip ,str(cmd)) ,end="")
            return result  # 字符串形式

        except Exception as e:
            print(str(e))
            print("%s执行命令异常:%s \n" % (self.ip, str(cmd)), end="")

    def runCommandToStringList(self, cmd):
        '''
        远程执行单条命令,输入参数为需要执行的单条命令,输出为结果的字符串列表形式
        '''
        # 检测虚拟机上是否安装了模块paramiko
        try:
            import paramiko
        except ImportError:
            try:
                command1 = "apt-get update && apt install python-pip && pip install --upgrade pip"
                command2 = "pip install paramiko "
                os.system(command1)
                time.sleep(5)
                os.system(command2)
                print("paramiko install Fail")
            except:
                print("Can not install paramiko, Aborted!")
                sys.exit(1)
            finally:
                import paramiko
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostname=self.ip, username=self.username, password=self.passwprd, port=self.sshPort, timeout=10,
                        allow_agent=False, look_for_keys=False)
            std_in, std_out, std_err = ssh.exec_command(cmd)
            res, err = std_out.readlines(), std_err.readlines()  # 获取命令结果,字符串形式
            if res:
                result = res
                result = [r.strip() for r in result]
            elif err:
                result = err
                result = [r.strip() for r in result]
            else:  # 如果执行的命令无返回值,则手动将返回结果置为空列表
                result = []
                # print("%s执行命令成功,但返回值为空列表:%s \n" %(self.ip ,str(cmd)) ,end="")
            ssh.close()
            # print("%s执行命令成功:%s \n" %(self.ip ,str(cmd)) ,end="")
            return result  # 字符串形式
        except Exception as e:
            print(str(e))
            print("%s执行命令异常:%s \n" % (self.ip, str(cmd)), end="")

if __name__ == '__main__':
    ssh=Ssh(ip='192.168.1.160',username='root',sshPort=22,password='**')
    res=ssh.runCommandToStringList('date')
    print(res)

文件“percona-release.list”的内容如下:

#
# Percona releases, stable
#
deb http://repo.percona.com/apt xenial main
deb-src http://repo.percona.com/apt xenial main
#
# Testing & pre-release packages
#
#deb http://repo.percona.com/apt xenial testing
#deb-src http://repo.percona.com/apt xenial testing
#
# Experimental packages, use with caution!
#
#deb http://repo.percona.com/apt xenial experimental
#deb-src http://repo.percona.com/apt xenial experimental

 

3 运行效果

我这里只是做测试,所以备份时间间隔选得较短,生产环境改大即可。且密码都改成了“ ** ”,要记得改成自己的。

  1 ssh://root@192.168.1.190:22/usr/bin/python3.5 -u /opt/pycharm_to_ubuntu/automatic_backup_and_recovery_mysql/auto_backup_and_recovery.py
  2 /usr/local/lib/python3.5/dist-packages/paramiko/kex_ecdh_nist.py:39: CryptographyDeprecationWarning: encode_point has been deprecated on EllipticCurvePublicNumbers and will be removed in a future version. Please use EllipticCurvePublicKey.public_bytes to obtain both compressed and uncompressed point encoding.
  3   m.add_string(self.Q_C.public_numbers().encode_point())
  4 /usr/local/lib/python3.5/dist-packages/paramiko/kex_ecdh_nist.py:96: CryptographyDeprecationWarning: Support for unsafe construction of public numbers from encoded data will be removed in a future version. Please use EllipticCurvePublicKey.from_encoded_point
  5   self.curve, Q_S_bytes
  6 /usr/local/lib/python3.5/dist-packages/paramiko/kex_ecdh_nist.py:111: CryptographyDeprecationWarning: encode_point has been deprecated on EllipticCurvePublicNumbers and will be removed in a future version. Please use EllipticCurvePublicKey.public_bytes to obtain both compressed and uncompressed point encoding.
  7   hm.add_string(self.Q_C.public_numbers().encode_point())
  8 192.168.1.190 数据库版本>=5.7,满足要求,可以使用xtrabackup2.4
  9 192.168.1.190 xtrabackup已安装
 10 192.168.1.190 存放总的全量备份目录的目录 /data/backup/last/ 创建成功
 11 192.168.1.190 增量备份目录 /data/backup/incremental/ 创建成功
 12 192.168.1.190 全量备份目录 /data/backup/full/ 创建成功
 13 192.168.1.190 剩余备份次数:5,本次全量备份成功,目录为:/data/backup/full/2020-06-02_18-42-39
 14 开始备份                                                            
 15 192.168.1.190 剩余备份次数:4,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-43-43
 16 开始备份                                                            
 17 192.168.1.190 剩余备份次数:3,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-44-45
 18 开始备份                                                            
 19 192.168.1.190 剩余备份次数:2,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-45-48
 20 开始备份                                                            
 21 192.168.1.190 剩余备份次数:1,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-46-51
 22 开始备份                                                            
 23 192.168.1.190 剩余备份次数:0,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-47-53
 24 192.168.1.190 恢复全量备份目录 /data/backup/full/2020-06-02_18-42-39 成功
 25 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-43-43 应用到全量备份目录 /data/backup/full/2020-06-02_18-42-39 成功
 26 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-44-45 应用到全量备份目录 /data/backup/full/2020-06-02_18-42-39 成功
 27 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-45-48 应用到全量备份目录 /data/backup/full/2020-06-02_18-42-39 成功
 28 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-46-51 应用到全量备份目录 /data/backup/full/2020-06-02_18-42-39 成功
 29 192.168.1.190 将最后一个增量备份目录 /data/backup/incremental/2020-06-02_18-47-53 应用到全量备份目录 /data/backup/full/2020-06-02_18-42-39 成功
 30 192.168.1.190 将总的全量备份目录 /data/backup/full/2020-06-02_18-42-39 再进行一次apply操作,用以回滚未提交的数据 成功
 31 192.168.1.190 上最新的总的全量备份目录为 /data/backup/last/2020-06-02_18-42-39 目录大小:554M
 32 
 33 192.168.1.190 是否恢复数据库数据?(y/n)
 34 192.168.1.190 在 1 分钟内未输入内容,则默认输入'y'
 35 192.168.1.190 关闭 mysql 成功,开始恢复数据
 36 192.168.1.190 数据库原有数据的备份目录 /var/lib/mysql-bak 创建成功
 37 192.168.1.190 数据库原有数据目录 /var/lib/mysql 备份成功
 38 192.168.1.190 数据库恢复成功
 39 192.168.1.190 上的全量备份目录 /data/backup/full/ 的子目录数量被限制为 4 40 192.168.1.190 上的增量备份目录 /data/backup/incremental/ 的子目录数量被限制为 10 41 192.168.1.190 上的总的全量备份目录 /data/backup/last/ 的子目录数量被限制为 5 42 192.168.1.190 上的数据库原有数据目录的备份目录 /var/lib/mysql-bak 的子目录数量被限制为 5 43 开始备份                                                            
 44 192.168.1.190 数据库版本>=5.7,满足要求,可以使用xtrabackup2.4
 45 192.168.1.190 xtrabackup已安装
 46 192.168.1.190 存放总的全量备份目录的目录 /data/backup/last/ 创建成功
 47 192.168.1.190 增量备份目录 /data/backup/incremental/ 创建成功
 48 192.168.1.190 全量备份目录 /data/backup/full/ 创建成功
 49 192.168.1.190 剩余备份次数:5,本次全量备份成功,目录为:/data/backup/full/2020-06-02_18-50-29
 50 开始备份                                                            
 51 192.168.1.190 剩余备份次数:4,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-51-32
 52 开始备份                                                            
 53 192.168.1.190 剩余备份次数:3,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-52-35
 54 开始备份                                                            
 55 192.168.1.190 剩余备份次数:2,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-53-38
 56 开始备份                                                            
 57 192.168.1.190 剩余备份次数:1,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-54-40
 58 开始备份                                                            
 59 192.168.1.190 剩余备份次数:0,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-55-43
 60 192.168.1.190 恢复全量备份目录 /data/backup/full/2020-06-02_18-50-29 成功
 61 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-51-32 应用到全量备份目录 /data/backup/full/2020-06-02_18-50-29 成功
 62 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-52-35 应用到全量备份目录 /data/backup/full/2020-06-02_18-50-29 成功
 63 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-53-38 应用到全量备份目录 /data/backup/full/2020-06-02_18-50-29 成功
 64 192.168.1.190 将增量备份目录 /data/backup/incremental/2020-06-02_18-54-40 应用到全量备份目录 /data/backup/full/2020-06-02_18-50-29 成功
 65 192.168.1.190 将最后一个增量备份目录 /data/backup/incremental/2020-06-02_18-55-43 应用到全量备份目录 /data/backup/full/2020-06-02_18-50-29 成功
 66 192.168.1.190 将总的全量备份目录 /data/backup/full/2020-06-02_18-50-29 再进行一次apply操作,用以回滚未提交的数据 成功
 67 192.168.1.190 上最新的总的全量备份目录为 /data/backup/last/2020-06-02_18-50-29 目录大小:554M
 68 
 69 192.168.1.190 是否恢复数据库数据?(y/n)
 70 192.168.1.190 在 1 分钟内未输入内容,则默认输入'y'
 71 192.168.1.190 关闭 mysql 成功,开始恢复数据
 72 192.168.1.190 数据库原有数据的备份目录 /var/lib/mysql-bak 创建成功
 73 192.168.1.190 数据库原有数据目录 /var/lib/mysql 备份成功
 74 192.168.1.190 数据库恢复成功
 75 192.168.1.190 上的全量备份目录 /data/backup/full/ 的子目录数量被限制为 4 76 192.168.1.190 上的增量备份目录 /data/backup/incremental/ 的子目录数量被限制为 10 77 192.168.1.190 上的总的全量备份目录 /data/backup/last/ 的子目录数量被限制为 5 78 192.168.1.190 上的数据库原有数据目录的备份目录 /var/lib/mysql-bak 的子目录数量被限制为 5 79 开始备份                                                            
 80 192.168.1.190 数据库版本>=5.7,满足要求,可以使用xtrabackup2.4
 81 192.168.1.190 xtrabackup已安装
 82 192.168.1.190 存放总的全量备份目录的目录 /data/backup/last/ 创建成功
 83 192.168.1.190 增量备份目录 /data/backup/incremental/ 创建成功
 84 192.168.1.190 全量备份目录 /data/backup/full/ 创建成功
 85 192.168.1.190 剩余备份次数:5,本次全量备份成功,目录为:/data/backup/full/2020-06-02_18-58-18
 86 开始备份                                                            
 87 192.168.1.190 剩余备份次数:4,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_18-59-21
 88 开始备份                                                            
 89 192.168.1.190 剩余备份次数:3,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_19-00-24
 90 开始备份                                                            
 91 192.168.1.190 剩余备份次数:2,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_19-01-27
 92 开始备份                                                            
 93 192.168.1.190 剩余备份次数:1,本次增量备份成功,目录为:/data/backup/incremental/2020-06-02_19-02-29
。。。。。。

 在虚拟机上也会生成相应的目录:

发表评论

0/200
228 点赞
0 评论
收藏