package com.zendesk.maxwell.recovery;
import com.zendesk.maxwell.*;
import com.zendesk.maxwell.replication.BinlogConnectorReplicator;
import com.zendesk.maxwell.replication.BinlogPosition;
import com.zendesk.maxwell.replication.MaxwellReplicator;
import com.zendesk.maxwell.replication.Position;
import com.zendesk.maxwell.replication.Replicator;
import com.zendesk.maxwell.row.HeartbeatRowMap;
import com.zendesk.maxwell.row.RowMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import snaq.db.ConnectionPool;
public class Recovery {
static final Logger LOGGER = LoggerFactory.getLogger(Recovery.class);
private final ConnectionPool replicationConnectionPool;
private final RecoveryInfo recoveryInfo;
private final MaxwellMysqlConfig replicationConfig;
private final String maxwellDatabaseName;
private final RecoverySchemaStore schemaStore;
private final boolean shykoMode;
public Recovery(MaxwellMysqlConfig replicationConfig,
String maxwellDatabaseName,
ConnectionPool replicationConnectionPool,
CaseSensitivity caseSensitivity,
RecoveryInfo recoveryInfo,
boolean shykoMode) {
this.replicationConfig = replicationConfig;
this.replicationConnectionPool = replicationConnectionPool;
this.recoveryInfo = recoveryInfo;
this.schemaStore = new RecoverySchemaStore(replicationConnectionPool, maxwellDatabaseName, caseSensitivity);
this.maxwellDatabaseName = maxwellDatabaseName;
this.shykoMode = shykoMode;
}
public Position recover() throws Exception {
String recoveryMsg = String.format(
"old-server-id: %d, position: %s",
recoveryInfo.serverID,
recoveryInfo.position
);
LOGGER.warn("attempting to recover from master-change: " + recoveryMsg);
List<BinlogPosition> list = getBinlogInfo();
for ( int i = list.size() - 1; i >= 0 ; i-- ) {
BinlogPosition binlogPosition = list.get(i);
Position position = recoveryInfo.position.withBinlogPosition(binlogPosition);
LOGGER.debug("scanning binlog: " + binlogPosition);
Replicator replicator;
if ( shykoMode ) {
replicator = new BinlogConnectorReplicator(
this.schemaStore,
null,
null,
replicationConfig,
0L, // server-id of 0 activates "mysqlbinlog" behavior where the server will stop after each binlog
maxwellDatabaseName,
position,
true,
recoveryInfo.clientID
);
} else {
replicator = new MaxwellReplicator(
this.schemaStore,
null,
null,
replicationConfig,
0L, // server-id of 0 activates "mysqlbinlog" behavior where the server will stop after each binlog
false,
maxwellDatabaseName,
position,
true,
recoveryInfo.clientID
);
}
replicator.setFilter(new RecoveryFilter(this.maxwellDatabaseName));
Position p = findHeartbeat(replicator);
if ( p != null ) {
LOGGER.warn("recovered new master position: " + p);
return p;
}
}
LOGGER.error("Could not recover from master-change: " + recoveryMsg);
return null;
}
/**
* try to find a given heartbeat value from the replicator.
* @return A BinlogPosition where the heartbeat was found, or null if none was found.
*/
private Position findHeartbeat(Replicator r) throws Exception {
r.startReplicator();
for (RowMap row = r.getRow(); row != null ; row = r.getRow()) {
if (!(row instanceof HeartbeatRowMap)) {
continue;
}
HeartbeatRowMap heartbeatRow = (HeartbeatRowMap) row;
Position heartbeatPosition = heartbeatRow.getPosition();
if (heartbeatPosition.getLastHeartbeatRead() == recoveryInfo.getHeartbeat())
return heartbeatPosition;
}
return null;
}
/**
* fetch a list of binlog positions representing the start of each binlog file
*
* @return a list of binlog positions to attempt recovery at
* */
private List<BinlogPosition> getBinlogInfo() throws SQLException {
ArrayList<BinlogPosition> list = new ArrayList<>();
try ( Connection c = replicationConnectionPool.getConnection() ) {
ResultSet rs = c.createStatement().executeQuery("SHOW BINARY LOGS");
while ( rs.next() ) {
list.add(BinlogPosition.at(4, rs.getString("Log_name")));
}
}
return list;
}
}