package com.zendesk.maxwell.bootstrap; import com.zendesk.maxwell.*; import com.zendesk.maxwell.replication.BinlogPosition; import com.zendesk.maxwell.replication.Replicator; import com.zendesk.maxwell.producer.AbstractProducer; import com.zendesk.maxwell.row.RowMap; import com.zendesk.maxwell.row.RowMapBufferByTable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Queue; public class AsynchronousBootstrapper extends AbstractBootstrapper { static final Logger LOGGER = LoggerFactory.getLogger(Replicator.class); private Thread thread = null; private Queue<RowMap> queue = new LinkedList<>(); private RowMap bootstrappedRow = null; private RowMapBufferByTable skippedRows = null; private SynchronousBootstrapper synchronousBootstrapper = getSynchronousBootstrapper(); public AsynchronousBootstrapper( MaxwellContext context ) throws IOException { super(context); skippedRows = new RowMapBufferByTable(); } protected SynchronousBootstrapper getSynchronousBootstrapper( ) { return new SynchronousBootstrapper(context); } @Override public boolean shouldSkip(RowMap row) throws SQLException, IOException { // The main replication thread skips rows of the currently bootstrapped // table and the tables that are queued for bootstrap. The bootstrap thread replays them at // the end of the bootstrap. If maxwell is stopped these skipped rows will be lost; // however, at next startup resume() restarts the bootstrap process from the // beginning which restores the consistency of the replication stream. if ( bootstrappedRow != null && haveSameTable(row, bootstrappedRow) ) { skippedRows.add(row); return true; } for ( RowMap queuedRow : queue ) { if ( haveSameTable(row, queuedRow) ) { skippedRows.add(row); return true; } } return false; } private boolean haveSameTable(RowMap row, RowMap bootstrapStartRow) { String databaseName = bootstrapDatabase(bootstrapStartRow); String tableName = bootstrapTable(bootstrapStartRow); return row.getDatabase().equals(databaseName) && row.getTable().equals(tableName); } @Override public void startBootstrap(final RowMap bootstrapStartRow, final AbstractProducer producer, final Replicator replicator) throws Exception { if (thread == null) { bootstrappedRow = bootstrapStartRow; thread = new Thread(new Runnable() { @Override public void run() { try { synchronousBootstrapper.startBootstrap(bootstrapStartRow, producer, replicator); } catch ( NoSuchElementException e ) { LOGGER.warn(String.format("async bootstrapping cancelled for table %s.%s", bootstrapDatabase(bootstrapStartRow), bootstrapTable(bootstrapStartRow))); cancelBootstrap(bootstrapStartRow, producer, replicator); } catch ( Exception e ) { e.printStackTrace(); System.exit(1); } } }); thread.start(); } else { queueRow(bootstrapStartRow); } } private void queueRow(RowMap row) { queue.add(row); LOGGER.info(String.format("async bootstrapping: queued table %s.%s for bootstrapping", bootstrapDatabase(row), bootstrapTable(row))); } @Override public void completeBootstrap(RowMap bootstrapCompleteRow, AbstractProducer producer, Replicator replicator) throws Exception { String databaseName = bootstrapDatabase(bootstrapCompleteRow); String tableName = bootstrapTable(bootstrapCompleteRow); try { replaySkippedRows(databaseName, tableName, producer, bootstrapCompleteRow); synchronousBootstrapper.completeBootstrap(bootstrapCompleteRow, producer, replicator); LOGGER.info(String.format("async bootstrapping ended for %s.%s", databaseName, tableName)); } catch ( Exception e ) { e.printStackTrace(); System.exit(1); } finally { thread = null; bootstrappedRow = null; } if ( !queue.isEmpty() ) { startBootstrap(queue.remove(), producer, replicator); } } public void cancelBootstrap(RowMap bootstrapStartRow, AbstractProducer producer, Replicator replicator) { try { replaySkippedRows(bootstrapDatabase(bootstrapStartRow), bootstrapTable(bootstrapStartRow), producer, bootstrapStartRow); thread = null; bootstrappedRow = null; if ( !queue.isEmpty() ) { startBootstrap(queue.remove(), producer, replicator); } } catch ( Exception e ) { e.printStackTrace(); } } private void replaySkippedRows(String databaseName, String tableName, AbstractProducer producer, RowMap bootstrapCompleteRow) throws Exception { BinlogPosition bootstrapStartBinlogPosition = getBootstrapStartBinlogPosition(bootstrapCompleteRow); LOGGER.info("async bootstrapping: replaying " + skippedRows.size(databaseName, tableName) + " skipped rows..."); skippedRows.flushToDisk(databaseName, tableName); while ( skippedRows.size(databaseName, tableName) > 0 ) { RowMap row = skippedRows.removeFirst(databaseName, tableName); if ( bootstrapStartBinlogPosition == null || row.getPosition().getBinlogPosition().newerThan(bootstrapStartBinlogPosition) ) producer.push(row); } LOGGER.info("async bootstrapping: replay complete"); } private BinlogPosition getBootstrapStartBinlogPosition(RowMap bootstrapCompleteRow) throws SQLException { try ( Connection connection = context.getMaxwellConnection() ) { String sql = "select * from `bootstrap` where id = ?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setLong(1, ( Long ) bootstrapCompleteRow.getData("id")); ResultSet resultSet = preparedStatement.executeQuery(); if ( resultSet.next() ) { return new BinlogPosition(resultSet.getLong("binlog_position"), resultSet.getString("binlog_file")); } else { return null; } } } @Override public void resume(AbstractProducer producer, Replicator replicator) throws Exception { synchronousBootstrapper.resume(producer, replicator); } public void join() throws InterruptedException { if ( thread != null ) { thread.join(); } } @Override public boolean isRunning() { return thread != null || queue.size() > 0; } @Override public void work(RowMap row, AbstractProducer producer, Replicator replicator) throws Exception { if ( isStartBootstrapRow(row) ) { startBootstrap(row, producer, replicator); } else if ( isCompleteBootstrapRow(row) ) { completeBootstrap(row, producer, replicator); } } }