blob: 5bf1373a6e53e480ad8807ccb5a9e16af4178f45 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.backup.impl;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.BACKUP_ATTEMPTS_PAUSE_MS_KEY;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.BACKUP_MAX_ATTEMPTS_KEY;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.DEFAULT_BACKUP_ATTEMPTS_PAUSE_MS;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.DEFAULT_BACKUP_MAX_ATTEMPTS;
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.JOB_NAME_CONF_KEY;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.backup.BackupCopyJob;
import org.apache.hadoop.hbase.backup.BackupInfo;
import org.apache.hadoop.hbase.backup.BackupInfo.BackupPhase;
import org.apache.hadoop.hbase.backup.BackupInfo.BackupState;
import org.apache.hadoop.hbase.backup.BackupRequest;
import org.apache.hadoop.hbase.backup.BackupRestoreFactory;
import org.apache.hadoop.hbase.backup.BackupType;
import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager;
import org.apache.hadoop.hbase.backup.util.BackupUtils;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Full table backup implementation
*
*/
@InterfaceAudience.Private
public class FullTableBackupClient extends TableBackupClient {
private static final Logger LOG = LoggerFactory.getLogger(FullTableBackupClient.class);
public FullTableBackupClient() {
}
public FullTableBackupClient(final Connection conn, final String backupId, BackupRequest request)
throws IOException {
super(conn, backupId, request);
}
/**
* Do snapshot copy.
* @param backupInfo backup info
* @throws Exception exception
*/
protected void snapshotCopy(BackupInfo backupInfo) throws Exception {
LOG.info("Snapshot copy is starting.");
// set overall backup phase: snapshot_copy
backupInfo.setPhase(BackupPhase.SNAPSHOTCOPY);
// call ExportSnapshot to copy files based on hbase snapshot for backup
// ExportSnapshot only support single snapshot export, need loop for multiple tables case
BackupCopyJob copyService = BackupRestoreFactory.getBackupCopyJob(conf);
// number of snapshots matches number of tables
float numOfSnapshots = backupInfo.getSnapshotNames().size();
LOG.debug("There are " + (int) numOfSnapshots + " snapshots to be copied.");
for (TableName table : backupInfo.getTables()) {
// Currently we simply set the sub copy tasks by counting the table snapshot number, we can
// calculate the real files' size for the percentage in the future.
// backupCopier.setSubTaskPercntgInWholeTask(1f / numOfSnapshots);
int res;
ArrayList<String> argsList = new ArrayList<>();
argsList.add("-snapshot");
argsList.add(backupInfo.getSnapshotName(table));
argsList.add("-copy-to");
argsList.add(backupInfo.getTableBackupDir(table));
if (backupInfo.getBandwidth() > -1) {
argsList.add("-bandwidth");
argsList.add(String.valueOf(backupInfo.getBandwidth()));
}
if (backupInfo.getWorkers() > -1) {
argsList.add("-mappers");
argsList.add(String.valueOf(backupInfo.getWorkers()));
}
String[] args = argsList.toArray(new String[0]);
String jobname = "Full-Backup_" + backupInfo.getBackupId() + "_" + table.getNameAsString();
if (LOG.isDebugEnabled()) {
LOG.debug("Setting snapshot copy job name to : " + jobname);
}
conf.set(JOB_NAME_CONF_KEY, jobname);
LOG.debug("Copy snapshot " + args[1] + " to " + args[3]);
res = copyService.copy(backupInfo, backupManager, conf, BackupType.FULL, args);
// if one snapshot export failed, do not continue for remained snapshots
if (res != 0) {
LOG.error("Exporting Snapshot " + args[1] + " failed with return code: " + res + ".");
throw new IOException("Failed of exporting snapshot " + args[1] + " to " + args[3]
+ " with reason code " + res);
}
conf.unset(JOB_NAME_CONF_KEY);
LOG.info("Snapshot copy " + args[1] + " finished.");
}
}
/**
* Backup request execution.
*
* @throws IOException if the execution of the backup fails
*/
@Override
public void execute() throws IOException {
try (Admin admin = conn.getAdmin()) {
// Begin BACKUP
beginBackup(backupManager, backupInfo);
String savedStartCode;
boolean firstBackup;
// do snapshot for full table backup
savedStartCode = backupManager.readBackupStartCode();
firstBackup = savedStartCode == null || Long.parseLong(savedStartCode) == 0L;
if (firstBackup) {
// This is our first backup. Let's put some marker to system table so that we can hold the
// logs while we do the backup.
backupManager.writeBackupStartCode(0L);
}
// We roll log here before we do the snapshot. It is possible there is duplicate data
// in the log that is already in the snapshot. But if we do it after the snapshot, we
// could have data loss.
// A better approach is to do the roll log on each RS in the same global procedure as
// the snapshot.
LOG.info("Execute roll log procedure for full backup ...");
Map<String, String> props = new HashMap<>();
props.put("backupRoot", backupInfo.getBackupRootDir());
admin.execProcedure(LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_SIGNATURE,
LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_NAME, props);
newTimestamps = backupManager.readRegionServerLastLogRollResult();
if (firstBackup) {
// Updates registered log files
// We record ALL old WAL files as registered, because
// this is a first full backup in the system and these
// files are not needed for next incremental backup
List<String> logFiles = BackupUtils.getWALFilesOlderThan(conf, newTimestamps);
backupManager.recordWALFiles(logFiles);
}
// SNAPSHOT_TABLES:
backupInfo.setPhase(BackupPhase.SNAPSHOT);
for (TableName tableName : tableList) {
String snapshotName =
"snapshot_" + Long.toString(EnvironmentEdgeManager.currentTime()) + "_"
+ tableName.getNamespaceAsString() + "_" + tableName.getQualifierAsString();
snapshotTable(admin, tableName, snapshotName);
backupInfo.setSnapshotName(tableName, snapshotName);
}
// SNAPSHOT_COPY:
// do snapshot copy
LOG.debug("snapshot copy for " + backupId);
snapshotCopy(backupInfo);
// Updates incremental backup table set
backupManager.addIncrementalBackupTableSet(backupInfo.getTables());
// BACKUP_COMPLETE:
// set overall backup status: complete. Here we make sure to complete the backup.
// After this checkpoint, even if entering cancel process, will let the backup finished
backupInfo.setState(BackupState.COMPLETE);
// The table list in backupInfo is good for both full backup and incremental backup.
// For incremental backup, it contains the incremental backup table set.
backupManager.writeRegionServerLogTimestamp(backupInfo.getTables(), newTimestamps);
HashMap<TableName, HashMap<String, Long>> newTableSetTimestampMap =
backupManager.readLogTimestampMap();
Long newStartCode =
BackupUtils.getMinValue(BackupUtils
.getRSLogTimestampMins(newTableSetTimestampMap));
backupManager.writeBackupStartCode(newStartCode);
// backup complete
completeBackup(conn, backupInfo, backupManager, BackupType.FULL, conf);
} catch (Exception e) {
failBackup(conn, backupInfo, backupManager, e, "Unexpected BackupException : ",
BackupType.FULL, conf);
throw new IOException(e);
}
}
protected void snapshotTable(Admin admin, TableName tableName, String snapshotName)
throws IOException {
int maxAttempts =
conf.getInt(BACKUP_MAX_ATTEMPTS_KEY, DEFAULT_BACKUP_MAX_ATTEMPTS);
int pause =
conf.getInt(BACKUP_ATTEMPTS_PAUSE_MS_KEY, DEFAULT_BACKUP_ATTEMPTS_PAUSE_MS);
int attempts = 0;
while (attempts++ < maxAttempts) {
try {
admin.snapshot(snapshotName, tableName);
return;
} catch (IOException ee) {
LOG.warn("Snapshot attempt " + attempts + " failed for table " + tableName
+ ", sleeping for " + pause + "ms", ee);
if (attempts < maxAttempts) {
try {
Thread.sleep(pause);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
throw new IOException("Failed to snapshot table "+ tableName);
}
}