Canl用法 包含Springboot配置

一、環境準備
準備兩個MySQL 5.7的數據庫,一個作爲源數據庫(A庫),一個作爲目標數據庫(B庫)。
源數據庫需要開啓binlog,設置binlog_format爲ROW模式。登錄MySQL,編輯my.cnf文件(位於/etc/my.cnf或/etc/mysql/my.cnf):

[mysqld]
log-bin=mysql-bin  # 開啓binlog
binlog-format=ROW  # 設置binlog_format爲ROW模式
server-id=1        # 設置服務器ID
保存後,重啓MySQL服務。

二、安裝Canal
從Canal的Github頁面下載源代碼:https://github.com/alibaba/canal ,也可以用Git克隆到本地:
git clone https://github.com/alibaba/canal.git
進入到下載的源代碼目錄,執行mvn clean install -Dmaven.test.skip進行編譯。
編譯完成後,會在canal.deployer/target目錄下生成canal.deployer-版本號.zip文件,解壓到你想要安裝的目錄,如/usr/local/canal/。

三、配置Canal
編輯Canal的配置文件/usr/local/canal/conf/example/instance.properties,將以下內容替換爲你的實際配置
canal.instance.master.address = 數據源地址:端口
canal.instance.dbUsername = 數據源用戶名
canal.instance.dbPassword = 數據源密碼
canal.instance.defaultDatabaseName = 數據庫名
canal.instance.filter.regex = 數據庫名.表名
配置Canal連接MySQL的賬號權限,確保具備以下權限:SELECT,RELOAD,SHOW DATABASES,REPLICATION SLAVE,REPLICATION CLIENT等。

四、啓動Canal服務
進入到Canal的bin目錄:cd /usr/local/canal/bin/
運行startup.sh腳本啓動Canal服務:sh startup.sh
使用ps -ef | grep canal命令,確認Canal服務已經啓動。

五、編寫數據同步程序
以Java爲例,使用Canal的Java客戶端庫,編寫數據同步程序。以下是一個簡單的示例:
```java
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import java.net.InetSocketAddress;
import java.util.List;

public class CanalClient {
    public static void main(String[] args) {
        // 創建Canal連接
        CanalConnector connector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("Canal服務地址", 11111), "example", "", "");

        // 連接Canal服務
        connector.connect();

        // 訂閱數據表
        connector.subscribe("數據庫名.表名");

        // 循環讀取數據變更消息
        while (true) {
            // 獲取1000條數據變更消息
            Message message = connector.getWithoutAck(1000);
            long batchId = message.getId();
            if (batchId == -1 || message.getEntries().isEmpty()) {
                continue;
            }

            printEntry(message.getEntries());

            // 確認消息
            connector.ack(batchId);
        }
    }

    private static void printEntry(List<CanalEntry.Entry> entries) {
        for (CanalEntry.Entry entry : entries) {
            if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
                // 解析數據變更消息
                CanalEntry.RowChange rowChange;
                try {
                    rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                } catch (Exception e) {
                    throw new RuntimeException("ERROR ## parser of eromanga-event has an error, data:" + entry.toString(), e);
                }

                CanalEntry.EventType eventType = rowChange.getEventType();
                String tableName = entry.getHeader().getTableName();

                System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s",
                        entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                        entry.getHeader().getSchemaName(), tableName, eventType));

                // 打印行變更信息
                for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                    if (eventType == CanalEntry.EventType.DELETE) {
                        printColumn(rowData.getBeforeColumnsList());
                    } else if (eventType == CanalEntry.EventType.INSERT) {
                        printColumn(rowData.getAfterColumnsList());
                    } else {
                        System.out.println("-------> before");
                        printColumn(rowData.getBeforeColumnsList());
                        System.out.println("-------> after");
                        printColumn(rowData.getAfterColumnsList());
                    }
                }
            }
        }
    }

    private static void printColumn(List<CanalEntry.Column> columns) {
        for (CanalEntry.Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }
}

**如果你正在使用Spring Boot,可以按照以下步驟編寫Canal數據同步程序:**
添加Canal客戶端庫的依賴。在pom.xml文件中添加以下依賴:
<dependencies>
    <!-- Canal客戶端依賴 -->
    <dependency>
        <groupId>com.alibaba.otter</groupId>
        <artifactId>canal.client</artifactId>
        <version>1.1.5</version>
    </dependency>
</dependencies>

創建一個CanalClient類,作爲數據同步的入口。可以參考之前給出的示例代碼。這個類可以是一個Spring Bean,可以加上@Component註解。
在CanalClient類中注入DataSource或者JdbcTemplate,用於連接目標數據庫B庫。可以使用Spring Boot的自動配置來簡化數據源的配置。
在CanalClient類中編寫數據處理邏輯,將變更數據同步到目標數據庫B庫。可以使用JdbcTemplate或其他ORM框架來執行SQL語句。
以下是一個示例的CanalClient類的代碼:

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.net.InetSocketAddress;
import java.util.List;

[@Component](https://my.oschina.net/u/3907912)
public class CanalClient {
    @Autowired
    private DataSource dataSource; // 注入目標數據庫B庫的數據源

    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void init() {
        jdbcTemplate = new JdbcTemplate(dataSource);
        start();
    }

    public void start() {
        // 創建Canal連接
        CanalConnector connector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("Canal服務地址", 11111), "example", "", "");

        // 連接Canal服務
        connector.connect();

        // 訂閱數據表
        connector.subscribe("數據庫名.表名");

        // 循環讀取數據變更消息
        while (true) {
            // 獲取1000條數據變更消息
            Message message = connector.getWithoutAck(1000);
            long batchId = message.getId();
            if (batchId == -1 || message.getEntries().isEmpty()) {
                continue;
            }

            processEntry(message.getEntries());

            // 確認消息
            connector.ack(batchId);
        }
    }

    private void processEntry(List<CanalEntry.Entry> entries) {
        for (CanalEntry.Entry entry : entries) {
            if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
                // 解析數據變更消息
                CanalEntry.RowChange rowChange;
                try {
                    rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                } catch (Exception e) {
                    throw new RuntimeException("ERROR ## parser of eromanga-event has an error, data:" + entry.toString(), e);
                }

                CanalEntry.EventType eventType = rowChange.getEventType();

                // 處理不同類型的數據變更
                if (eventType == CanalEntry.EventType.DELETE) {
                    processDeleteEvent(entry.getHeader().getTableName(), rowChange.getRowDatasList());
                } else if (eventType == CanalEntry.EventType.INSERT) {
                    processInsertEvent(entry.getHeader().getTableName(), rowChange.getRowDatasList());
                } else if (eventType == CanalEntry.EventType.UPDATE) {
                    processUpdateEvent(entry.getHeader().getTableName(), rowChange.getRowDatasList());
                }
            }
        }
    }

    private void processDeleteEvent(String tableName, List<CanalEntry.RowData> rowDatas) {
        // 處理刪除事件
        for (CanalEntry.RowData rowData : rowDatas) {
            // 構造SQL語句,執行刪除操作
            String sql = "DELETE FROM " + tableName + " WHERE id = ?";
            jdbcTemplate.update(sql, getColumnValue(rowData.getBeforeColumnsList(), "id"));
        }
    }

    private void processInsertEvent(String tableName, List<CanalEntry.RowData> rowDatas) {
        // 處理插入事件
        for (CanalEntry.RowData rowData : rowDatas) {
            // 構造SQL語句,執行插入操作
            String sql = "INSERT INTO " + tableName + " (id, name) VALUES (?, ?)";
            jdbcTemplate.update(sql, getColumnValue(rowData.getAfterColumnsList(), "id"), getColumnValue(rowData.getAfterColumnsList(), "name"));
        }
    }

    private void processUpdateEvent(String tableName, List<CanalEntry.RowData> rowDatas) {
        // 處理更新事件
        for (CanalEntry.RowData rowData : rowDatas) {
            // 構造SQL語句,執行更新操作
            String sql = "UPDATE " + tableName + " SET name = ? WHERE id = ?";
            jdbcTemplate.update(sql, getColumnValue(rowData.getAfterColumnsList(), "name"), getColumnValue(rowData.getBeforeColumnsList(), "id"));
        }
    }

    private String getColumnValue(List<CanalEntry.Column> columns, String columnName) {
        for (CanalEntry.Column column : columns) {
            if (column.getName().equalsIgnoreCase(columnName)) {
                return column.getValue();
            }
        }
        return null;
    }
}
這樣,你可以啓動Spring Boot應用程序,CanalClient會自動連接到Canal服務,並實時同步變更數據到目標數據庫B庫。
請注意替換示例代碼中的"Canal服務地址"、"數據庫名.表名"等佔位符爲實際的值。此外,需要根據具體的表結構和業務邏輯調整SQL語句和處理邏輯。

六、運行同步程序
將編寫好的同步程序部署到服務器,並運行。至此,當源數據庫的表發生變化時,Canal會將這些變化實時地同步到目標數據庫。
注意:實際的數據同步操作需要你自行編寫,包括連接目標數據庫,創建相應的SQL語句,並執行這些SQL語句。這需要你對Java的JDBC API或者其他ORM框架(如MyBatis、Hibernate等)有一定了解。這部分內容涉及的知識點較多,無法在這裏一一詳述。如果需要,你可以查閱相關的開發文檔或教程,學習如何使用這些API或框架進行數據庫操作。
當使用Spring Boot時,你可以按照以下步驟將歷史數據同步到目標數據庫B庫中:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;

@Component
public class HistoryDataSync {
    @Autowired
    private DataSource dataSource; // 注入目標數據庫B庫的數據源

    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void init() {
        jdbcTemplate = new JdbcTemplate(dataSource);
        syncHistoryData();
    }

    public void syncHistoryData() {
        // 獲取歷史數據
        List<Map<String, Object>> historyData = getHistoryDataFromSourceDB();

        // 插入歷史數據
        insertDataToTargetDB(historyData);
    }

    private List<Map<String, Object>> getHistoryDataFromSourceDB() {
        // 查詢源數據庫A庫中的歷史數據
        String sql = "SELECT * FROM source_table";
        return jdbcTemplate.queryForList(sql);
    }

    private void insertDataToTargetDB(List<Map<String, Object>> historyData) {
        // 插入歷史數據到目標數據庫B庫的相應表中
        String sql = "INSERT INTO target_table (column1, column2, ...) VALUES (?, ?, ...)";
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Map<String, Object> data = historyData.get(i);
                ps.setObject(1, data.get("column1"));
                ps.setObject(2, data.get("column2"));
                // 設置其他列的值
            }

            @Override
            public int getBatchSize() {
                return historyData.size();
            }
        });
    }
}

在上面的示例代碼中,getHistoryDataFromSourceDB()方法用於從源數據庫A庫中獲取歷史數據。你需要根據實際的表結構和查詢條件編寫相應的SQL語句。
insertDataToTargetDB()方法用於將歷史數據插入到目標數據庫B庫的相應表中。使用jdbcTemplate.batchUpdate()方法批量插入數據,通過實現BatchPreparedStatementSetter接口來設置插入SQL語句的參數。
請確保替換示例代碼中的表名、列名等佔位符爲實際的值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章