package com.zendesk.maxwell; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; import com.djdch.log4j.StaticShutdownCallbackRegistry; import com.zendesk.maxwell.bootstrap.AbstractBootstrapper; import com.zendesk.maxwell.metrics.MaxwellMetrics; import com.zendesk.maxwell.producer.AbstractProducer; import com.zendesk.maxwell.recovery.Recovery; import com.zendesk.maxwell.recovery.RecoveryInfo; 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.schema.MysqlPositionStore; import com.zendesk.maxwell.schema.MysqlSchemaStore; import com.zendesk.maxwell.schema.SchemaStoreSchema; import com.zendesk.maxwell.util.Logging; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.SQLException; public class Maxwell implements Runnable { static { Logging.setupLogBridging(); } protected MaxwellConfig config; protected MaxwellContext context; protected Replicator replicator; static final Logger LOGGER = LoggerFactory.getLogger(Maxwell.class); public Maxwell(MaxwellConfig config) throws SQLException { this.config = config; this.context = new MaxwellContext(this.config); this.context.probeConnections(); } public void run() { try { start(); } catch (Exception e) { LOGGER.error("maxwell encountered an exception", e); } } public void terminate() { this.context.terminate(); } private Position attemptMasterRecovery() throws Exception { Position recoveredPosition = null; MysqlPositionStore positionStore = this.context.getPositionStore(); RecoveryInfo recoveryInfo = positionStore.getRecoveryInfo(config); if ( recoveryInfo != null ) { Recovery masterRecovery = new Recovery( config.replicationMysql, config.databaseName, this.context.getReplicationConnectionPool(), this.context.getCaseSensitivity(), recoveryInfo, this.config.shykoMode ); recoveredPosition = masterRecovery.recover(); if (recoveredPosition != null) { // load up the schema from the recovery position and chain it into the // new server_id MysqlSchemaStore oldServerSchemaStore = new MysqlSchemaStore( context.getMaxwellConnectionPool(), context.getReplicationConnectionPool(), context.getSchemaConnectionPool(), recoveryInfo.serverID, recoveryInfo.position, context.getCaseSensitivity(), config.filter, false ); oldServerSchemaStore.clone(context.getServerID(), recoveredPosition); positionStore.delete(recoveryInfo.serverID, recoveryInfo.clientID, recoveryInfo.position); } } return recoveredPosition; } protected Position getInitialPosition() throws Exception { /* first method: do we have a stored position for this server? */ Position initial = this.context.getInitialPosition(); if (initial == null) { /* second method: are we recovering from a master swap? */ if ( config.masterRecovery ) initial = attemptMasterRecovery(); /* third method: capture the current master position. */ if ( initial == null ) { try ( Connection c = context.getReplicationConnection() ) { initial = Position.capture(c, config.gtidMode); } } if (initial != null) { /* if the initial position didn't come from the store, store it */ context.getPositionStore().set(initial); } } return initial; } public String getMaxwellVersion() { String packageVersion = getClass().getPackage().getImplementationVersion(); if ( packageVersion == null ) return "??"; else return packageVersion; } static String bootString = "Maxwell v%s is booting (%s), starting at %s"; private void logBanner(AbstractProducer producer, Position initialPosition) { String producerName = producer.getClass().getSimpleName(); LOGGER.info(String.format(bootString, getMaxwellVersion(), producerName, initialPosition.toString())); } protected void onReplicatorStart() {} private void start() throws Exception { MaxwellMetrics.setup(config, context); try { startInner(); } finally { this.context.terminate(); } } private void startInner() throws Exception { try ( Connection connection = this.context.getReplicationConnection(); Connection rawConnection = this.context.getRawMaxwellConnection() ) { MaxwellMysqlStatus.ensureReplicationMysqlState(connection); MaxwellMysqlStatus.ensureMaxwellMysqlState(rawConnection); if (config.gtidMode) { MaxwellMysqlStatus.ensureGtidMysqlState(connection); } SchemaStoreSchema.ensureMaxwellSchema(rawConnection, this.config.databaseName); try ( Connection schemaConnection = this.context.getMaxwellConnection() ) { SchemaStoreSchema.upgradeSchemaStoreSchema(schemaConnection); } } AbstractProducer producer = this.context.getProducer(); AbstractBootstrapper bootstrapper = this.context.getBootstrapper(); Position initPosition = getInitialPosition(); logBanner(producer, initPosition); this.context.setPosition(initPosition); MysqlSchemaStore mysqlSchemaStore = new MysqlSchemaStore(this.context, initPosition); mysqlSchemaStore.getSchema(); // trigger schema to load / capture before we start the replicator. if ( this.config.shykoMode ) this.replicator = new BinlogConnectorReplicator(mysqlSchemaStore, producer, bootstrapper, this.context, initPosition); else this.replicator = new MaxwellReplicator(mysqlSchemaStore, producer, bootstrapper, this.context, initPosition); bootstrapper.resume(producer, replicator); replicator.setFilter(context.getFilter()); context.setReplicator(replicator); this.context.start(); this.onReplicatorStart(); // Dropwizard throws an exception if you try to register multiple metrics with the same name. // Since there are codepaths that create multiple replicators (at least in the tests) we need to protect // against that. String lagGaugeName = MetricRegistry.name(MaxwellMetrics.getMetricsPrefix(), "replication", "lag"); if ( !(MaxwellMetrics.metricRegistry.getGauges().containsKey(lagGaugeName)) ) { MaxwellMetrics.metricRegistry.register( lagGaugeName, new Gauge<Long>() { @Override public Long getValue() { return replicator.getReplicationLag(); } } ); } replicator.runLoop(); Exception error = this.context.getError(); if (error != null) { throw error; } } public static void main(String[] args) { try { MaxwellConfig config = new MaxwellConfig(args); if ( config.log_level != null ) Logging.setLevel(config.log_level); final Maxwell maxwell = new Maxwell(config); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { maxwell.terminate(); StaticShutdownCallbackRegistry.invoke(); } }); maxwell.start(); } catch ( SQLException e ) { // catch SQLException explicitly because we likely don't care about the stacktrace LOGGER.error("SQLException: " + e.getLocalizedMessage()); LOGGER.error(e.getLocalizedMessage()); System.exit(1); } catch ( Exception e ) { e.printStackTrace(); System.exit(1); } } }