IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    MySQL备份方案(综述及脚本)

    Vlix_Liu发表于 2015-11-01 16:19:49
    love 0

     

     

    1.环境说明

     

        系统为centos 6.5

     

        需要安装mutt和msmtp并可以发送邮件

    需要安装python 2.6.6

    需要安装xtrabackup

     

    2.备份方案功能模块介绍

    MySql备份脚本

    备份:

    使用xtrabackup进行备份,每次备份会把备份文件放到一个当前日期和时间的文件夹内。所以创建备份夹new,把备份文件放到new中,并根据new中文件夹的个数判断是全备还是增备还是需要转移文件到last中。第一个文件是全备,每次增备是在前一天的基础上进行增备。备份脚本在把所有的文件从new移动到last的时候 会把所有文件文件打包。以下是mysql备份脚本:

    #!/usr/bin/env python
    #coding:utf-8
    #auther:Bran Guo
    #date:10/23/2015 
    #description:myql自动备份脚本,添加定时任务自动运行,不要修改mysqlbackup文件夹中的文件
    #version:V1.0
    import ConfigParser,os,datetime,logger
    #读取配置文件
    conf = ConfigParser.ConfigParser()
    conf.read("mysqlbak.conf")
    bakdir_new = conf.get("file_path","bakdir_new")
    bakdir_last = conf.get("file_path","bakdir_last")
    bak_cycle = conf.get('bak_cycle','bak_cycle')
    bak_output = conf.get('log_file','bak_output')
    bak_user = conf.get('mysql_set','bak_user')
    bak_passwd = conf.get('mysql_set','bak_passwd')
    os.environ['bakdir_new']=str(bakdir_new)
    os.environ['bak_output']=str(bak_output)
    os.environ['bak_user']=str(bak_user)
    os.environ['bak_passwd']=str(bak_passwd)
     
    #判断备份文件夹个数
    dirnew_count = int(os.popen('ls %s |wc -l' % bakdir_new).read())
    if dirnew_count == 0:
        os.system('echo %s full backup start ---------------------------------------------------- >> $bak_output ' % datetime.datetime.now())
        ret = os.system('innobackupex --user=$bak_user --password=$bak_passwd $bakdir_new &>> $bak_output') 
        logger.logger(ret)
    elif dirnew_count >= int(bak_cycle):
        os.system('rm -rf %s/*' % bakdir_last)
        os.system('mv %s/* %s' % (bakdir_new,bakdir_last))
        os.system('tar zcf %s/`date +%%m-%%d-%%Y`.tar.gz %s/*' %(bakdir_last,bakdir_last))
        os.system('echo %s full backup start ---------------------------------------------------- >> $bak_output ' % datetime.datetime.now())
        ret = os.system('innobackupex --user=$bak_user --password=$bak_passwd $bakdir_new &>> $bak_output') 
        logger.logger(ret)
    else:
            full_file = os.popen('ls %s' % bakdir_new).readlines()
            for file_name in full_file :
            file_list= []
            file_list.append(file_name)
            basedir = file_list[-1]
        os.system('echo %s incremental backup start ---------------------------------------------------- >> $bak_output ' % datetime.datetime.now())
        os.environ['basedir']=str(basedir)
        ret = os.system('innobackupex --user=$bak_user --password=$bak_passwd --incremental $bakdir_new --incremental-basedir=$bakdir_new/$basedir &>> $bak_output')
        logger.logger(ret)

    还原:

    使用xtrabackup还原需要先准备(perpare)。一般情况下,在备份完成后,数据尚且不能用于恢复操作,因为备份的数据中可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务。因此,此时数据文件仍处理不一致状态。“准备”的主要作用正是通过回滚未提交的事务及同步已经提交的事务至数据文件也使得数据文件处于一致性状态。准备的过程是以第一个完备为基础,提交第二个然后是第三个一直到最后一个。整个准备完成后使用第一个完备文件进行还原即可。还原脚本使用cli进行交互,用户可以选择还原到最近日期或指定日期。

    还原脚本运行界面

    image

    还原脚本代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    #!/usr/bin/python
    #coding:utf-8
    #auther:Bran Guo
    #date:10/26/2015
    #version:V1.0
    import os,ConfigParser
    #读取配置文件
    conf = ConfigParser.ConfigParser()
    conf.read("mysqlbak.conf")
    bakdir_new = conf.get('file_path','bakdir_new')
    bakdir_last = conf.get('file_path','bakdir_last')
    mysql_data_path = conf.get('mysql_set','mysql_data_path')
    dirlist_new = os.popen('ls %s' % bakdir_new).readlines()
    dirlist_last = os.popen('ls %s' % bakdir_last).readlines()
    #备份函数
    def restore(dir_count=len(dirlist_new),bakdir=bakdir_new,dirlist=dirlist_new):
        if dir_count == 1:
            print dir_count,bakdir,dirlist
            ret=os.system('innobackupex --apply-log --redo-only %s/%s' %(bakdir,dirlist[0]))       
            if ret != 0:
                print "prepare failed"
                exit()
        else:
            ret=os.system('innobackupex --apply-log --redo-only %s/%s' %(bakdir,dirlist[0]))       
            count = 1
            while (count < dir_count):
                incrdir = dirlist[count]
                basedir = dirlist[0]
                os.environ['incrdir'] = str(incrdir)
                os.environ['basedir'] = str(basedir)
                os.environ['bakdir'] = str(bakdir)
                ret=os.system('innobackupex --apply-log --redo-only $bakdir/$basedir  --incremental-dir=$bakdir/$incrdir')
                if ret != 0:
                    print "prepare failed"
                count +=1
        os.system('service mysqld stop')
        os.system('rm -rf %s' % mysql_data_path)
        os.system('innobackupex --copy-back %s/%s' %(bakdir,dirlist[0]))    
        os.system('chown -R mysql:mysql %s' % mysql_data_path)
        os.system('service mysqld start')
    #输入菜单
    while True:
        user_input = raw_input('Command (m for help):').strip()
        if user_input == 'm':
            print '''Warning: The following command will remove mysql datafile, should be used with caution.
        r   restore to recent backup
        s   show backup list
        n   choose backup restore from new
        l   choose backup restore from last
        q   quit
    ''',
        elif user_input == 'r':
            restore()
        elif user_input == 'q':
            exit()
        elif user_input == 's':
            print 'New:'
            os.system('ls %s' % bakdir_new)
            print 'Last'
            os.system('ls %s' % bakdir_new)
        elif user_input == 'n':
            os.system('ls -l %s' % bakdir_new)
            while True:
                user_input = raw_input('Please enter line number restore:').strip()
                if user_input == 'q':
                    exit()
                try:
                    line_number = int(user_input)
                    dir_count = len(dirlist_new)
                    if line_number <= dir_count:
                        restore(line_number)
                    else:
                        print '''Please enter a number less then line or "q".'''
                except ValueError:
                    print '''Please enter a number less then line or "q".'''
        elif user_input == 'l':
            os.system('ls -l %s' % bakdir_last)
            while True:
                user_input = raw_input('Please enter line number restore:').strip()
                if user_input == 'q':
                    exit()
                try:
                    line_number = int(user_input)
                    dir_count = len(dirlist_last)
                    if line_number <= dir_count:
                        restore(line_number,bakdir_last,dirlist_last)
                    else:
                        print '''Please enter a number less then line sum or "q".'''
                except ValueError:
                    print '''Please enter a number less then line sum "q".'''

    远程保存:

    使用rsync和scp同步文件(脚本在备份服务器)

    备份脚本rsync

    1
    2
    #!/bin/bash
    rsync -Paz -e 'ssh -p 22'  xxx@106.xxx.xxx.xxx:/home/dev/mysqlbackup/new ./ &> ./logs/rsyncbak.log

    备份脚本scp

    1
    2
    #!/bin/bash
    scp -P 22 xxx@106.xxx.xxx.xxx:/home/dev/mysqlbackup/last/*.tar.gz ./history &> ./logs/scpbak.log

    日志:

    所有xtrabackup的输出会保存到一个日志文件便于排查。通过命令执行后返回值判断成功或失败后的结果输出到一个日志文件中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/usr/bin/python
    import datetime,os,ConfigParser
    conf = ConfigParser.ConfigParser()
    conf.read("mysqlbak.conf")
    bak_log = conf.get("log_file","bak_log")
    mail_addr = conf.get('mail','mail_addr')
    logfile = 'backup.log'
    def logger(ret):
        if ret == 0:
            echo_line = "%s\tBackup mysql data file succes\n" % datetime.datetime.now()
        else:
            echo_line = "%s\tBackup mysql data file failed, plz check bakoutput.log\n" % datetime.datetime.now()
            os.system('echo "%s" | mutt -s "Backup mysql failed" %s' %(echo_line,mail_addr))
        f = file(bak_log,'a')
        f.write(echo_line)
        f.flush()
        f.close()

    邮件:

    每次备份失败后背自动发送邮件给指定邮件地址,便于运维及时发现问题并进行排查。发送邮件功能写到日志脚本中

    配置文件:

    除远程保存部分脚本(放在备份服务器)外,所有变量都被抽出来放到配置文件中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #备份文件路径
    [file_path]
    bakdir_new = /home/dev/mysqlbackup/new
    bakdir_last = /home/dev/mysqlbackup/last
    #备份周期
    [bak_cycle]
    bak_cycle = 7
    #日志文件位置
    [log_file]
    bak_output = bakoutput.log
    bak_log = backup.log
    #MySQL设置
    [mysql_set]
    mysql_data_path = /var/lib/mysql
    bak_user = user
    bak_passwd = password
    #备份失败发送邮件地址
    [mail]
    mail_addr = xxxxxx@xxx.com


沪ICP备19023445号-2号
友情链接