大数据|Canal 快速入门指南

187 阅读2分钟

Canal 是阿里开源的框架,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

image.png

Canal的工作原理

把自己伪装成MySQL slave,模拟MySQL slave的交互协议向MySQL Mater发送 dump协议,MySQL mater收到canal发送过来的dump请求,开始推送bin_log给canal,然后canal解析bin_log,再发送到存储目的地,比如MySQL,Kafka,Elastic Search等等。

Canal的适用场景

  • 数据库实时备份和维护
  • 带业务逻辑的增量数据处理

本地启动 MySQL,并修改对应的配置

下面就是对应的 docker-compose.yaml

version: '2'

services:
  mysql:
    image: mariadb:10.3.34
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: mysql
      MYSQL_PASSWORD: mysql
      MYSQL_DATABASE: test
      MYSQL_BIND_ADDRESS: 0.0.0.0
      TZ: Asia/Shanghai
    command:
      - --lower_case_table_names=1
      - --character-set-server=utf8
    ports:
      - 3306:3306
    volumes: ## 这里挂载目录修改为自己的目录
      - "/data/docker-script/mariadb_data:/var/lib/mysql"

修改 /etc/mysql/my.cnf 配置

server-id=1
log_bin=mysql-bin
binlog_format=row

重启 mysql

docker restart {mysql container id}

创建 canal 用户,并赋予权限

create user 'canal'@'%' identified by 'Canal@123456';
grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'canal'@'%' identified by 'Canal@123456';

本地启动 Canal Server

拉取 canal-server 镜像

docker pull canal/canal-server:1.1.4

启动

构建一个destination name为test的队列

docker run -d -it -h 127.0.0.1 -e canal.auto.scan=false \
 -e canal.destinations=test \
 -e canal.instance.master.address=127.0.0.1:3306 \
 -e canal.instance.dbUsername=canal \
 -e canal.instance.dbPassword=Canal@123456 \
 -e canal.instance.connectionCharset=UTF-8 \
 -e canal.instance.tsdb.enable=true \
 -e canal.instance.gtidon=false \
 --name=canal-server -p 11110:11110 -p 11111:11111 -p 11112:11112 -p 9100:9100 -m 4096m canal/canal-server

本地启动 Canal Client

添加 canal maven

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

添加 Canal Client Code

public class SimpleCanalClientExample {


    public static void main(String args[]) throws CanalClientException {
        // 创建链接 指定 canal server 地址
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1",
                11111), "test", "", "");
        int batchSize = 1000;
        int emptyCount = 0;
        try {
            connector.connect();
            connector.subscribe("tsoc\..*");
            connector.rollback();
            int totalEmptyCount = 1200;
            while (emptyCount < totalEmptyCount) {
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if ((batchId == -1 || size == 0) && emptyCount % 10 == 0) {
                    emptyCount++;
                    System.out.println("-- empty count : " + emptyCount);
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                    }
                } else {
                    emptyCount = 0;
                    // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
                    printEntry(message.getEntries());
                }

                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }

            System.out.println("empty too many times, exit");
        } finally {
            connector.disconnect();
        }
    }

    private static void printEntry(List<Entry> entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }

            RowChange rowChage = null;
            try {
                rowChage = RowChange.parseFrom(entry.getStoreValue());
                String sql = rowChage.getSql();
                System.out.println(sql);
            } 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());
        }
    }

}