备份恢复方案

770 阅读4分钟

备份恢复方案有以下几个东西需要备份:主要是需要将数据备份下来

  1. 首先查找数据存放的地方
  • 存入consul的数据
  • 存入keycloak的资源及权限控制
  • 数据库表及数据
  • ...
  1. 怎么备份?:针对数据存放的位置不同,备份的方式也不同 数据备份,方式如下:
  • consul数据--> consul-kv.json
  • DB ---> ispeco.sql
  • keycloak resources ---> keycloak-resources.json

将上述文件压缩成压缩包:以时间(ispeco-backup-resources年-月-日-时-分-秒)命名;存放在用户设定的存放路径(若用户未指定,则存放在默认路径/etc/ispeco/backup/路径下);

  1. 恢复数据 (1) 查询默认路径下的备份文件,ui展示

(2) 根据用户选择的压缩文件进行恢复

  • keycloak
  • 恢复consul数据
  • 恢复keycloak数据

备份恢复流程

backup.jpg

备份consul数据

通过命令备份

备份数据

// 导出所有kv键值对,注意prefix是导出键值对的前缀,为空字符串或不填说明要导出所有
consul kv export prefix > path/filename;

consul kv export test > kv.json; //导出consul中以test为前缀的所有key到当前路径的kv.json文件中

备注: 导出的数据value是base64编码的字符串。 使用base64 -d命令编码就可以看到原始的value值,如:

$ echo 'eyJ2ZXJzaW9uX3RpbWVzdGFtcCI6IC0xfQ==' | base64 -d
{"version_timestamp": -1}

恢复数据

//(1)从文件导入
consul kv import @kv.json; 

//(2) 从stdin导入
cat values.json | consul kv import -

//(3) 直接传递JSON
consul kv import "$(cat values.json)"

 //导入
consul kv import @kv.json;

备注: 导入的value必须是base64编码过的值。

通过api备份/恢复数据

备份数据

/v1/txn PUT
[
  {
    "KV": {
      "Verb": "<verb>", get-tree
      "Key": "<key>", //可以设置成前缀
      "Value": "<Base64-encoded blob of data>",
      "Flags": <flags>,
      "Index": <index>,
      "Session": "<session id>"
    }
  }
]

恢复数据

/v1/txn PUT
[
  {
    "KV": {
      "Verb": "<verb>", set
      "Key": "<key>", //key名称1
      "Value": "<Base64-encoded blob of data>",
      "Flags": <flags>,
      "Index": <index>,
      "Session": "<session id>"
    }
  },
  .....
  {
    "KV": {
      "Verb": "<verb>", set
      "Key": "<key>", //key名称n
      "Value": "<Base64-encoded blob of data>",
      "Flags": <flags>,
      "Index": <index>,
      "Session": "<session id>"
    }
  }
]

注意事项

consul请求:/v1/txn 一次事务只允许操作64个key

备份DB数据

目前采用mysqldump命令进行备份数据

mysqldump

备份mysql表结构及数据

远程备份语法:
mysqldump -h 服务器 -u用户名 -p密码 数据库名 >备份文件.sql
本地备份语法:
mysqldump -u用户名 -p密码 数据库名 > 备份文件.sql

//示例
mysqldump -uroot -ppassword databasesName >C:\/\/databaseFile\/database.sql

恢复DB数据库表结构与数据

恢复备份语法:
mysql -uroot -p'密码' 数据库名 < /home/back/company.sql(备份文件的路径)

//示例
mysql -uroot -ppassword databasesName <C:\/\/databaseFile\/database.sql

注意事项

使用 mysqldump 备份数据库时避免锁表 可加参数--single-transaction, --skip-opt解决

  1. 导出表时默认会枷锁

对一个正在运行的数据库进行备份请慎重!! 如果一定要在服务运行期间备份,请添加 --skip-opt选项,

类似执行:

mysqldump --skip-opt -u root --password=123456 dbname >mysql.SQL

2.开启一致性事务

--opt 会lock 本次需要备份的所有表,因为本次备份的是 dbname数据库,所以会锁住dbname的所有表。

--single-transaction参数的作用,设置事务的隔离级别为可重复读,即REPEATABLE READ,这样能保证在一个事务中所有相同的查询读取到同样的数据,也就大概保证了在dump期间,如果其他innodb引擎的线程修改了表的数据并提交,对该dump线程的数据并无影响

java实现代码

/**
 * MySQL备份还原工具类
 */
public class MySqlBackupRestoreUtils {

    /**
     * 备份数据库
     * @param host host地址,可以是本机也可以是远程
     * @param userName 数据库的用户名
     * @param password 数据库的密码
     * @param savePath 备份的路径
     * @param fileName 备份的文件名
     * @param databaseName 需要备份的数据库的名称
     * @return
     * @throws IOException 
     */
    public static boolean backup(String host, String userName, String password, String backupFolderPath, String fileName,
                                 String database) throws Exception {
        File backupFolderFile = new File(backupFolderPath);
        if (!backupFolderFile.exists()) {
            // 如果目录不存在则创建
            backupFolderFile.mkdirs();
        }
        if (!backupFolderPath.endsWith(File.separator) && !backupFolderPath.endsWith("/")) {
            backupFolderPath = backupFolderPath + File.separator;
        }
        // 拼接命令行的命令
        String backupFilePath = backupFolderPath + fileName;
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("mysqldump --opt").append(" --add-drop-database").append(" --add-drop-table");
        stringBuilder.append(" -h").append(host).append(" -u").append(userName).append(" -p").append(password);
        stringBuilder.append(" --result-file=").append(backupFilePath).append(".sql").append(" --default-character-set=utf8 ").append(database);
        System.out.println(stringBuilder.toString());
        // 调用外部执行 exe 文件的 Java API
        Process process = Runtime.getRuntime().exec(getCommand(stringBuilder.toString()));
        if (process.waitFor() == 0) {
            // 0 表示线程正常终止
            System.out.println("数据已经备份到 " + backupFilePath + " 文件中");
            return true;
        }
        return false;
    }

    /**
     * 还原数据库
     * @param restoreFilePath 数据库备份的脚本路径
     * @param host IP地址
     * @param database 数据库名称
     * @param userName 用户名
     * @param password 密码
     * @return
     */
    public static boolean restore(String restoreFilePath, String host, String userName, String password, String database)
            throws Exception {
        File restoreFile = new File(restoreFilePath);
        if (restoreFile.isDirectory()) {
            for (File file : restoreFile.listFiles()) {
                if (file.exists() && file.getPath().endsWith(".sql")) {
                    restoreFilePath = file.getAbsolutePath();
                    break;
                }
            }
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("mysql -h").append(host).append(" -u").append(userName).append(" -p").append(password);
        stringBuilder.append(" ").append(database).append(" < ").append(restoreFilePath);
        try {
            Process process = Runtime.getRuntime().exec(getCommand(stringBuilder.toString()));
            if (process.waitFor() == 0) {
                System.out.println("数据已从 " + restoreFilePath + " 导入到数据库中");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private static String[] getCommand(String command) {
        String os = System.getProperty("os.name");  
        String shell = "/bin/bash";
        String c = "-c";
        if(os.toLowerCase().startsWith("win")){  
            shell = "cmd";
            c = "/c";
        }  
        String[] cmd = { shell, c, command };
        return cmd;
    }

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        String userName = "root";
        String password = "123456";
        String database = "jansens";
        
        System.out.println("开始备份");
        String backupFolderPath = "d:/dev/";
        String fileName = "mdh";
        backup(host, userName, password, backupFolderPath, fileName, database);
        System.out.println("备份成功");
        
        /*System.out.println("开始还原");
        String restoreFilePath = "d:/dev/mdh.sql";
        restore(restoreFilePath, host, userName, password, database);
        System.out.println("还原成功");*/

    }

}


备份恢复keycloak数据

备份

REST API: POST /{realm}/partial-export?exportGroupsAndRoles=true&exportClients=true

JAVA API:

//获取RealmResource
RealmResource realm = getKeycloak().realm(realmName);
//获得导出的realm对象realmRepresentation  参数:exportGroupsAndRoles=true&exportClients=true
RealmRepresentation realmRepresentation = realm.partialExport(true, true);
List<ClientRepresentation> clients = realmRepresentation.getClients();
//处理clients,设置client:gateway的secret为"0cc2e58b-8a91-432b-bae7-6cffda154cd9",删除不需要备份的client
//导出realmRepresentation为realm-export.json

还原

REST API: POST /{realm}/partialImport -d @realm-export.json

JAVA API:

//解析realm-export.json获得RealmRepresentation对象 method(File file)方法为解析json文件获取RealmRepresentation对象
RealmRepresentation realmRepresentation = method(File file);
//获取所需还原的Roles、Users、clients信息,封装PartialImportRepresentatio对象,设置导入时若资源已存在执行策略为"OVERWRITE"
List<ClientRepresentation> clients = realmRepresentation.getClients();
List<UserRepresentation> users = realmRepresentation.getUsers();
RolesRepresentation roles = realmRepresentation.getRoles();
PartialImportRepresentation partialImportRepresentation = new PartialImportRepresentation();
partialImportRepresentation.setClients(clients);
partialImportRepresentation.setUsers(users);
partialImportRepresentation.setRoles(roles);
partialImportRepresentation.setIfResourceExists("OVERWRITE");
//获取RealmResource
RealmResource realm = getKeycloak().realm(realmName);
//导入
realm.partialImport(partialImportRepresentation)

导入策略:

  • SKIP: 直接跳过,无法回滚
  • OVERWRITE:重新导入(覆盖)所有资源(符合需求)
  • FAIL:直接失败返回409

参考文档

www.consul.io/api-docs/kv

www.consul.io/api/txn

www.keycloak.org/docs-api

API

  • 备份:/dashboard/web/api/backup POST
{
    "filepath": "",
    "type": "" //默认为ALL全部备份,可以选择备份哪些内容
}
  • 恢复:/dashboard/web/api/backup/restore POST
content-type:form-data
{
    "backupFile": "路径/文件名.zip"
}