mysql backup restore_mysql-backup-restore

MySQL Backup Restore

MySQL数据库的备份与恢复, 项目中需要对系统数据进行备份和恢复, 要求能在界面上点击进行自主备份和恢复, 所以有了这篇文章, 我把其中不涉及业务的部分摘了出来, 做了一下整理, 全部源代码在https://gitee.com/nichenxyx/mysql-backup-restore

先介绍一下整体思路

在使用JDBC去做系统数据的整体备份和删除是不太好做的, 更何况封装后的JPA呢, 所以使用原生的方式反而更方便

我在MySQL-5.7.22版本下将mysql命令程序和mysqldump命令程序直接拷贝了出来, 作为程序备份和恢复的基础工具并写了两个脚本执行相关业务逻辑的东西

下面说一下两个脚本,最后再说一下Java的逻辑

mysql-backup.sh -- 备份脚本

用法

-h host MySQL所在主机

-p port (可选参数)MySQL的端口, 默认3306

-u user 连接MySQL的用户

-P password 连接MySQL的密码

-d database 需要备份的数据库

-n ignore tables (可选参数)需要忽略的表, 如果有多个, 使用英文逗号分隔

示例

./mysql-bakup.sh -h 192.168.1.2 -p 3306 -u root -P password -d aa -n menu,role

结果

会以当前日期生成一个-finished.tar.gz结尾的文件

全部源代码

#!/bin/bash

# desc: 备份数据库

# author: zhuliang

whilegetopts 'h:p:u:P:d:n' OPT; do

case $OPT in

h)

host="$OPTARG";;

p)

port="$OPTARG";;

u)

user="$OPTARG";;

P)

password="$OPTARG";;

d)

database="$OPTARG";;

n)

ignoreTables="$OPTARG";;

?)

echo "$OPTARG"

echo -e "Usage:`basename $0`[options] optionsValue\n-h host\n-p port\n-u user\n-P password\n-d database\n-n ignore tables, optional, if has multiple value, split with\",\""

exit0

esac

done

if [[ -z $host ]]; then

echo "-h parameter missed, for help add -h parameter"

exit1

fi

if [[ -z $user ]]; then

echo "-u parameter missed, for help add -h parameter"

exit3

fi

if [[ -z $password ]]; then

echo "-P parameter missed, for help add -h parameter"

exit4

fi

if [[ -z $database ]]; then

echo "-d parameter missed, for help add -h parameter"

exit5

fi

if [[ -z $port ]]; then

port=3306

fi

# 进入命令所在目录

cd `dirname $0`

chmod +x ./mysqldump

# 拼接需要执行的命令

cmd="./mysqldump --host=$host--port=$port--user=$user--password=$password--databases$database--allow-keywords --compress --force --hex-blob --quick"

ignoreArray=(${ignoreTables//,/})

fortable in ${ignoreArray[@]}

do

cmd="$cmd--ignore-table=$database.$table"

done

echo $cmd

date=`date +"%Y%m%d%H%M%S"`

logfile=backup.log

date +"%Y-%m-%d %H:%M:%S Start backup database..." >> $logfile

sqlFile=$date.sql

tarFile=$date.tar.gz

# 执行命令并将结果保存

$cmd > $sqlFile

tarzcvf $tarFile $sqlFile

rm -rf $sqlFile

# 最终以finished.tar.gz结尾才算是最终备份成功

mv $tarFile $date-finished.tar.gz

date +"%Y-%m-%d %H:%M:%S Finished backup database" >> $logfile

mysql-restore.sh -- 恢复脚本

-h host MySQL所在主机

-p port (可选参数)MySQL的端口, 默认3306

-u user 连接MySQL的用户

-P password 连接MySQL的密码

-d database 需要恢复的数据库

-f sql tar file 需要用于恢复的压缩文件

示例

./mysql-restore.sh -h 192.168.1.2 -p 3306 -u root -P password -d aa -f20191111062402-finished.tar.gz

源代码

#!/bin/bash

# desc: 恢复数据

# author: zhuliang

whilegetopts 'h:p:u:P:d:f:' OPT; do

case $OPT in

h)

host="$OPTARG";;

p)

port="$OPTARG";;

u)

user="$OPTARG";;

P)

password="$OPTARG";;

d)

database="$OPTARG";;

f)

sqlTarFile="$OPTARG";;

?)

echo -e "Usage:`basename $0`[options] optionsValue\n-h host\n-p port\n-u user\n-P password\n-d database\n-f sql tar file"

exit0

esac

done

if [[ -z $host ]]; then

echo "-h parameter missed, for help add -h parameter"

exit1

fi

if [[ -z $user ]]; then

echo "-u parameter missed, for help add -h parameter"

exit3

fi

if [[ -z $password ]]; then

echo "-P parameter missed, for help add -h parameter"

exit4

fi

if [[ -z $database ]]; then

echo "-d parameter missed, for help add -h parameter"

exit5

fi

if [[ -z $sqlTarFile ]]; then

echo "-f parameter missed, for help add -h parameter"

exit6

fi

if [[ -z $port ]]; then

port=3306

fi

cd `dirname $0`

date=`date +"%Y%m%d-%H%M%S"`

logfile=backup.log

date +"%Y-%m-%d %H:%M:%S Start restore data..." >> $logfile

sqlFile=`uuidgen`.sql

tarzxvf $sqlTarFile -O > $sqlFile

chmod +x ./mysql

./mysql --host=$host --port=$port --user=$user --password=$password $database < $sqlFile

rm -rf $sqlFile

date +"%Y-%m-%d %H:%M:%S Finished restore data" >> $logfile

BackupRestoreService --Java调用

先看一下代码

package com.vhengdata.service;

import cn.hutool.core.collection.CollUtil;

import cn.hutool.core.io.FileUtil;

import cn.hutool.core.io.IoUtil;

import cn.hutool.core.util.StrUtil;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.core.io.ClassPathResource;

import org.springframework.stereotype.Service;

import java.io.*;

import java.sql.*;

import java.util.*;

import java.util.regex.Pattern;

import java.util.stream.Collectors;

@Service

@Slf4j

public class BackupRestoreService {

private final String BACKUP_POSTFIX = "-finished.tar.gz";

private volatile boolean backuping = false;

private volatile boolean restoring = false;

@Value("${spring.datasource.url:}")

private String jdbcUrl;

@Value("${spring.datasource.username:}")

private String username;

@Value("${spring.datasource.password:}")

private String password;

public List getBackups() {

String backupPath = "backup";

File backupFolder = new File(backupPath);

String[] backups = backupFolder.list(new FilenameFilter() {

@Override

public boolean accept(File dir, String name) {

return name.endsWith(BACKUP_POSTFIX);

}

});

List backList = CollUtil.toList(backups).stream()

.map(filename -> filename.replace(BACKUP_POSTFIX, ""))

.sorted((b1, b2) -> b2.compareToIgnoreCase(b1))

.collect(Collectors.toList());

return backList;

}

public void backupData() {

if (backuping) {

throw new ServiceException(ResultEnum.TASK_BASY.getCode(), "当前正在进行备份操作, 请稍后再试");

}

if (restoring) {

throw new ServiceException(ResultEnum.TASK_BASY.getCode(), "当前正在进行数据恢复操作, 请稍后再试");

}

try {

backuping = true;

String configBackupFolder = fileConfig.getBackup();

File configBackupFolderFile = new File(configBackupFolder);

if (!configBackupFolderFile.exists()) {

configBackupFolderFile.mkdirs();

}

// 获取参数

if (StrUtil.isEmpty(jdbcUrl)) {

throw new ServiceException(ResultEnum.PARAM_DATA_ERROR.getCode(), "数据库配置不正确");

}

String host, port, db, user, pwd;

Map dbConfigMap = readConfigFromJdbcUrl(jdbcUrl);

host = dbConfigMap.get("host");

port = dbConfigMap.get("port");

db = dbConfigMap.get("db");

if (dbConfigMap.containsKey("user")) {

user = dbConfigMap.get("user");

} else {

user = username;

}

if (dbConfigMap.containsKey("password")) {

pwd = dbConfigMap.get("password");

} else {

pwd = password;

}

String shellpath = configBackupFolder + "/mysql-bak.sh";

File shellFile = new File(shellpath);

if (!FileUtil.exist(shellFile)) {

InputStream inputStream = new ClassPathResource("mysql/mysql-bak.sh").getInputStream();

FileOutputStream outputStream = new FileOutputStream(shellFile);

IoUtil.copy(inputStream, outputStream);

inputStream.close();

outputStream.close();

}

String mysqlpath = configBackupFolder + "/mysqldump";

if (!FileUtil.exist(mysqlpath)) {

InputStream inputStream = new ClassPathResource("mysql/mysqldump").getInputStream();

FileOutputStream outputStream = new FileOutputStream(new File(mysqlpath));

IoUtil.copy(inputStream, outputStream);

inputStream.close();

outputStream.close();

}

if (!shellFile.canExecute()) {

shellFile.setExecutable(true);

}

String shellCmd = String.format("%s -h %s -p %s -u %s -P %s -d %s", shellFile.getAbsolutePath(), host, port, user, pwd, db);

log.info("Backup: {}", shellCmd);

Runtime.getRuntime().exec(shellCmd).waitFor();

} catch (InterruptedException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally {

backuping = false;

}

}

public void restoreData(String filename) {

if (backuping) {

throw new ServiceException(ResultEnum.TASK_BASY.getCode(), "当前正在进行备份操作, 请稍后再试");

}

if (restoring) {

throw new ServiceException(ResultEnum.TASK_BASY.getCode(), "当前正在进行数据恢复操作, 请稍后再试");

}

try {

restoring = true;

if (!filename.endsWith(BACKUP_POSTFIX)) {

filename += BACKUP_POSTFIX;

}

String configBackupFolder = fileConfig.getBackup();

File configBackupFolderFile = new File(configBackupFolder);

if (!configBackupFolderFile.exists()) {

configBackupFolderFile.mkdirs();

}

String backupFilePath = configBackupFolder + "/" + filename;

File backupFile = new File(backupFilePath);

if (!backupFile.exists()) {

throw new TourException(ResultEnum.PARAM_ERROR.getCode(), "备份文件不存在");

}

String backupFileAbPath = backupFile.getAbsolutePath();

// 获取参数

if (StrUtil.isEmpty(jdbcUrl)) {

throw new TourException(ResultEnum.PARAM_DATA_ERROR.getCode(), "数据库配置不正确");

}

String host, port, db, user, pwd;

Map dbConfigMap = readConfigFromJdbcUrl(jdbcUrl);

host = dbConfigMap.get("host");

port = dbConfigMap.get("port");

db = dbConfigMap.get("db");

if (dbConfigMap.containsKey("user")) {

user = dbConfigMap.get("user");

} else {

user = username;

}

if (dbConfigMap.containsKey("password")) {

pwd = dbConfigMap.get("password");

} else {

pwd = password;

}

String shellpath = configBackupFolder + "/mysql-restore.sh";

File shellFile = new File(shellpath);

if (!FileUtil.exist(shellFile)) {

InputStream inputStream = new ClassPathResource("mysql/mysql-restore.sh").getInputStream();

FileOutputStream outputStream = new FileOutputStream(shellFile);

IoUtil.copy(inputStream, outputStream);

inputStream.close();

outputStream.close();

}

String mysqlpath = configBackupFolder + "/mysql";

if (!FileUtil.exist(mysqlpath)) {

InputStream inputStream = new ClassPathResource("mysql/mysql").getInputStream();

FileOutputStream outputStream = new FileOutputStream(new File(mysqlpath));

IoUtil.copy(inputStream, outputStream);

inputStream.close();

outputStream.close();

}

if (!shellFile.canExecute()) {

shellFile.setExecutable(true);

}

String shellCmd = String.format("%s -h %s -p %s -u %s -P %s -d %s -f %s", shellFile.getAbsolutePath(), host, port, user, pwd, db, backupFileAbPath);

log.info("Restore: {}", shellCmd);

Runtime.getRuntime().exec(shellCmd).waitFor();

} catch (InterruptedException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally {

restoring = false;

}

}

// jdbc:mysql://localhost:3306/worksql?useUnicode=true&characterEncoding=UTF-8

private Map readConfigFromJdbcUrl(String jdbcUrl) {

Map configMap = new HashMap<>(8);

if (StrUtil.isEmpty(jdbcUrl)) {

return configMap;

}

Pattern pattern = Pattern.compile("jdbc:mysql:\\/\\/(.*)\\/(.*)", Pattern.CASE_INSENSITIVE);

Matcher matcher = pattern.matcher(jdbcUrl);

if (matcher.find()) {

// 主机地址和端口

String hostPort = matcher.group(1);

if (hostPort.contains(":")) {

String[] hostPorts = hostPort.split(":");

configMap.put("host", hostPorts[0]);

configMap.put("port", hostPorts[1]);

} else {

configMap.put("host", hostPort);

configMap.put("port", "3306");

}

// 连接的数据库

String dbParams = matcher.group(2);

if (dbParams.contains("?")) {

configMap.put("db", dbParams.split("\\?")[0]);

} else {

configMap.put("db",dbParams);

return configMap;

}

// 用户名和密码

String params = dbParams.split("\\?")[1];

if (params.length() > 1) {

params = params.substring(1);

String[] queries = params.split("&");

for (String query : queries) {

String[] keyValue = query.split("=");

if (keyValue[0].equals("user")) {

configMap.put("user", keyValue[1]);

continue;

}

if (keyValue[0].equals("password")) {

configMap.put("password", keyValue[1]);

}

}

}

return configMap;

}

return configMap;

}

}

这个本身是运行爱Springboot框架下的, 额外引入的包是hutool库和lombok

程序为了减少配置, 从springboot的配置文件中获取系统数据库的配置, 并解析jdbc的url获取相关连接参数

两个执行脚本和两个基本命令是放在resource目录下的mysql目录下, 跟其他代码一起打包到jar文件中发布

在执行的时候会先将打包在jar里的文件进行释放, 然后再执行, 在单机的并发处理上使用volatile关键词修饰的两个boolean变量防止出现两个进程备份进程同时进行(分布式环境下的问题处理有分布式下的处理方式)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值