为MySQL创建应用一致性快照最佳实践(Linux)

更新时间:2025-04-29 10:16:03

在创建应用一致性快照时,系统会暂停正在写入的数据,确保快照捕获到的数据的完整性和一致性。通过应用一致性快照恢复数据时,可以减少数据损坏和丢失的风险,可以确保应用(例如MySQL)能够正常启动,且数据与创建快照时的状态一致。本文以在CentOS 7.9实例上部署MySQL 8.0数据库为例,验证应用一致性快照的数据备份效果。

前提条件

  • ECS实例的云盘类型是ESSD云盘,且云盘未开启多重挂载功能

  • ECS实例处于运行中且云助手运行正常。如何查看云助手状态请参见查看云助手状态及异常状态处理

  • 您已安装MySQL数据库,并知悉数据库的登录名及密码。具体操作,请参见手动部署MySQL数据库(Linux)

  • 已为ECS实例配置应用一致性快照相关的RAM角色以及自定义权限策略。具体操作,请参见创建RAM角色并授予给ECS实例

    说明

    创建应用一致性快照时,需要调用云助手访问ECS实例并执行命令,因此需要通过RAM角色授予云助手相应的权限。

    • RAM角色:自定义,例如AppSnapshotRoleName。

    • 自定义权限策略:策略内容如下,表示在创建快照过程中具有查询快照、创建快照、设置标签和查询云盘信息等相关权限。

      {
          "Version": "1",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": [
                      "ecs:DescribeSnapshot*",
                      "ecs:CreateSnapshot*",
                      "ecs:TagResources",
                      "ecs:DescribeDisks"
                  ],
                  "Resource": [
                      "*"
                  ],
                  "Condition": {}
              }
          ]
      }

操作思路

本实践通过如下操作来验证,如何通过创建应用一致性快照来确保在创建快照的瞬间,数据库状态与创建快照时的状态一致,从而保证应用一致性。操作流程如下:

image
  1. 步骤一:创建prescript.shpostscript.sh脚本

    创建暂停(prescript.sh)和恢复(postscript.sh)数据库写操作的脚本,用于暂停(创建快照前)和恢复(快照创建后)数据库的写操作,在创建应用一致性快照时需要使用这两个脚本。

  2. 步骤二:准备数据库验证环境

    创建数据库表和存储过程,用于模拟数据写入操作,以便后续验证应用一致性快照的效果。并在数据库中调用存储过程(TestPIT)模拟数据插入操作,后续创建应用一致性快照时,方便查看数据库的暂停效果和回滚数据后的效果。

  3. 步骤三:通过控制台创建应用一致性快照

    MySQL数据库所在的Linux实例创建应用一致性快照并应用暂停和恢复数据库写操作脚本。创建应用一致性快照时,数据库的写操作会暂时停止,确保数据一致性。

  4. 步骤四:验证应用一致性快照是否创建成功

    通过云助手命令执行结果确认快照是否创建成功,并查看暂停和恢复数据库写操作的时间点。

  5. 步骤五:验证通过应用一致性快照恢复数据的效果

    先通过应用一致性快照回滚云盘,然后将MySQL数据库最后写入时间与数据库暂停时间(prescript.sh脚本执行时间)做对比,来判断应用一致性快照的数据恢复是否正确。

    • 如果MySQL数据最后写入时间早于prescript.sh脚本执行时间:说明数据库在暂停期间没有写入操作,数据恢复后MySQL数据最后写入时间反映了暂停前的最后状态,数据库状态与快照创建时的状态一致。

    • 如果MySQL数据最后写入时间等于或晚于prescript.sh脚本执行时间:说明数据库在暂停期间仍有写入操作,应用一致性快照的效果未达到预期。

操作步骤

步骤一:创建prescript.shpostscript.sh脚本

重要

本文中使用的prescript.shpostscript.sh脚本内容仅为验证示例使用。如果您需要为自己的业务应用创建应用一致性快照,请根据实际业务编写prescript.shpostscript.sh脚本。关于脚本的更多介绍,请参见创建应用一致性快照

  1. 使用root用户远程连接ECS实例。

    具体操作,请参见使用Workbench工具以SSH协议登录Linux实例

  2. 创建/tmp/prescript.sh脚本并写入脚本内容。

    1. 使用root用户创建/tmp/prescript.sh

      vim /tmp/prescript.sh
    2. 输入i,进入编辑模式。

    3. 在脚本中根据应用自定义prescript.sh脚本内容。

      展开查看prescript.sh脚本内容:

      TIMESTAMP=`date +%s`
      MYSQL_TEMP_FILE_NAME="/tmp/mysqlfreeze${TIMESTAMP}.tmp"
      LOG_FILE_NAME="/tmp/mysqlfreeze${TIMESTAMP}.log"
      
      # 设置您的MySQL用户名
      export MYSQL_USER="$MYSQL_USER"
      # 设置您的MySQL密码
      export MYSQL_PWD="$MYSQL_PASSWORD"
      
      function Log()
      {
          echo "$1" 
          echo "$1" >> ${LOG_FILE_NAME}
      }
      
      
      function ExitWithResult()
      {
          Log "[INFO]:mysql freeze result is $1."
          exit $1
      }
      
      function Main()
      {
          Log "*********************************************************************"
          Log "[INFO]:Begin to freeze mysql."
      
          which mysql
          if [ $? -ne 0 ]
          then
              Log "[INFO]:mysql is not installed."
              ExitWithResult 0
          fi  
      
          systemctl status mysqld.service | grep "inactive (dead)"
          if [ $? -ne 1 ]
          then
              Log "[ERROR]:mysql is not running."
              ExitWithResult 0
          fi  
      
          mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "show processlist;" > "${MYSQL_TEMP_FILE_NAME}" 2>&1
          if [ $? -ne 0 ]
          then
              cat ${MYSQL_TEMP_FILE_NAME} >>"${LOG_FILE_NAME}"
              [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
              Log "[ERROR]:Show process list failed."
              ExitWithResult 1
          fi
      
      
          process_id=`cat ${MYSQL_TEMP_FILE_NAME} | grep "select 1 and sleep(25)" | awk -F " " '{print $1}'`
          if [ "$process_id" != "" ]
          then
              cat ${MYSQL_TEMP_FILE_NAME} >>"${LOG_FILE_NAME}"
              [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
              Log "[ERROR]:MySQL already been freezed "
              ExitWithResult 1
          fi
      
          cat ${MYSQL_TEMP_FILE_NAME}
      
          Log "[INFO]:Try to execute flush tables command"
      
             echo "flush tables with read lock;select 1 and sleep(25);" | nohup mysql -u$MYSQL_USER   >> "${LOG_FILE_NAME}" 2>&1 &
          if [ $? -ne 0 ]
          then
              Log "[ERROR]:Freeze mysql failed."
              ExitWithResult 1
          fi  
      
          Log "[INFO]:Flush tables command execute success"
      
          checkTime=0
          while [ 1 ]
          do
              mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "show processlist;" > "${MYSQL_TEMP_FILE_NAME}" 2>&1
              if [ $? -ne 0 ]
              then
                  cat ${MYSQL_TEMP_FILE_NAME} >>"${LOG_FILE_NAME}"
                  [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
                  Log "[ERROR]:Show process list failed."
                  ExitWithResult 1
              fi
              
              cat ${MYSQL_TEMP_FILE_NAME}
      
              process_id=`cat ${MYSQL_TEMP_FILE_NAME} | grep "select 1 and sleep(25)" | awk -F " " '{print $1}'`
              if [ "$process_id" = "" ]
              then
                  checkTime=`expr $checkTime + 1`
                  Log "[INFO]:Mysql is not freeze. checkTime is ${checkTime}"
                  sleep 1
              else
                  Log "[INFO]:Found sleep command in processlist,freeze success"
                  break
              fi
      
                 if [ $checkTime -eq 10 ]
              then
                  cat "${MYSQL_TEMP_FILE_NAME}" >>"${LOG_FILE_NAME}" 2>&1
                  
                  freeze_id=`cat ${MYSQL_TEMP_FILE_NAME} | grep "flush tables with read lock" | awk -F " " '{print $1}'`            
                  mysql -u$MYSQL_USER -e "kill $freeze_id;" >> "${LOG_FILE_NAME}" 2>&1
                  if [ $? -ne 0 ]
                  then
                      Log "[ERROR]:Thaw mysql failed."
                  fi    
      
                  [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
                  Log "[ERROR]:Mysql is not freeze. Will return error"
                  ExitWithResult 1
              fi
          done
      
             [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
          Log "[INFO]:Finish freeze mysql."
          ExitWithResult 0
      }
      Main

      在脚本中,您需要修改以下参数信息:

      • $MYSQL_USER:修改为MySQL用户名。

      • $MYSQL_PASSWORD:修改为MySQL密码。

    4. Esc键,输入:wq,然后按回车键,退出并保存内容。

    5. 为脚本设置仅root用户读、写和执行权限。

      重要

      为了保证脚本的执行安全,请确保仅root用户具有对脚本的读、写及执行权限,即权限为700,否则执行脚本时会判断失败。

      chmod 700 /tmp/prescript.sh
  3. 创建/tmp/postscript.sh脚本并写入脚本内容。

    1. 使用root用户创建/tmp/postscript.sh

      vim /tmp/postscript.sh
    2. 输入i,进入编辑模式。

    3. 在脚本中根据应用自定义postscript.sh脚本内容。

      展开查看postscript.sh脚本内容:

      TIMESTAMP=`date +%s`
      MYSQL_TEMP_FILE_NAME="/tmp/mysqlthaw${TIMESTAMP}.tmp"
      LOG_FILE_NAME="/tmp/mysqlthaw${TIMESTAMP}.log"
      # 设置您的MySQL用户名
      export MYSQL_USER="$MYSQL_USER"
      # 设置您的MySQL密码
      export MYSQL_PWD="$MYSQL_PASSWORD"
      
      function Log()
      {
          echo "$1" 
          echo "$1" >> ${LOG_FILE_NAME}
      }
      
      
      function ExitWithResult()
      {
          Log "[INFO]:mysql unfreeze result is $1."
          exit $1
      }
      
      function Main()
      {
          Log "*********************************************************************"
          Log "[INFO]:Begin to thaw mysql."   
      
          which mysql
          if [ $? -ne 0 ]
          then
              Log "[INFO]:mysql is not installed."
              ExitWithResult 0
          fi  
      
          systemctl status mysqld.service | grep "inactive (dead)"
          if [ $? -ne 1 ]
          then
              Log "[ERROR]:mysql is not running."
              ExitWithResult 0
          fi  
      
      
          mysql -u$MYSQL_USER  -e "show processlist;" > "${MYSQL_TEMP_FILE_NAME}" 2>&1
          if [ $? -ne 0 ]
          then
              cat ${MYSQL_TEMP_FILE_NAME} >>"${LOG_FILE_NAME}"
              [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
              Log "[ERROR]:show process list failed."
              ExitWithResult 1
          fi
      
          Log "[INFO]:show process list success."
      
          cat ${MYSQL_TEMP_FILE_NAME}
          
          process_ids=`cat ${MYSQL_TEMP_FILE_NAME} | grep "select 1 and sleep(25)" | awk -F " " '{print $1}'`
          if [ "$process_ids" = "" ]
          then
              [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
              Log "[ERROR]:Get freeze process_id failed."
              ExitWithResult 1
          fi
      
          cat ${MYSQL_TEMP_FILE_NAME} | grep "select 1 and sleep(25)" | awk -F " " '{print $1}'| while read pid
          do
              Log "[INFO]:Try to stop sql process ${pid}."
      
              mysql -u$MYSQL_USER  -e "kill $pid;" >> "${LOG_FILE_NAME}" 2>&1
              if [ $? -ne 0 ]
              then
                  [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
                  Log "[ERROR]:Thaw mysql failed.PIDs is ${process_ids}"
                  ExitWithResult 1
              fi   
              Log "[INFO]:Stop sql process ${pid} success."
      
          done 
          
          [ -f ${MYSQL_TEMP_FILE_NAME} ] && rm -rf ${MYSQL_TEMP_FILE_NAME}
          Log "[INFO]:Finish thaw mysql."
          ExitWithResult 0
      }
      Main

      在脚本中,您需要修改以下参数信息:

      • $MYSQL_USER:修改为MySQL用户名。

      • $MYSQL_PASSWORD:修改为MySQL密码。

    4. Esc键,输入:wq,然后按回车键,退出并保存内容。

    5. 为脚本设置仅root用户的读、写和执行权限。

      重要

      为了保证脚本的执行安全,请确保仅root用户具有对脚本的读、写及执行权限,权限为700,否则执行脚本时会判断失败。

      chmod 700 /tmp/postscript.sh
  4. 进入/tmp目录查看脚本权限是否正确。

    cd /tmp
    ls -l

    结果如下所示,表示脚本权限正确。

    image

步骤二:准备数据库验证环境

  1. 创建测试脚本(/root/test.sql)。

    1. 创建并打开测试脚本(/root/test.sql)。

      vim /root/test.sql
    2. 输入i,进入编辑模式。

    3. 编写验证的SQL脚本。

      SQL脚本中内容包含创建数据库表(PointInTime)及验证存储过程(TestPIT),具体内容如下所示。

      USE AdventureWorks;
      CREATE TABLE PointInTime(id int, t datetime);
      DELIMITER $$
      CREATE PROCEDURE `TestPIT`()
      BEGIN
      DECLARE i int;
      SET i=1;
      WHILE i < 180
      DO
      INSERT INTO PointInTime VALUES(i, now());
      SELECT SLEEP(1);
      SET i=i+1;
      END WHILE;
      END $$
      DELIMITER ;
    4. Esc键,输入:wq,然后按回车键,退出并保存内容。

  2. 登录MySQL数据库。

    输入以下命令,按回车键,并根据界面提示输入MySQL密码。

    mysql -u <mysqlUserName> -p

    其中<mysqlUserName>需替换为您的MySQL用户名。

  3. 创建新的数据库AdventureWorks。

    CREATE DATABASE AdventureWorks;
  4. 执行测试脚本。

    source /root/test.sql
  5. 调用存储过程(TestPIT)。

    CALL TestPIT;
    重要

    在验证过程中,您需要在TestPIT运行(大概3分钟左右)完成前创建应用一致性快照,否则无法验证应用一致性快照的效果。

步骤三:通过控制台创建应用一致性快照

  1. 访问ECS控制台-快照一致性组

  2. 在页面左侧顶部,选择目标资源所在的资源组和地域。地域

  3. 快照一致性组页签中单击创建快照一致性组

  4. 创建快照对话框中,设置快照一致性组参数。

    1. 资源类型默认选中实例

    2. 选择单台实例及实例中的ESSD类型云盘。

    3. 展开高级配置,设置应用一致性快照。

      1. 选中启用应用一致性快照

      2. 设置prescript.sh脚本和postscript.sh脚本的路径与步骤一创建的脚本路径一致。

      3. 设置文件系统IO暂停与恢复时长。

  5. 单击确认

    创建后会返回云助手命令执行ID,您可以根据命令执行ID查看创建结果。

步骤四:验证应用一致性快照是否创建成功

  1. 单击云助手命令执行ID,在云助手页面查看执行结果。

    image

    如上图所示,ExitCode返回值为0,表示云助手上创建应用一致性快照执行成功,此时回显信息中显示创建应用一致性快照和快照一致性组ID。

    说明

    如果ExitCode返回值不为0,请根据ExitCode错误码信息排查相关问题。更多信息,请参见错误码信息

  2. 在云助手的返回信息中,查看数据库的暂停和恢复时间点。

    在返回信息中,找到prescript.sh脚本开始时间和postscript.sh脚本完成时间。

    • prescript.sh脚本执行时间为2024-08-27 15:27:55,表示数据库在该时间点暂停写操作。image

    • postscript.sh脚本执行时间为2024-08-27 15:27:57,表示数据库在该时间点恢复写操作。

      image

  3. 查看快照一致性组和云盘快照信息。

    1. 访问ECS控制台-快照一致性组

    2. 快照一致性组页签中找到已创建的快照一致性组,单击快照一致性组ID查看快照详情。

    3. 快照信息区域,根据快照的标签信息,检查是否成功创建应用一致性快照。

      示例中云盘快照的标签显示APPConsistent:True,表示创建的是应用一致性快照。

      image

  4. 连接MySQL数据库,查看数据暂停提交时间。

    1. 远程连接ECS实例。

      具体操作,请参见使用Workbench工具以SSH协议登录Linux实例

    2. 登录MySQL数据库。

      输入以下命令,按回车键,并根据界面提示输入MySQL密码。

      mysql -u <mysqlUserName> -p

      其中<mysqlUserName>需替换为您的MySQL用户名。

    3. 查询数据库表PointInTime的内容。

      USE AdventureWorks;
      SELECT * FROM PointInTime;
    4. 在查询结果中,查看数据库的暂停时间点。

      您可以发现在2024-08-27 15:27:55~2024-08-27 15:27:58时间段内没有插入数据。

      image

步骤五:验证通过应用一致性快照恢复数据的效果

  1. 通过已创建的快照一致性组回滚云盘。

    具体操作,请参见通过快照一致性组回滚云盘

  2. 重新登录MySQL数据库并查询数据库表PointInTime的内容。

    1. 远程连接ECS实例。

      具体操作,请参见使用Workbench工具以SSH协议登录Linux实例

    2. 登录MySQL数据库。

      输入以下命令,按回车键,并根据界面提示输入MySQL密码。

      mysql -u <mysqlUserName> -p

      其中<mysqlUserName>需替换为您的MySQL用户名。

    3. 查询数据库表PointInTime的内容。

      USE AdventureWorks;
      SELECT * FROM PointInTime;
    4. 在查询结果中,查看恢复数据后数据库最后一条记录的时间点。

      您可以发现数据库最后一条插入数据的时间点为2024-08-27 15:27:54,早于步骤四中查询的暂停时间点2024-08-27 15:27:55,因此证明关于MySQL的应用一致性快照备份的结果是正确的。

      image

  • 本页导读 (1)
  • 前提条件
  • 操作思路
  • 操作步骤
  • 步骤一:创建prescript.sh和postscript.sh脚本
  • 步骤二:准备数据库验证环境
  • 步骤三:通过控制台创建应用一致性快照
  • 步骤四:验证应用一致性快照是否创建成功
  • 步骤五:验证通过应用一致性快照恢复数据的效果
AI助理

点击开启售前

在线咨询服务

你好,我是AI助理

可以解答问题、推荐解决方案等