!87 fix: 修复数据库备份与恢复问题

* feat: 修复数据库备份与恢复问题
* feat: 启用 BINLOG 支持全量备份和增量备份,未启用 BINLOG 仅支持全量备份
* feat: 数据库恢复后自动备份,避免数据丢失
This commit is contained in:
kanzihuang
2024-01-22 03:12:16 +00:00
committed by Coder慌
parent f27d3d200f
commit de5b9e46d3
11 changed files with 147 additions and 61 deletions

View File

@@ -77,6 +77,15 @@ func (svc *DbProgramMysql) GetBinlogFilePath(fileName string) string {
}
func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbBackupHistory) (*entity.BinlogInfo, error) {
binlogEnabled, err := svc.CheckBinlogEnabled(ctx)
if err != nil {
return nil, err
}
rowFormatEnabled, err := svc.CheckBinlogRowFormat(ctx)
if err != nil {
return nil, err
}
dir := svc.getDbBackupDir(backupHistory.DbInstanceId, backupHistory.DbBackupId)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return nil, err
@@ -95,10 +104,11 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
"--add-drop-database",
"--result-file", tmpFile,
"--single-transaction",
"--master-data=2",
"--databases", backupHistory.DbName,
}
if binlogEnabled && rowFormatEnabled {
args = append(args, "--master-data=2")
}
cmd := exec.CommandContext(ctx, svc.getMysqlBin().MysqldumpPath, args...)
logx.Debugf("backup database using mysqldump binary: %s", cmd.String())
if err := runCmd(cmd); err != nil {
@@ -115,7 +125,10 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
if err != nil {
return nil, err
}
binlogInfo, err := readBinlogInfoFromBackup(reader)
binlogInfo := &entity.BinlogInfo{}
if binlogEnabled && rowFormatEnabled {
binlogInfo, err = readBinlogInfoFromBackup(reader)
}
_ = reader.Close()
if err != nil {
return nil, errors.Wrapf(err, "从备份文件中读取 binlog 信息失败")
@@ -568,18 +581,24 @@ func (svc *DbProgramMysql) ReplayBinlog(ctx context.Context, originalDatabase, t
return errors.Wrap(err, "启动 mysqlbinlog 程序失败")
}
defer func() {
_ = mysqlbinlogCmd.Cancel()
if err := mysqlbinlogCmd.Wait(); err != nil {
if replayErr != nil {
replayErr = errors.Wrap(replayErr, "运行 mysqlbinlog 程序失败")
} else {
replayErr = errors.Errorf("运行 mysqlbinlog 程序失败: %s", mysqlbinlogErr.String())
if mysqlbinlogErr.Len() > 0 {
logx.Errorf("运行 mysqlbinlog 程序失败", mysqlbinlogErr.String())
if replayErr != nil {
replayErr = errors.Wrap(replayErr, "运行 mysqlbinlog 程序失败: "+mysqlbinlogErr.String())
} else {
replayErr = errors.Errorf("运行 mysqlbinlog 程序失败: %s", mysqlbinlogErr.String())
}
}
}
}()
if err := mysqlCmd.Start(); err != nil {
logx.Error("启动 mysql 程序失败")
return errors.Wrap(err, "启动 mysql 程序失败")
}
if err := mysqlCmd.Wait(); err != nil {
logx.Errorf("运行 mysql 程序失败: %s", mysqlErr.String())
return errors.Errorf("运行 mysql 程序失败: %s", mysqlErr.String())
}
@@ -606,27 +625,30 @@ func (svc *DbProgramMysql) getServerVariable(_ context.Context, varName string)
}
// CheckBinlogEnabled checks whether binlog is enabled for the current instance.
func (svc *DbProgramMysql) CheckBinlogEnabled(ctx context.Context) error {
func (svc *DbProgramMysql) CheckBinlogEnabled(ctx context.Context) (bool, error) {
value, err := svc.getServerVariable(ctx, "log_bin")
if err != nil {
return err
switch {
case err == nil:
return strings.ToUpper(value) == "ON", nil
case errors.Is(err, sql.ErrNoRows):
return false, nil
default:
return false, err
}
if strings.ToUpper(value) != "ON" {
return errors.Errorf("数据库未启用 binlog")
}
return nil
}
// CheckBinlogRowFormat checks whether the binlog format is ROW.
func (svc *DbProgramMysql) CheckBinlogRowFormat(ctx context.Context) error {
// CheckBinlogRowFormat checks whether the binlog format is ROW or MIXED.
func (svc *DbProgramMysql) CheckBinlogRowFormat(ctx context.Context) (bool, error) {
value, err := svc.getServerVariable(ctx, "binlog_format")
if err != nil {
return err
switch {
case err == nil:
value = strings.ToUpper(value)
return value == "ROW" || value == "MIXED", nil
case errors.Is(err, sql.ErrNoRows):
return false, nil
default:
return false, err
}
if strings.ToUpper(value) != "ROW" {
return errors.Errorf("binlog 格式 %s 不是行模式", value)
}
return nil
}
func runCmd(cmd *exec.Cmd) error {

View File

@@ -153,8 +153,9 @@ func (s *DbInstanceSuite) TestRestorePontInTime() {
s.createTable(dbNameBackupTest, tableNameRestorePITTest, "")
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
time.Sleep(time.Second)
targetTime := time.Now()
// 首次恢复数据库
firstTargetTime := time.Now()
s.dropTable(dbNameBackupTest, tableNameBackupTest, "")
s.selectTable(dbNameBackupTest, tableNameBackupTest, "运行 mysql 程序失败")
s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
@@ -165,7 +166,28 @@ func (s *DbInstanceSuite) TestRestorePontInTime() {
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "运行 mysql 程序失败")
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
s.testReplayBinlog(backupHistory, targetTime)
s.testReplayBinlog(backupHistory, firstTargetTime)
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
s.testBackup(backupHistory)
// 再次恢复数据库
secondTargetTime := time.Now()
s.dropTable(dbNameBackupTest, tableNameBackupTest, "")
s.selectTable(dbNameBackupTest, tableNameBackupTest, "运行 mysql 程序失败")
s.dropTable(dbNameBackupTest, tableNameRestorePITTest, "")
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "运行 mysql 程序失败")
s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "")
s.testRestore(backupHistory)
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
s.testReplayBinlog(backupHistory, secondTargetTime)
s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")