MySQL binlog分析程序:Open Replicator

第0章:簡介

(1)下面是http://code.google.com中的binlog事件分析結構圖:


(2)獲取開源包的maven座標

        <!-- MySQL JAVA Slave Client -->
        <dependency>
            <groupId>com.google.code</groupId>
            <artifactId>open-replicator</artifactId>
            <version>1.0.5</version>
        </dependency>

(3)參考網站

開源項目首頁:http://code.google.com/p/open-replicator/

開源中國:http://www.oschina.net/p/open-replicator


(4)札記

1)Open Replicator是一個用Java編寫的MySQL binlog分析程序。Open Replicator 首先連接到MySQL(就像一個普通的MySQL Slave一樣),然後接收和分析binlog,最終將分析得出的binlog events以回調的方式通知應用。

2)Open Replicator可以被應用到MySQL數據變化的實時推送,多Master到單Slave的數據同步等多種應用場景。

3)在程序結構上,最主要的設計原則是高性能,低內存佔用。Open Replicator目前只支持MySQL5.0及以上版本。


第1章:實踐

(1)包裝Open Replicator類(AutoOpenReplicator.java)

package com.mcc.core.openReplicator;

import com.google.code.or.OpenReplicator;
import com.google.code.or.common.glossary.column.StringColumn;
import com.google.code.or.net.Packet;
import com.google.code.or.net.Transport;
import com.google.code.or.net.impl.packet.EOFPacket;
import com.google.code.or.net.impl.packet.ResultSetRowPacket;
import com.google.code.or.net.impl.packet.command.ComQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * MySQL binlog分析程序 ,用到open-replicator包
 * 增加加自動配置binlog位置及重連機制
 *
 * @author <a href="mailto:[email protected]">menergy</a>
 *         DateTime: 13-12-26  下午2:22
 */
public class AutoOpenReplicator extends OpenReplicator {
    // members
    private static Logger logger = LoggerFactory.getLogger(AutoOpenReplicator.class);

    private boolean autoReconnect = true;
    // timeout auto reconnect , default 30 second
    private int delayReconnect = 30;
    // default timeout is 60 second, after timeout will be reconnect!
    private int defaultTimeout = 120 * 1000;
    // COM Query Transport
    private Transport comQueryTransport;
    // static block

    // constructors

    // properties

    /**
     * 是否自動重連
     *
     * @return 自動重連
     */
    public boolean isAutoReconnect() {
        return autoReconnect;
    }

    /**
     * 設置自動重連
     *
     * @param autoReconnect 自動重連
     */
    public void setAutoReconnect(boolean autoReconnect) {
        this.autoReconnect = autoReconnect;
    }

    /**
     * 斷開多少秒後進行自動重連
     *
     * @param delayReconnect 斷開後多少秒
     */
    public void setDelayReconnect(int delayReconnect) {
        this.delayReconnect = delayReconnect;
    }

    /**
     * 斷開多少秒後進行自動重連
     *
     * @return 斷開後多少秒
     */
    public int getDelayReconnect() {
        return delayReconnect;
    }

    // public methods

    // protected methods

    @Override
    public void start() {
        do {
            try {
                long current = System.currentTimeMillis();
                if (!this.isRunning()) {
                    if (this.getBinlogFileName() == null) updatePosition();
                    logger.info("Try to startup dump binlog from mysql master[{}, {}] ...", this.binlogFileName, this.binlogPosition);
                    this.reset();
                    super.start();
                    logger.info("Startup successed! After {} second if nothing event fire will be reconnect ...", defaultTimeout / 1000);
                } else {
                    if (current - this.lastAlive >= this.defaultTimeout) {
                        this.stopQuietly(0, TimeUnit.SECONDS);
                    }
                }
                TimeUnit.SECONDS.sleep(this.getDelayReconnect());
            } catch (Exception e) {
                if (logger.isErrorEnabled()) {
                    logger.error("connect mysql failure!", e);
                }
                // reconnect failure, reget last binlog & position from master node and update cache!
                //LoadCenter.loadAll(); // just update all cache, not flush!
                updatePosition();
                try {
                    TimeUnit.SECONDS.sleep(this.getDelayReconnect());
                } catch (InterruptedException ignore) {
                    // NOP
                }
            }
        } while (this.autoReconnect);
    }

    @Override
    public void stopQuietly(long timeout, TimeUnit unit) {
        super.stopQuietly(timeout, unit);
        if (this.getBinlogParser() != null) {
            // 重置, 當MySQL服務器進行restart/stop操作時進入該流程
            this.binlogParser.setParserListeners(null); // 這句比較關鍵,不然會死循環
        }
    }

    // friendly methods

    // private methods

    /**
     * 自動配置binlog位置
     */
    private void updatePosition() {
        // 配置binlog位置
        try {
            ResultSetRowPacket binlogPacket = query("show master status");
            if (binlogPacket != null) {
                List<StringColumn> values = binlogPacket.getColumns();
                this.setBinlogFileName(values.get(0).toString());
                this.setBinlogPosition(Long.valueOf(values.get(1).toString()));
            }
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error("update binlog position failure!", e);
            }
        }
    }

    /**
     * ComQuery 查詢
     *
     * @param sql 查詢語句
     * @return
     */
    private ResultSetRowPacket query(String sql) throws Exception {
        ResultSetRowPacket row = null;
        final ComQuery command = new ComQuery();
        command.setSql(StringColumn.valueOf(sql.getBytes()));
        if (this.comQueryTransport == null) this.comQueryTransport = getDefaultTransport();
        this.comQueryTransport.connect(this.host, this.port);
        this.comQueryTransport.getOutputStream().writePacket(command);
        this.comQueryTransport.getOutputStream().flush();
        // step 1
        this.comQueryTransport.getInputStream().readPacket();
        //
        Packet packet;
        // step 2
        while (true) {
            packet = comQueryTransport.getInputStream().readPacket();
            if (packet.getPacketBody()[0] == EOFPacket.PACKET_MARKER) {
                break;
            }
        }
        // step 3
        while (true) {
            packet = comQueryTransport.getInputStream().readPacket();
            if (packet.getPacketBody()[0] == EOFPacket.PACKET_MARKER) {
                break;
            } else {
                row = ResultSetRowPacket.valueOf(packet);
            }
        }
        this.comQueryTransport.disconnect();
        return row;
    }

    private void reset() {
        this.transport = null;
        this.binlogParser = null;
    }
    // inner class

    // test main
}


(2)事件監聽器模板類(NotificationListener.java)

package com.mcc.core.openReplicator;

import com.google.code.or.binlog.BinlogEventListener;
import com.google.code.or.binlog.BinlogEventV4;
import com.google.code.or.binlog.impl.event.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Binlog事件監聽器模板
 *
 * @author <a href="mailto:[email protected]">menergy</a>
 *         DateTime: 13-12-26  下午2:34
 */
public class NotificationListener implements BinlogEventListener {
    private static Logger logger = LoggerFactory.getLogger(NotificationListener.class);
    private String eventDatabase;

    /**
     * 這裏只是實現例子,該方法可以自由處理邏輯
     * @param event
     */
    @Override
    public void onEvents(BinlogEventV4 event) {
        Class<?> eventType = event.getClass();
        // 事務開始
        if (eventType == QueryEvent.class) {
            QueryEvent actualEvent = (QueryEvent) event;
            this.eventDatabase = actualEvent.getDatabaseName().toString();

            //TODO,這裏可以獲取事件數據庫信息,可做其它邏輯處理
            logger.info("事件數據庫名:{}",eventDatabase);

            return;
        }

        // 只監控指定數據庫
        if (eventDatabase != null && !"".equals(eventDatabase.trim())) {
            if (eventType == TableMapEvent.class) {

                TableMapEvent actualEvent = (TableMapEvent) event;
                long tableId = actualEvent.getTableId();
                String tableName = actualEvent.getTableName().toString();

                //TODO,這裏可以獲取事件表信息,可做其它邏輯處理
                logger.info("事件數據表ID:{}, 事件數據庫表名稱:{}",tableId, tableName);

            } else if (eventType == WriteRowsEvent.class) { // 插入事件

                WriteRowsEvent actualEvent = (WriteRowsEvent) event;
                long tableId = actualEvent.getTableId();

                //TODO,這裏可以獲取寫行事件信息,可做其它邏輯處理
                logger.info("寫行事件ID:{}",tableId);

            } else if (eventType == UpdateRowsEvent.class) { // 更新事件

                UpdateRowsEvent actualEvent = (UpdateRowsEvent) event;
                long tableId = actualEvent.getTableId();

                //TODO,這裏可以獲取更新事件信息,可做其它邏輯處理
                logger.info("更新事件ID:{}",tableId);

            } else if (eventType == DeleteRowsEvent.class) {// 刪除事件

                DeleteRowsEvent actualEvent = (DeleteRowsEvent) event;
                long tableId = actualEvent.getTableId();

                //TODO,這裏可以獲取刪除事件信息,可做其它邏輯處理
                logger.info("刪除事件ID:{}",tableId);

            } else if (eventType == XidEvent.class) {// 結束事務
                XidEvent actualEvent = (XidEvent) event;
                long xId = actualEvent.getXid();

                //TODO,這裏可以獲取結束事件信息,可做其它邏輯處理
                logger.info("結束事件ID:{}",xId);

            }
        }

    }
}


(3)MySQL binlog分析程序測試類(OpenReplicatorTest.java)

package com.mcc.core.test;

import com.mcc.core.openReplicator.AutoOpenReplicator;
import com.mcc.core.openReplicator.NotificationListener;

/**
 * MySQL binlog分析程序測試
 *
 * @author <a href="mailto:[email protected]">menergy</a>
 *         DateTime: 13-12-26  下午2:26
 */
public class OpenReplicatorTest {
    public static void main(String args[]){
        // 配置從MySQL Master進行復制
        final AutoOpenReplicator aor = new AutoOpenReplicator();
        aor.setServerId(100001);
        aor.setHost("192.168.1.1");
        aor.setUser("admin");
        aor.setPassword("123456");
        aor.setAutoReconnect(true);
        aor.setDelayReconnect(5);
        aor.setBinlogEventListener(new NotificationListener());
        aor.start();
    }
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章