Canal同步binlog数据到ES7报错并总结问题

写在前面的话:

       ES在7之后不推荐transport的方式进行连接,统一使用rest的风格连接,如果使用前者要代码层面的要求特别高,把es项目和服务项目都要调整,这里用rest风格目前是流行的只需要在hosts后面加上http://,核心配置的东西不多,使用transport报各种各样的错就直接用rest模式,配置文件的书写使用idea减少没必要的错误,其次存在kafka和rabbit的配置,这些配置可能不能去掉否则空指针异常,这种情况避免他们使用到同一个端口号。

Canal同步binlog数据到ES7报错并总结问题_第1张图片

z1 开启mysql的binlog功能

2  下载canal.adapte和canal.deployer工具包

3   配置canal.deployer文件

在下载工具包的时候一定注意es版本与当前包的版本关系,不是越新越好,否则会出现各种报错

配置canal.deployer对mysql的binlog监听,在deployer-1.1.7\conf\example下的instance.properties文件配置要监听的mysq服务器,这个文件配置并不复杂,只需要配置账号密码,数据库名,以及要监听的数据库表,可以去全部可以是指定数据库文件,这里为了方面,监听所有,省的来回修改,里面最重要的点是mysql的binlog日志。

 启动canal.deployer-1.1.7\bin下的startup.bat查看log文件夹下的日志,避免报错,影响接下来的配置

配置canal.adapte内容在conf下的application.yml中配置数据采集服务规则,

  srcDataSources:
    defaultDS:
      url: jdbc:mysql://127.0.0.1:3308/canal?useUnicode=true
      username: root
      password: 1234
  canalAdapters:
  - instance: example # canal instance Name or mq topic name
    groups:
    - groupId: g1
      outerAdapters:

 - name: es7
        keys: db1Key
        hosts: 127.0.0.1:9300 # 127.0.0.1:9200 for rest mode
        properties:
          mode: transport # or rest
          # security.auth: test:123456 #  only used for rest mode
          cluster.name: elasticsearch

这是服务的提供数据抓取的核心配置,在es7中配置一个自己的规则canal.yml

dataSourceKey: defaultDS   #指定数据源,这个值和adapter的application.yml文件中配置的srcDataSources值对应。
destination: example       #指定canal-server中配置的某个实例的名字,注意:我们可能配置多个实例,你要清楚的知道每个实例收集的是那些数据,不要瞎搞。
groupId: g1                #组ID,对应application.yml中的canalAdapters/groups/groupId中的值
outerAdapterKey: db1Key    #对应application.yml中的canalAdapters/groups/outerAdapters/name/key中的值
esMapping:                 #ES的mapping(映射)
  _index: canal          #要同步到的ES的索引名称(自定义),需要自己在ES上创建哦!
  #_type: _doc              #ES索引的类型名称(自定义)
  _id: _id                 #ES标示文档的唯一标示,通常对应数据表中的主键ID字段,注意我这里写成的是"_id",有个下划线哦!
  #pk: id                  #如果不需要_id, 则需要指定一个属性为主键属性
  sql: "select t.id as _id, t.account,t.password,t.age, t.email from user t" #这里就是数据表中的每个字段到ES索引中叫什么名字的sql映射,注意映射到es中的每个字段都要是唯一的,不能重复。
  #etlCondition: "where t.occur_time>='{0}'"
commitBatch: 3000

 版本不一致会报错

 exception caught on transport layer [NettyTcpChannel{localAddress=/127.0.0.1:9300, remoteAddress=/127.0.0.1:65412}], closing connection

然后创建ES索引,做好字段关系映射

PUT /canal
{
  "mappings": {
    "properties":{
      "id":{
        "type":"long"
      },
      "account":
      {
        "type":"text",
        "analyzer": "ik_smart"
      },
      "password":{
        "type":"text",
        "index": false
      },
       "age":{
        "type":"long"
      },
      "email":{
        "type":"text"
      }
    }
  }
  }

这些弄完后发现会一直报错

java.lang.ClassCastException: com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource

解决方式,编译源代码并替换,下载地址

https://github.com/alibaba/canal/archive/refs/tags/canal-1.1.5.tar.gz编译方式,单独在局部编译容易报错,手动定位到定位到 client-adapter.escore 模块的 pom.xml 的 druid 更新为

    com.alibaba
    druid
    provided

选择在根目录下运行 

mvn clean package成功后把canal-canal-1.1.5/client-adapter/es7x/target 下 将打包好的 client-adapter.es7x-1.1.5-jar-with-dependencies.jar 替换掉 canal-adapter/plugin 下原来的依赖,最终会解决。

      还有一种解决方案下载v1.1.5-alpha-2的adapter,然后复制这个版本的client-adapter.es7x-1.1.5-SNAPSHOT-jar-with-dependencies.jar 放入1.1.5的plugins目录中,这是一种比较快捷的方式,如果你了解源码思路可以采取局部编译,担是容易形成依赖下不下来问题。

       Canal-deployer是对接mysql的服务,伪装成从节点发送dump请求,让master发送binlog给服务端,canal-deployer负责采集数据增量同步到其他设备如ES,MQ,Redis等,除此之外应用层可以对对接这里个服务,获取增量信息,springBoot中测试如下,导入依赖,创建连接客户端

       
        
            com.alibaba.otter
            canal.protocol
            1.1.5
        

        
            com.alibaba.otter
            canal.client
            1.1.5
        

客户端代码

package canal.es;

import java.net.InetSocketAddress;
import java.util.List;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;


@Component
public class CanalClient implements InitializingBean {

    private final static int BATCH_SIZE = 1000;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 创建链接
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "", "");
        try {
            //打开连接
            connector.connect();
            //订阅数据库表,全部表
            connector.subscribe(".*\\..*");
            //回滚到未进行ack的地方,下次fetch的时候,可以从最后一个没有ack的地方开始拿
            connector.rollback();
            while (true) {
                // 获取指定数量的数据
                Message message = connector.getWithoutAck(BATCH_SIZE);
                //获取批量ID
                long batchId = message.getId();
                //获取批量的数量
                int size = message.getEntries().size();
                //如果没有数据
                if (batchId == -1 || size == 0) {
                    try {
                        //线程休眠2秒
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    //如果有数据,处理数据
                    printEntry(message.getEntries());
                }
                //进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。
                connector.ack(batchId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            connector.disconnect();
        }
    }

    /**
     * 打印canal server解析binlog获得的实体类信息
     */
    private static void printEntry(List entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                //开启/关闭事务的实体类型,跳过
                continue;
            }
            //RowChange对象,包含了一行数据变化的所有特征
            //比如isDdl 是否是ddl变更操作 sql 具体的ddl sql beforeColumns afterColumns 变更前后的数据字段等等
            RowChange rowChage;
            try {
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e);
            }
            //获取操作类型:insert/update/delete类型
            EventType eventType = rowChage.getEventType();
            //打印Header信息
            System.out.println(String.format("================》; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                    eventType));
            //判断是否是DDL语句
            if (rowChage.getIsDdl()) {
                System.out.println("================》;isDdl: true,sql:" + rowChage.getSql());
            }
            //获取RowChange对象里的每一行数据,打印出来
            for (RowData rowData : rowChage.getRowDatasList()) {
                //如果是删除语句
                if (eventType == EventType.DELETE) {
                    printColumn(rowData.getBeforeColumnsList());
                    //如果是新增语句
                } else if (eventType == 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 columns) {
        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }
}

启动SpringBoot项目运行结果

Canal同步binlog数据到ES7报错并总结问题_第2张图片分别对应了删除和修改操作,binlog通过日志事务版本号来标记记录数据,当物理上的数据丢失就能通过这个保存的快照恢复到之前的数据,可以有以下操作,开启binlog日志,创建数据库,创建一张表,插如几条数据,查看binlog,生成事务事件id,把表删除,使用mysql自带的mysqlbinlog  mysql-bin.000001或者

show binlog events [in 'log_name'] [FROM pos] [limit [offset,] row_count]

show binlog events in 'mysql-bin.000001' from 124 limit 0 5;

使用 mysqlbinlog 命令,查找从备份时间到误操作时间之间的 binlog 日志文件。命令格式如下:

mysqlbinlog --start-datetime="2023-06-01 10:00:00" --stop-datetime="2023-06-02 10:00:00" /var/lib/mysql/binlog.000001 > mysqlbinlog.txt
 其中,--start-datetime 表示起始时间,--stop-datetime 表示结束时间,/var/lib/mysql/binlog.000001 是 binlog 日志文件的路径,> mysqlbinlog.txt 表示将输出结果保存到 mysqlbinlog.txt 文件中。
  1. 打开 mysqlbinlog.txt 文件,查找误删除数据的操作。
  2. 复制误删除操作的 binlog 日志。
  3. 回到 MySQL 控制台,使用 mysqlbinlog 命令来还原误删除的操作。命令格式如下
    mysqlbinlog --no-defaults mysqlbinlog.txt | mysql -u username -p dbname
     其中,--no-defaults 表示不使用默认配置文件,mysqlbinlog.txt 是保存误删除操作的 binlog 日志文件,username 是 MySQL 用户名,dbname 是要还原的数据库名称。

你可能感兴趣的:(android)