com.github.brandtg.switchboard.MysqlReplicator.java Source code

Java tutorial

Introduction

Here is the source code for com.github.brandtg.switchboard.MysqlReplicator.java

Source

/**
 * Copyright (C) 2015 Greg Brandt (brandt.greg@gmail.com)
 *
 * Licensed 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 com.github.brandtg.switchboard;

import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.management.ObjectName;

public class MysqlReplicator extends MysqlLogPuller {
    private static final Logger LOG = LoggerFactory.getLogger(MysqlReplicator.class);
    private static final Joiner SPACE = Joiner.on(" ");
    private static final String ENCODING = "UTF-8";

    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private final String jdbcString;
    private final String user;
    private final String password;

    private MysqlReplicationApplier applier;
    private PoolingDataSource<PoolableConnection> dataSource;

    public MysqlReplicator(String database, InetSocketAddress sourceAddress, InetSocketAddress sinkAddress,
            String jdbcString, String user, String password) {
        this(database, sourceAddress, sinkAddress, jdbcString, user, password, -1);
    }

    /**
     * Creates an agent to listen to MySQL changes (via binlog).
     *
     * @param database
     *  The MySQL database name
     * @param sourceAddress
     *  The switchboard server address from which to pull events
     * @param sinkAddress
     *  The local listener port to receive raw binlog data
     * @param lastIndex
     *  The last index that was processed by whoever is constructing this (if -1, processed none)
     */
    public MysqlReplicator(String database, InetSocketAddress sourceAddress, InetSocketAddress sinkAddress,
            String jdbcString, String user, String password, long lastIndex) {
        super(database, sourceAddress, sinkAddress, lastIndex);
        this.jdbcString = jdbcString;
        this.user = user;
        this.password = password;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("database", database).add("sourceAddress", sourceAddress)
                .add("sinkAddress", sinkAddress).add("jdbcString", jdbcString).add("user", user).toString();
    }

    @Override
    public void start() throws Exception {
        // DBCP2 pool
        ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(jdbcString, user, password);
        ObjectName poolName = new ObjectName(JdbcBasedLogIndex.class.getCanonicalName(),
                URLEncoder.encode(jdbcString, ENCODING), "replicatorConnectionPool");
        PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,
                poolName);
        ObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<PoolableConnection>(
                poolableConnectionFactory);
        poolableConnectionFactory.setPool(connectionPool);
        this.dataSource = new PoolingDataSource<PoolableConnection>(connectionPool);
        LOG.info("Opened connection pool to {} as {}", jdbcString, user);

        // Replication applier
        applier = new MysqlReplicationApplier(inputStream, dataSource);

        super.start();
    }

    @Override
    public void shutdown() throws Exception {
        super.shutdown();
        dataSource.close();
        applier.shutdown();
    }

    @Override
    protected Runnable getCallback() {
        return applier;
    }

    /** Main. */
    public static void main(String[] args) throws Exception {
        Options options = new Options();
        options.addOption("u", "user", true, "MySQL user");
        options.addOption("p", "password", true, "MySQL password");
        options.addOption("h", "host", true, "MySQL host");
        options.addOption("P", "port", true, "MySQL port");
        options.addOption("s", "sinkPort", true, "Local sink port");
        options.addOption("l", "lastIndex", true, "Last transaction ID");
        options.addOption("h", "help", false, "Prints help message");
        CommandLine commandLine = new GnuParser().parse(options, args);

        if (commandLine.getArgs().length < 2 || commandLine.hasOption("help")) {
            HelpFormatter helpFormatter = new HelpFormatter();
            helpFormatter.printHelp("usage: [opts] switchboardHost:port db1 [db2 ...]", options);
            System.exit(1);
        }

        // Switchboard host
        String[] hostPort = commandLine.getArgs()[0].split(":");
        InetSocketAddress source = new InetSocketAddress(hostPort[0], Integer.valueOf(hostPort[1]));
        InetSocketAddress sink = new InetSocketAddress(
                Integer.valueOf(commandLine.getOptionValue("sinkPort", "9090")));

        // Databases to replicate
        String[] databases = Arrays.copyOfRange(commandLine.getArgs(), 1, commandLine.getArgs().length);

        // JDBC params for local copy
        String user = commandLine.getOptionValue("user", "root");
        String password = commandLine.getOptionValue("password", "");
        String jdbcString = String.format("jdbc:mysql://%s:%d", commandLine.getOptionValue("host", "localhost"),
                Integer.valueOf(commandLine.getOptionValue("port", "3306")));
        Long lastIndex = Long.valueOf(commandLine.getOptionValue("lastIndex", "-1"));

        // Create replicators
        final List<MysqlReplicator> replicators = new ArrayList<>();
        for (String database : databases) {
            MysqlReplicator replicator = new MysqlReplicator(database, source, sink, jdbcString, user, password,
                    lastIndex);
            replicators.add(replicator);
        }

        // Shutdown hook
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                for (MysqlReplicator replicator : replicators) {
                    try {
                        replicator.shutdown();
                    } catch (Exception e) {
                        LOG.error("Could not shut down {}", replicator, e);
                    }
                }
            }
        });

        for (MysqlReplicator replicator : replicators) {
            replicator.start();
            LOG.info("Started {}", replicator);
        }
    }
}