暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

#gStore-weekly | gstore源码解析(六):备份与恢复机制

图谱学苑 2022-07-21
1398

上一章我们分析了gstore安全机制系列的源码,接下来将解析gstore的备份与恢复机制。

1.1 简介

gstore的备份和恢复可通过两种方式进行:第一种是通过本地命令执行;第二种就是调用API接口,基于API接口的方式我们可以很方便的实现定时备份的功能,在gstore可视化管理工具workbench中就集成了此功能;接下来会逐一进行解析。

1.2 备份

数据库的备份是针对具体的某一个库进行全量的数据备份,备份的原理就是复制当前数据库文件夹到备份目录下,并重命名为带时间戳后缀的文件夹。

1.2.1 本地备份

本地备份的执行命令和参数说明如下:

$ bin/gbackup -db dbname -p backupfilepath
# dbname:数据库名称
# backupfilepath(optional):备份数据库路径(相对路径,默认为gStore目录下的./backups)

通过以上命令可知,在执行本地命令时需要指定备份的数据库名称,默认备份路径为gStore目录下的backups,在进行本地数据备份时建议先关闭所有API Server;,部分关键代码在Main/gbackup.cpp中,gbackup::Main函数的实现如下所示:

    int main(int argc, char * argv[]) {
    Util util;
    string db_name, backup_path;
    // 判断参数是否合法
    if (argc < 2) {
    cout << "Invalid arguments! Input \"bin/gbackup -h\" for help." << endl;
    return 0;
    } else if (argc == 2) {
    // 当本地命令输入为bin/gbackup -h[--help],则输出帮助信息
    ......
    } else {
    // 读取参数-db或--database
    db_name = Util::getArgValue(argc, argv, "db", "database");
    // 读取参数-p或--path
    backup_path= Util::getArgValue(argc, argv, "p", "path");
    // 如果备份路径参数为空,则赋值为默认备份路径./backups
    if (backup_path.empty()) {
    backup_path = DEFALUT_BACKUP_PATH;
    }
    int len = db_name.length();
    // 校验数据库名称是否合法
    if (db_name.length() > 3 && db_name.substr(len - 3, 3) == ".db") {
    cout<<"your database name can not end with .db! Input \"bin/gbackup -h\" for help."<<endl;
    return -1;
    }
    // 判断为系统库时返回错误信息
    if (db_name == "system") {
    cout << "Your database's name can not be system." << endl;
    return -1;
    }
    // 校验备份路径的合法性
    if (backup_path == "." || backup_path == "./") {
    cout << "Backup Path Can not be root, Backup Failed!" << endl;
    return 0;
    }
    if (backup_path[backup_path.length() - 1] == '/')
    backup_path = backup_path.substr(0, backup_path.length() - 1);
    // 拼装数据库源路径:db_name.db
    string db_path = db_name + ".db";
    if (backup_path == "") backup_path = DEFALUT_BACKUP_PATH;
    long tv_begin = Util::get_cur_time();
    // 执行复制
    int ret = copy(db_path, backup_path);
    if (ret == 1) {
    cout << "Database Name Error, Backup Failed!" << endl;
    } else {
    // 复制成功后,重命名为db_name.db_时间戳
    string timestamp = Util::get_timestamp();
    backup_path = backup_path + "/" + db_path;
    string _backup_path = backup_path + "_" + timestamp;
    string sys_cmd = "mv " + backup_path + " " + _backup_path;
    system(sys_cmd.c_str());
    long tv_end = Util::get_cur_time();
    cout << "DB:" << db_name << " Backup Successfully! Used " << (tv_end - tv_begin) << " ms"<<endl;
    }
    return 0;
    }}


    Main函数中调用的gbackup::copy函数如下:
      /**
      * copy dir
      * @param src_path
      * @param dest_path
      * @return int
      */int copy(string src_path, string dest_path){
      string sys_cmd;
      // 校验源路径是否存在,不存在则返回
      if(!boost::filesystem::exists(src_path)){
      //check the source path
      cout << "Source Path Error, Please Check It Again!" << endl;
      return 1;
      }
      // 判断目标路径是否存在,不存在则创建改路径
      if(!boost::filesystem::exists(dest_path)){
      //check the destnation path
      sys_cmd = "mkdir -p ./" + dest_path;
      system(sys_cmd.c_str());
      }
      // 执行复制命令
      sys_cmd = "cp -r " + src_path + " ./" + dest_path ;
      system(sys_cmd.c_str());
      return 0;// success
      }

      1.2.2 接口备份

      接口备份的前提需要先启动API Server,我们以ghttp服务为例,接口请求参数如下所示:

        {
        "operation":"backup", // 操作名称
        "username": "root", // 用户名
        "password": "123456", //密码
        "db_name": "demo", //备份数据库名称
        "backup_path": "" //备份目录,若不传或为空时,默认为gStore根目录下的backups目录
        }

        ghttp服务在接口收到备份请求时,处理逻辑过程与本地备份类似,部分核心代码为ghttp::backup_thread_new函数:

          /**
          * @description: backup a database
          * @Author:liwenjie
          * @param {const} shared_ptr
          * @param {string} db_name:the operation database name
          * @param {string} backup_path: the backup path
          * @return {*}
          */void backup_thread_new(const shared_ptr<HttpServer::Response>& response,string db_name,string backup_path,string ip) {
          string error="";
          string operation="backup";
          // 校验参数db_name是否合法
          error=checkparamValue("db_name",db_name);
          if (error.empty() == false) {
          sendResponseMsg(1003, error, response,ip,operation);
          return;
          }
          // 校验数据库是否存在
          if (checkdbexist(db_name) == false){
          error = "the database [" + db_name + "] not built yet.";
          sendResponseMsg(1004, error, response,ip,operation);
          return;
          }
          ......
          //begin backup database
          string path=backup_path;
          // 如果备份路径为空,则赋值为默认备份路径./backups
          if(path == "") path = BACKUP_PATH;
          // 校验备份路径是否合法
          if(path == "." ){
          cout << "Backup Path Can not be root or empty, Backup Failed!" << endl;
          string error = "Failed to backup the database. Backup Path Can not be root or empty.";
          sendResponseMsg(1003,error,response,ip,operation);
          pthread_rwlock_unlock(&(it_already_build->second->db_lock));
          return;
          }
          if(path[path.length() - 1] == '/') path = path.substr(0, path.length() - 1);
          cout<<"backup path:"<<path<<endl;
          string db_path = db_name + ".db";
          pthread_rwlock_wrlock(&databases_map_lock);
          // 复制文件
          int ret = copy(db_path, path);
          pthread_rwlock_unlock(&databases_map_lock);
          string timestamp="";
          if(ret == 1){
          cout << "Database Folder Misssing, Backup Failed!" << endl;
          string error = "Failed to backup the database. Database Folder Misssing.";
          sendResponseMsg(1005,error,response,ip,operation);
          pthread_rwlock_unlock(&(it_already_build->second->db_lock));
          return;
          } else{
          string time = Util::get_date_time();
          timestamp = Util::get_timestamp();
          cout << "Time:" + time << endl;
          cout << "DB:" + db_name + " Backup done!" << endl;
          }
          // 复制成功后,重命名为db_name.db_时间戳
          path = path + "/" + db_path;
          string _path = path + "_" + timestamp;
          string sys_cmd = "mv " + path + " " + _path;
          cout<<"backup command:"<<sys_cmd<<endl;
          system(sys_cmd.c_str());
          cout << "database backup done." << endl;
          string success = "Database backup successfully.";
          //返回数据
          ......
          *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
          pthread_rwlock_unlock(&(it_already_build->second->db_lock));
          // 记录操作日志
          writeIpAccessLog(ip,operation,"backup "+db_name+" database successfully!",0);

          1.3 还原

          数据库的还原也是针对具体的某一个库进行数据还原,其原理是从备份路径下复制完整的文件到gStore根目录,在删除当前数据库文件后重命名备份文件为数据库文件。

          1.3.1 本地还原

          还原的执行命令和参数说明如下:

          $ bin/grestore -db dbname -p path 
          # dbname: 数据库名称(不需要带.db)
          # path: 备份文件路径(相对gStore根目录而言的相对路径,且需要到文件夹)

          通过以上命令可知,在执行本地命令时需要指定还原的数据库名称,备份文件路径;部分关键代码在Main/grestore.cpp中,grestore::Main函数的实现如下所示:

            int main(int argc, char * argv[]) {
            Util util;
            string db_name, backup_path, db_path, path;
            if (argc < 2) {
            cout << "Invalid arguments! Input \"bin/grestore -h\" for help." << endl;
            return 0;
            } else if (argc == 2) {
            // 当本地命令输入为bin/grestore -h[--help],则输出帮助信息
            ......
            } else {
            // 读取参数-db或--database,并校验参数的合法性
            db_name = Util::getArgValue(argc, argv, "db", "database");
            ......
            // 读取参数-p或--path,并校验参数的合法性
            backup_path = Util::getArgValue(argc, argv, "p", "path");
            ......
            // 加载system库并询问是否存在待还原的库,若不存在则向system库中增加还原库的信息
            Database system_db("system");
            system_db.load();
            string sparql = "ASK WHERE{<" + db_name + "> <database_status> \"already_built\".}";
            ......
            // 复制备份文件到gStore根目录
            int ret = copy(backup_path, DEFALUT_BUILD_PATH);
            ......
            // 删除当前库文件
            db_path = db_name + ".db";
            string sys_cmd = "rm -rf " + db_path;
            system(sys_cmd.c_str());
            // 重命名备份库文件为db_name.db
            path = Util::get_folder_name(backup_path, db_name);
            sys_cmd = "mv " + path + ' ' + db_path;
            system(sys_cmd.c_str());
            return 0;
            }}

            1.3.2 接口还原

            接口还原的前提也需要先启动API Server,我们以ghttp服务为例,接口请求参数如下所示:

              {
              "operation":"restore", // 操作名称
              "username": "root", // 用户名
              "password": "123456", //密码
              "db_name": "demo", //还原数据库名称
              "backup_path": "" //备份文件的路径
              }
              ghttp服务在接口收到还原请求时,处理逻辑与本地还原类似,部分核心代码为ghttp::restore_thread_new函数:
                /**
                * @description:restore the database
                * @Author:liwenjie
                * @param {const} shared_ptr
                * @param {string} db_name the operation database name
                * @param {string} backup_path the backup path
                * @param {string} username the operation username
                * @param {string} ip the request ip address
                * @return {*}
                */void restore_thread_new(const shared_ptr<HttpServer::Response>& response,string db_name,string backup_path,string username,string ip) {
                string error="";
                string operation="restore";
                // 校验参数db_name的合法性
                error=checkparamValue("db_name",db_name);
                ......
                // 校验参数backup_path的合法性
                string path=backup_path;
                ......
                string database=db_name;
                cout << "restore "<< database << endl;


                pthread_rwlock_rdlock(&already_build_map_lock);
                std::map<std::string, struct DBInfo *>::iterator it_already_build = already_build.find(db_name);
                // 判断还原的数据库是否存在,若不存在则向system库中增加还原库的信息
                if(it_already_build == already_build.end())
                {
                ......
                }
                ......
                // 删除当前库文件
                string sys_cmd = "rm -rf " + db_name + ".db";
                system(sys_cmd.c_str());
                // 复制备份文件到gStore根目录
                pthread_rwlock_wrlock(&databases_map_lock);
                int ret = copy(path, DB_PATH);
                pthread_rwlock_unlock(&databases_map_lock);
                // 判断复制是否成功
                if (ret == 1) {
                string error = "Failed to restore the database. Backup Path Error";
                sendResponseMsg(1005,error,response,ip,operation);
                cout << "Backup Path Error, Restore Failed!" << endl;
                pthread_rwlock_unlock(&(it_already_build->second->db_lock));
                return;
                } else {
                string time = Util::get_date_time();
                cout << "Time:" + time << endl;
                cout << "DB:" + db_name + " Restore done!" << endl;
                }
                // 重命名为db_name.db
                pthread_rwlock_unlock(&(it_already_build->second->db_lock));
                path = Util::get_folder_name(path, db_name);
                sys_cmd = "mv " + path + " " + db_name + ".db";
                system(sys_cmd.c_str());
                // 返回数据
                cout << "database restore done." << endl;
                string success = "Database ["+db_name+"] restore successfully.";
                sendResponseMsg(0,success,response,ip,operation);
                }

                1.4 小结

                本章节介绍了备份与恢复的机制和两种实现方式,建议在阅读的同时结合源码Main/gbackup.cpp、Main/grestore.cpp、Main/ghttp.cpp一起分析,会更容易理解。接下来我们拟将分析gstore的RDF解析机制。


                针对gStore有任何问题也可通过加运营同学微信,邀请加入gStore图谱社区咨询。
                诚邀大家参加
                ·gStore-weekly技术文章征集活动·
                  相关技术文章,包含但不限于以下内容:系统技术解析、案例分享、实践总结、开发心得、客户案例、使用技巧、学习笔记等。文章要求原创。
                  入选周刊即送精美礼品~
                文章转载自图谱学苑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论