package org.apache.fullmatix.mysql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.fullmatix.mysql.MasterStatus.MasterStatusAttribute;
import org.apache.fullmatix.mysql.SlaveStatus.SlaveStatusAttribute;
import org.apache.helix.model.InstanceConfig;
import org.apache.helix.spectator.RoutingTableProvider;
import org.apache.log4j.Logger;
public class Replicator extends RoutingTableProvider {
private static Logger LOG = Logger.getLogger(Replicator.class);
private InstanceConfig currentMasterConfig;
static String user = "monty";
static String password = "some_pass";
AtomicReference<ReplicationState> replicationState =
new AtomicReference<Replicator.ReplicationState>(ReplicationState.STOPPED);
private InstanceConfig _localInstanceConfig;
enum ReplicationState {
INITIATED_START,
INITIATED_STOP,
STARTING,
STARTED,
SWITCHING,
STOPPING,
STOPPED
}
public Replicator(InstanceConfig instanceConfig) {
_localInstanceConfig = instanceConfig;
}
public void initiateStart() {
replicationState.set(ReplicationState.INITIATED_START);
// do nothing here, it correct method will be invoked by ClusterStateObserver
}
public void start(InstanceConfig newMasterConfig) throws Exception {
// LOG.info("Starting replication for ");
replicationState.set(ReplicationState.STARTING);
if (newMasterConfig != null) {
String master = newMasterConfig.getInstanceName();
if (currentMasterConfig == null
|| !master.equalsIgnoreCase(currentMasterConfig.getInstanceName())) {
LOG.info("Found new master:" + newMasterConfig.getInstanceName());
if (currentMasterConfig != null) {
stop();
}
currentMasterConfig = newMasterConfig;
startReplication(currentMasterConfig);
} else {
LOG.info("Already replicating from " + master);
}
} else {
LOG.info("No master found");
}
}
public void startReplication(InstanceConfig masterInstanceConfig) throws Exception {
String masterHost = masterInstanceConfig.getHostName();
String masterPort = masterInstanceConfig.getRecord().getSimpleField("mysql_port");
String slaveHost = _localInstanceConfig.getHostName();
String slaveMysqlPort = _localInstanceConfig.getPort();
setupReplication(masterHost, masterPort, slaveHost, slaveMysqlPort);
replicationState.set(ReplicationState.STARTED);
}
private void setupReplication(String masterHost, String masterMysqlPort, String slaveHost,
String slaveMysqlPort) throws Exception {
boolean started = true;
Connection masterConnection = null;
Connection slaveConnection = null;
Statement masterStatement = null;
Statement slaveStatement = null;
try {
slaveConnection =
DriverManager.getConnection("jdbc:mysql://" + slaveHost + ":" + slaveMysqlPort + "",
user, password);
slaveStatement = slaveConnection.createStatement();
masterConnection =
DriverManager.getConnection("jdbc:mysql://" + masterHost + ":" + masterMysqlPort + "",
user, password);
masterStatement = masterConnection.createStatement();
LOG.info("slave status before stopping replication");
SlaveStatus slaveStatusBeforeStop =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info(slaveStatusBeforeStop);
// FETCH MASTER STATUS
LOG.info("master status");
MasterStatus masterStatus =
new MasterStatus(masterStatement.executeQuery("show master status"));
LOG.info(masterStatus);
// STOP SLAVE
LOG.info("Stopping slave");
slaveStatement.execute("stop slave");
LOG.info("slave status after stopping replication");
SlaveStatus slaveStatusAfterStop =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info(slaveStatusAfterStop);
// CHANGE MASTER
LOG.info("starting replication with new master");
StringBuilder changeMasterCommand = new StringBuilder();
changeMasterCommand.append("change master to ") //
.append(" master_host=").append("'").append(masterHost).append("'") // hostname
.append(",master_port=").append(masterMysqlPort) // port
.append(",master_user=").append("'").append(user).append("'") // user that has super
// privilege to setup
// replication
.append(",master_password=").append("'").append(password).append("'") // password
.append(",master_auto_position=1");
LOG.info("Running change master to command:" + changeMasterCommand);
slaveStatement.execute(changeMasterCommand.toString());
// START SLAVE
LOG.info("Successfully ran change master command, executing start slave");
slaveStatement.execute("start slave");
SlaveStatus slaveStatusAfterStart =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info("slave status after starting replication");
LOG.info(slaveStatusAfterStart);
started = true;
} catch (Exception e) {
LOG.error(e);
} finally {
if (slaveConnection != null) {
slaveConnection.close();
}
if (masterConnection != null) {
masterConnection.close();
}
}
if (started) {
LOG.info("Replication started in background");
} else {
throw new Exception("Unable to start replicating from :" + masterHost);
}
}
public void initiateStop() {
replicationState.set(ReplicationState.INITIATED_STOP);
stop();
}
public void stop() {
if (replicationState.get().equals(ReplicationState.STOPPED)
|| replicationState.get().equals(ReplicationState.STOPPING) ||
currentMasterConfig ==null) {
// TODO If state is STOPPING, wait until the status is changed to STOPPED
return;
}
LOG.info("Stopping replication from current master:" + currentMasterConfig.getInstanceName());
replicationState.set(ReplicationState.STOPPING);
Connection slaveConnection = null;
Statement slaveStatement = null;
String slaveHost = _localInstanceConfig.getHostName();
String slaveMysqlPort = _localInstanceConfig.getPort();
try {
slaveConnection =
DriverManager.getConnection("jdbc:mysql://" + slaveHost + ":" + slaveMysqlPort + "",
user, password);
slaveStatement = slaveConnection.createStatement();
LOG.info("slave status before stopping replication");
SlaveStatus slaveStatusBeforeStop =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info(slaveStatusBeforeStop);
// STOP SLAVE
LOG.info("Stopping slave");
slaveStatement.execute("stop slave");
LOG.info("slave status after stopping replication");
SlaveStatus slaveStatusAfterStop =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info(slaveStatusAfterStop);
currentMasterConfig = null;
} catch (Exception e) {
LOG.error(e);
} finally {
if (slaveConnection != null) {
try {
slaveConnection.close();
} catch (SQLException e) {
// ignore
}
}
}
}
/*
*
*/
public void catchupAndStop() {
Connection masterConnection = null;
Connection slaveConnection = null;
Statement masterStatement = null;
Statement slaveStatement = null;
String slaveHost = _localInstanceConfig.getHostName();
String slaveMysqlPort = _localInstanceConfig.getPort();
try {
slaveConnection =
DriverManager.getConnection("jdbc:mysql://" + slaveHost + ":" + slaveMysqlPort + "",
user, password);
slaveStatement = slaveConnection.createStatement();
LOG.info("slave status before stopping replication");
SlaveStatus slaveStatusBeforeStop =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info(slaveStatusBeforeStop);
// STOP SLAVE
LOG.info("Stopping slave");
slaveStatement.execute("stop slave");
LOG.info("slave status after stopping replication");
SlaveStatus slaveStatusAfterStop =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info(slaveStatusAfterStop);
if (currentMasterConfig != null) {
String masterHost = currentMasterConfig.getHostName();
String masterMysqlPort = currentMasterConfig.getPort();
try {
masterConnection =
DriverManager.getConnection(
"jdbc:mysql://" + masterHost + ":" + masterMysqlPort + "", user, password);
masterStatement = masterConnection.createStatement();
// FETCH MASTER STATUS
LOG.info("master status");
MasterStatus masterStatus =
new MasterStatus(masterStatement.executeQuery("show master status"));
LOG.info(masterStatus);
String file = masterStatus.getString(MasterStatusAttribute.File);
String position = masterStatus.getString(MasterStatusAttribute.Position);
if (file != null && position != null) {
StringBuilder catchupQuery = new StringBuilder();
catchupQuery.append("START SLAVE UNTIL ").append("MASTER_LOG_FILE='").append(file)
.append("', master_log_pos=").append(position);
masterStatement.execute(catchupQuery.toString());
LOG.info("Successfully applied changes from last master");
SlaveStatus slaveStatusAfterCatchup =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
LOG.info("slave status after catch up");
LOG.info(slaveStatusAfterCatchup);
// catch up query returns after the changes are pulled into mysql relay log, wait until
// the sql thread applies all completed.
String relayMasterLogFile =
slaveStatusAfterCatchup.getString(SlaveStatusAttribute.Relay_Master_Log_File);
int readMasterLogPos =
slaveStatusAfterCatchup.getInt(SlaveStatusAttribute.Read_Master_Log_Pos);
int execMasterLogPos =
slaveStatusAfterCatchup.getInt(SlaveStatusAttribute.Exec_Master_Log_Pos);
StringBuilder waitQuery = new StringBuilder();
if (!file.equals(relayMasterLogFile) || execMasterLogPos != readMasterLogPos) {
waitQuery.append("select MASTER_POS_WAIT('").append(file).append("',")
.append(execMasterLogPos).append(")");
LOG.info("Running MASTER_POS_WAIT query since slave is not caught up completely with master. ");
LOG.info(String
.format(
"relayMasterLogFile=%s, masterLogFile=%s, readMasterLogPos=%s, execMasterLogPos=%s",
relayMasterLogFile, file, readMasterLogPos, execMasterLogPos));
slaveStatement.execute(waitQuery.toString());
SlaveStatus slaveStatus =
new SlaveStatus(slaveStatement.executeQuery("show slave status"));
relayMasterLogFile =
slaveStatus.getString(SlaveStatusAttribute.Relay_Master_Log_File);
readMasterLogPos = slaveStatus.getInt(SlaveStatusAttribute.Read_Master_Log_Pos);
execMasterLogPos = slaveStatus.getInt(SlaveStatusAttribute.Exec_Master_Log_Pos);
if (!file.equals(relayMasterLogFile) || execMasterLogPos != readMasterLogPos) {
LOG.error("Unable to catch up from previous master, some data might be lost. Continuing to become master.");
}
}
}
currentMasterConfig = null;
} catch (Exception e) {
LOG.error("Exception while running catchup phase", e);
} finally {
if (masterConnection != null) {
masterConnection.close();
}
}
}
} catch (Exception e) {
LOG.error(e);
} finally {
if (slaveConnection != null) {
try {
slaveConnection.close();
} catch (SQLException e) {
// ignore
}
}
}
}
}