canal同步mysql數據到es、oracle、mq、redis和mysql中

前言:

mysql數據的同步只能解決數據結構相同的數據同步,假如我想把mysql中的數據變更同步到oracle、es、mq、redis或者說同樣是mysql但是表結構不同的數據庫中,mysql自帶的主從功能已經滿足不了這種需求了;阿里巴巴推出了一個開源框架canal 就能解決這類問題。

環境:

1、canal.deployer:1.1.4
2、canal.adapter:1.1.4
3、elasticsearch:6.7.2 (1.1.4canal不支持7.x的es,支持的es版本爲 6.4.3-6.8)
4、mysql:5.7
5、jdk:1.8

注意:本文主要是講解canal,所以默認已經有一點es基礎,並且es、es的可視化工具和mysql已經安裝好了。。。

準備操作:

一、canal安裝:

下載地址: https://github.com/alibaba/canal/releases

說明:這裏需要下載的是:
1、canal.deployer1.1.4版本- - - 可以理解爲相當於canal的服務端
2、canal.adapter1.1.4版本- - - 可以理解爲相當於canal的插件
3、最新的是1.1.5版本,但是是快照版,我們這裏還是選擇穩定版本

二、mysql修改

1、配置文件
[mysqld]
log-bin=mysql-bin #開啓 binlog
binlog-format=ROW # 選擇 ROW 模式(不瞭解的可以看下博主之前的文章
server_id=1 # 不要和 canal 的 slaveId 重複就行

注意:
1、mac下還需要修改:bind-address = 127.0.0.1->bind-address = 0.0.0.0
2、修改完配置文件一定要記得重啓mysql

2、創建一個可以遠程連接的用戶

CREATE USER canal IDENTIFIED BY 'canal';  
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;

三、canal-deployer修改:

1、打開deployer/conf/example

2、刪除除instance.properties以外的所有東西(如果不是新下載的)

3、修改配置文件:instance.properties:

## mysql serverId , v1.0.26+ will autoGen   
# 這個東西可以不設置,如果要設置別和上面mysql配置文件中的值重複就行
# canal.instance.mysql.slaveId=0
canal.instance.master.address=mysql啓動的ip:端口  #例如:192.168.34.66:3306
# username/password
canal.instance.dbUsername=canal   # 剛纔自己創建賬號
canal.instance.dbPassword=canal   # 剛纔自己創建密碼
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
# 這個可以根據需要修改過濾,默認是直接監聽所有
canal.instance.filter.regex=.*\\..*    #例如:canal_tsdb.test_table1

四、啓動canal-deployer

linux/mac:切換到deployer/bin目錄下 sh startup.sh 即可
windows:切換到deployer/bin目錄下雙擊startup.bat 即可

數據同步一:java代碼實現

1、創建一個maven工程,添加以下依賴

        <dependency>
            <groupId>com.alibaba.otter</groupId>
            <artifactId>canal.client</artifactId>
            <version>1.1.4</version>
        </dependency>

2、編寫java代碼,獲取mysql中數據的變化(這段代碼canal的github上有)

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.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;


public class SimpleCanalClientExample {
    public static void main(String args[]) {
        // 創建鏈接
        //192.168.35.254爲canal-deployer啓動的ip地址
        //canal-deployer如果你沒有修改過的話,默認端口是11111
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.35.254", 11111), "example", "", "");
        //底層默認是1000
        int batchSize = 1;
        try {
            connector.connect();
            connector.subscribe(".*\\..*");
            // connector.subscribe("canal_tsdb.test_table1");
            //把上次停止後未提交的數據回滾,因爲不確定是否已處理
            connector.rollback();
            while (true) {
                System.out.println("當前時間爲:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                //每次獲取多少條數據
                Message message = connector.getWithoutAck(batchSize);
                //System.out.println("內容爲:"+ JSON.toJSONString(message));
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                    }
                } else {
                    try {
                        System.out.println(batchId);
                        printEntry(message.getEntries());
                        // int i=1/0;
                        connector.ack(batchId); // 提交確認
                    } catch (Exception e) {
                        connector.rollback(batchId); // 處理失敗, 回滾數據
                    }
                }
            }
        } finally {
            connector.disconnect();
        }
    }
    private static void printEntry(List<Entry> entrys) {
        System.out.println("讀取長度爲:" + entrys.size());
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }
            RowChange rowChage = null;
            try {
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                        e);
            }
            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                    eventType));
            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.DELETE) {
                    printColumn(rowData.getBeforeColumnsList());
                } else if (eventType == EventType.INSERT) {
                    printColumn(rowData.getAfterColumnsList());
                } else {
                    System.out.println("-------&gt; before");
                    printColumn(rowData.getBeforeColumnsList());
                    System.out.println("-------&gt; after");
                    printColumn(rowData.getAfterColumnsList());
                }
            }
        }
    }
    private static void printColumn(List<Column> columns) {
        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }
}

3、運行之後,只要我們修改mysql中的數據,就會有東西打印出來,如圖所示:
在這裏插入圖片描述
4、分析: 上圖可以看到,其實在java代碼中已經能拿到mysql中數據的變化,可以具體到那個數據源,那張表,那條記錄,更新的甚至都能知道更新前和更新後分別是什麼

5、思考: 個人覺得這種方式比較靈活,在java代碼中拿到了數據源,表,字段,具體什麼操作等信息之後,如果想同步數據,不是分分鐘的事情,無論你是es,oracle,mq,redis,mysql還是什麼hbase,只要java能連的東西,都能同步數據,但是這種方式同樣也有缺點,就是主庫只要一發生變化,從庫就要執行相同的操作,而且是用java去操作對應的備份單元,這樣就要求從庫和主庫的性能不能差太多。。。

數據同步二:mysql同步到myql中

1、準備兩個mysql數據庫

2、主從庫的賬號密碼都得保證能遠程連接

3、注意: mysql版本不能是8.x,目前測出來,canal1.1.4不支持mysql8.x

4、修改canal-adapter/conf/application.yml配置文件:

canal.conf:
  mode: tcp
 # 修改成自己canal-deployer啓動的ip和端口
  canalServerHost: 192.168.35.254:11111
  batchSize: 500
  syncBatchSize: 1000
  retries: 0
  timeout:
  accessKey:
  secretKey:
  srcDataSources:
    defaultDS:
      # 換成自己mysql主庫的地址和數據源
      url: jdbc:mysql://192.168.34.66:3306/canal_tsdb?useUnicode=true
      username: canal  # 換成自己mysql主庫的賬號
      password: canal  #  換成自己mysql主庫的密碼
  canalAdapters:
  - instance: example
    groups:
    - groupId: g1
      outerAdapters:
       - name: logger
       - name: rdb
         key: mysql1
         properties:
           jdbc.driverClassName: com.mysql.jdbc.Driver
           jdbc.url: jdbc:mysql://從庫ip:3306/從庫數據源?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
           jdbc.username: 從庫賬號
           jdbc.password: 從庫密碼

5、修改canal-adapter/conf/rdb/mytest_user.yml配置文件:

dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: mysql1
concurrent: true
dbMapping:
   #配置數據庫--主庫
   database: canal_tsdb
   #配置表--主庫
   table: test1
   #從庫
   targetTable: bqorderdb.test2
   targetPk:
     idid: id
   #如果兩張表的結構是一樣的話,直接設置爲true
   #mapAll: true
   targetColumns: 
   #從表字段名字: 主表字段名字
    idid: id
    name: name
    address: address

6、啓動canal-adapter:

linux/mac:切換到adapter/bin目錄下 sh startup.sh 即可
windows:切換到adapter/bin目錄下雙擊startup.bat 即可

7、效果: 主庫canal_tsdb下的test1表和從庫下bqorderdb下的test2表實現了同步

8、注意:

1、canal-adapter/conf/rdb/mytest_user.yml配置文件修改的時候,需要注意主庫和從庫之間的表結構的問題,如果相同,可以直接配置mapAll: true,如果不一樣,則需要配置targetColumns,格式爲 從表字段名字: 主表字段名字

2、canal-adapter/conf/rdb/mytest_user.yml的groupId應該和canal-adapter/conf/application.yml中的保持一致

9、思考: 如果需要同時同步多張表怎麼辦,或者說來自不同數據源的表?

10、解決: 在canal-adapter/conf/rdb/mytest_user.yml和canal-adapter/conf/application.yml中再增加一個groupId即可

canal.conf:
  mode: tcp
  canalServerHost: 192.168.35.254:11111
  batchSize: 500
  syncBatchSize: 1000
  retries: 0
  timeout:
  accessKey:
  secretKey:
  srcDataSources:
    defaultDS:
      url: jdbc:mysql://192.168.34.66:3306/canal_tsdb?useUnicode=true
      username: canal
      password: canal
  canalAdapters:
  - instance: example
    groups:
    - groupId: g1
      outerAdapters:
       - name: logger
       - name: rdb
         key: mysql1
         properties:
           jdbc.driverClassName: com.mysql.jdbc.Driver
           jdbc.url: jdbc:mysql://從庫ip:3306/從庫數據源?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
           jdbc.username: 從庫賬號
           jdbc.password: 從庫密碼
    - groupId: g2
      outerAdapters:
       - name: logger
       - name: rdb
         key: mysql1
         properties:
           jdbc.driverClassName: com.mysql.jdbc.Driver
           jdbc.url: jdbc:mysql://從庫ip:3306/從庫數據源?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
           jdbc.username: 從庫賬號
           jdbc.password: 從庫密碼
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: mysql1
concurrent: true
dbMapping:
   #配置數據庫--主庫
   database: canal_tsdb
   #配置表--主庫
   table: test1
   #從庫
   targetTable: bqorderdb.test2
   targetPk:
     idid: id
   #如果兩張表的結構是一樣的話,直接設置爲true
   #mapAll: true
   targetColumns: 
   #從表字段名字: 主表字段名字
    idid: id
    name: name
    address: address
groupId: g2
outerAdapterKey: mysql1
concurrent: true
dbMapping:
   #配置數據庫--主庫
   database: canal_tsdb
   #配置表--主庫
   table: canal_test
   #從庫
   targetTable: bqorderdb.test3
   targetPk:
     idid: id
   #如果兩張表的結構是一樣的話,直接設置爲true
   #mapAll: true
   targetColumns: 
   #從表字段名字: 主表字段名字
    idid: id
    name: name
    address: address

數據同步三:mysql同步到es中

1、準備一個mysql數據庫

2、準備一個es,但是版本不能低於6.4.3也不能高於6.8

3、創建一個index、type和mappings

4、修改canal-adapter/conf/application.yml配置文件:

canal.conf:
  mode: tcp
  canalServerHost: 192.168.35.254:11111
  batchSize: 500
  syncBatchSize: 1000
  retries: 0
  timeout:
  accessKey:
  secretKey:
  srcDataSources:
    defaultDS:
      url: jdbc:mysql://192.168.34.66:3306/canal_tsdb?useUnicode=true
      username: canal
      password: canal
  canalAdapters:
  - instance: example
    groups:
    - groupId: g1
      outerAdapters:
       - name: logger
       - name: es
         hosts: es啓動的ip地址:9300
         properties:
           cluster.name: elasticsearch

5、修改canal-adapter/conf/test1.yml配置文件:

dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
  _index: test1
  _type: _doc
  _id: _id
  upsert: true
  sql: "select a.id as _id,a.name,a.address from canal_tets a"
  commitBatch: 3000

6、注意:

1、canal-adapter/conf/rdb/mytest_user.yml的groupId應該和canal-adapter/conf/application.yml中的保持一致

2、在創建es的index的時候,如果是7以下的版本,還是需要創建type的,這裏用的es版本是6.7.2,所以也是需要創建的

3、es同步的主要關鍵在於這句sql,他能把mysql表中的字段映射到es的field上,要映射幾個字段就查幾個字段

7、效果: 我們可以看到mysql中操作一條數據,es中也會對應的變更
在這裏插入圖片描述
在這裏插入圖片描述

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