Canal使用-基于读取binlog

1、https://github.com/alibaba/canal

2、示例代码

package net.wecash.o2ocanalclient.canal;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import net.wecash.o2ocanalclient.entity.OrderdetailDO;
import net.wecash.o2ocanalclient.entity.RcOrderdetailDO;
import net.wecash.o2ocanalclient.entity.RepaymentPlanDO;
import net.wecash.o2ocanalclient.entity.ThirdOrderDetailDO;
import net.wecash.o2ocanalclient.mappers.RcOrderdetailMapper;
import net.wecash.o2ocanalclient.utils.CanalUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;

/**
 * 数据导入 将orderdetail 和 pl_repayment_plan表的数据实时更新到rc_orderdetail表中
 * 利用canal读取数据库binlog文件
 * @Author:Luobh
 * @create: 2018-11-09
 **/
@Component
public class DataImportTask {

    Logger logger = LogManager.getLogger(this.getClass());
    @Autowired
    RcOrderdetailMapper rcOrderdetailMapper;
    @Value("${canal.zkServers}")
    String zkServers;
    @Value("${canal.destination}")
    String destination;
    @Autowired
    TaskExecutor taskExecutor;

    public void start() {
        taskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                logger.info("data import start...zkServers->{},destination->{}",zkServers,destination);
                // 基于zookeeper动态获取canal server的地址,建立链接,其中一台server发生crash,可以支持failover
                CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(), 11111),"example", "", "");
                //CanalConnector connector = CanalConnectors.newClusterConnector(zkServers, destination, "", "");
                int batchSize = 1000;
                try {
                    connector.connect();
                    //订阅关注的表
                    connector.subscribe("o2o.pl_repayment_plan_user,o2o.orderdetail,o2o.o2o_third_orderdetail");
                    while (true) {
                        long batchId = -1;
                        Message message = null;
                        try {
                            //获取指定数量的数据 不设置阻塞时间
                            message = connector.getWithoutAck(batchSize);
                            batchId = message.getId();
                            int size = message.getEntries().size();
                            if (batchId == -1 || size == 0) {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    logger.error("canal sleep error,", e);
                                }
                            } else {
                                logger.info("deal with message start,size->{}", size);
                                dealWithEntry(message.getEntries());
                            }
                             //提交确认
                             connector.ack(batchId);
                        } catch (Exception e) {
                            //失败后不回滚,记录下错误日志
                            logger.info("canal deal with message error,batchId->{},message->{}", batchId, message,e);
                        }
                    }
                } finally {
                    logger.info("connector disconnect... ");
                    connector.disconnect();
                }
            }
        });

    }

    private void dealWithEntry(List<CanalEntry.Entry> entrys) {
        for (CanalEntry.Entry entry : entrys) {
            try {
                CanalEntry.RowChange rowChage = null;
                try {
                    rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                } catch (Exception e) {
                    throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e);
                }
                logger.info("tableName->{},rowChage->{}",entry.getHeader().getTableName(),rowChage);
                //根据不同表来源的数据进行操作
                tableProxy(entry.getHeader().getTableName(),rowChage);
            } catch (Exception e) {
                //逐条记录错误信息,防止一条数据影响多条
                logger.error("dealWithEntry error,entry->{}",entry.toString(),e);
            }
        }
    }

    private void tableProxy(String tableName,CanalEntry.RowChange rowChage) {
        CanalEntry.EventType eventType = rowChage.getEventType();
        if ("orderdetail".equals(tableName)) {
            orderdetailHandler(rowChage,eventType);

        } else if ("pl_repayment_plan_user".equals(tableName)) {
            plRepaymentPlanHandler(rowChage,eventType);

        } else if ("o2o_third_orderdetail".equals(tableName)) {
            thirdOrderdetailHandler(rowChage,eventType);

        }
    }

    /**
     * 老贷后还款计划表
     * orderdetail
     * @param rowChage
     * @param eventType
     */
    private void orderdetailHandler(CanalEntry.RowChange rowChage,CanalEntry.EventType eventType) {
        for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
            if (eventType == CanalEntry.EventType.INSERT || eventType == CanalEntry.EventType.UPDATE) {
                //插入 更新
                OrderdetailDO orderdetailDO = new OrderdetailDO();
                CanalUtil.columnListToDO(rowData.getAfterColumnsList(),orderdetailDO);
                orderDetailToRc(orderdetailDO);
            } else if (eventType == CanalEntry.EventType.DELETE) {
                //删除
                Long id = CanalUtil.getDelId(rowData.getBeforeColumnsList());
                rcOrderdetailMapper.deleteRcOrderdetailById(id);
            }
        }
    }

    /**
     * 新贷后还款计划表
     * pl_repayment_plan_user
     * @param rowChage
     * @param eventType
     */
    private void plRepaymentPlanHandler(CanalEntry.RowChange rowChage,CanalEntry.EventType eventType) {
        for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
            if (eventType == CanalEntry.EventType.INSERT || eventType == CanalEntry.EventType.UPDATE) {
                //插入 更新
                RepaymentPlanDO repaymentPlanDO = new RepaymentPlanDO();
                CanalUtil.columnListToDO(rowData.getAfterColumnsList(),repaymentPlanDO);
                if (repaymentPlanDO.getPeriod()==null || repaymentPlanDO.getOrderId()==null) {
                    Map<String,Integer> map = rcOrderdetailMapper.getPeriodAndOrderIdByPlOrderId(repaymentPlanDO.getPlOrderId());
                    if (map!=null) {
                        Integer period = map.get("period");
                        Integer orderId = map.get("order_id");
                        repaymentPlanDO.setPeriod(period);
                        repaymentPlanDO.setOrderId(Long.valueOf(orderId));
                    }
                }
                repaymentPlanUserToRc(repaymentPlanDO);
            } else if (eventType == CanalEntry.EventType.DELETE) {
                //删除
                Long id = CanalUtil.getDelId(rowData.getBeforeColumnsList());
                rcOrderdetailMapper.deleteRcOrderdetailById(id);
            }
        }
    }

    /**
     * 旖美表
     * o2o_third_orderdetail
     * @param rowChage
     * @param eventType
     */
    private void thirdOrderdetailHandler(CanalEntry.RowChange rowChage,CanalEntry.EventType eventType) {
        for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
            if (eventType == CanalEntry.EventType.INSERT || eventType == CanalEntry.EventType.UPDATE) {
                //插入 更新
                ThirdOrderDetailDO thirdOrderDetailDO = new ThirdOrderDetailDO();
                CanalUtil.columnListToDO(rowData.getAfterColumnsList(),thirdOrderDetailDO);
                thirdOrderDetailToRc(thirdOrderDetailDO);
            } else if (eventType == CanalEntry.EventType.DELETE) {
                //删除
                Long id = CanalUtil.getDelId(rowData.getBeforeColumnsList());
                rcOrderdetailMapper.deleteRcOrderdetailById(id);
            }
        }
    }

    private void repaymentPlanUserToRc(RepaymentPlanDO planDO) {
        try {
            RcOrderdetailDO rcdo = new RcOrderdetailDO();
            BeanUtils.copyProperties(planDO, rcdo);
            rcdo.setOrderId(planDO.getOrderId());
            rcdo.setStatus(planDO.getBillStatus());
            rcdo.setRepayment(planDO.getPeriod() + "-" + planDO.getCurrentPeriod());
            rcdo.setRepaymentTime(planDO.getShouldRepayTime());
            rcdo.setPaymentDate(planDO.getRealRepayTime());
            rcdo.setStage(planDO.getEveryPrincipalInterest());
            rcdo.setPrincipal(planDO.getShouldRepayPrincipal());
            rcdo.setInterests(planDO.getShouldRepayInterest());
            rcdo.setIsOverdue(planDO.getOverdueSign());
            rcdo.setOverDay(planDO.getOverdueDays());
            rcdo.setRemainAmount(planDO.getRemainTotal());
            rcdo.setShouldAmount(planDO.getShouldRepayTotal());
            rcdo.setRealAmount(planDO.getRealRepayTotal());
            rcdo.setDemurrage(planDO.getShouldRepayOverdueFee());
            rcdo.setLsnum(planDO.getLsnum());
            rcdo.setPaytype(planDO.getRepayType());
            rcdo.setCompensateAmount(planDO.getChannelPayAmount());
            rcdo.setCompensateDate(planDO.getChannelPayTime());
            rcdo.setCreateTime(planDO.getCreateTime());
            rcdo.setUpdateTime(planDO.getUpdateTime());
            rcOrderdetailMapper.saveRcOrderdetail(rcdo);
        } catch (Exception e) {
            logger.error("repaymentPlanUserToRc error",e);
        }
    }

    private void orderDetailToRc(OrderdetailDO orderdetailDO) {
        try {
            RcOrderdetailDO rcdo = new RcOrderdetailDO();
            BeanUtils.copyProperties(orderdetailDO, rcdo);
            rcdo.setCreateTime(orderdetailDO.getCreateTime());
            rcdo.setUpdateTime(orderdetailDO.getUpdateTime());
            rcOrderdetailMapper.saveRcOrderdetail(rcdo);
        } catch (Exception e) {
            logger.error("orderDetailToRc error,",e);
        }
    }

    private void thirdOrderDetailToRc(ThirdOrderDetailDO thirdOrderDetailDO) {
        try {
            RcOrderdetailDO rcdo = new RcOrderdetailDO();
            BeanUtils.copyProperties(thirdOrderDetailDO, rcdo);
            rcdo.setId(thirdOrderDetailDO.getId()+3000000000000000000L);
            rcdo.setPaymentDate(thirdOrderDetailDO.getPaymentTime());
            rcdo.setCreateTime(thirdOrderDetailDO.getCreateTime());
            rcdo.setUpdateTime(thirdOrderDetailDO.getUpdateTime());
            rcOrderdetailMapper.saveRcOrderdetail(rcdo);
        } catch (Exception e) {
            logger.error("orderDetailToRc error,",e);
        }
    }
}

3、遇到的问题

问题1:column size is not match for table:o2o.pl_repayment_plan_user,52 vs 51

 

检查配置文件/canal.deployer-1.1.1/conf/canal.properties

canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml

以及

# table meta tsdb info
canal.instance.tsdb.enable=true
canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
canal.instance.tsdb.dbUsername=canal
canal.instance.tsdb.dbPassword=canal

检查 tsdb是否已经开启 

如果都已开启还是有这个问题 则需要清除canal对表结构的缓存

conf/example/h2.mv.db

问题2:找不到binlog日志文件

清空缓存

canal.deployer-1.1.1/conf/example/meta.dat

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在上一篇文章中,我们介绍了使用 Canal 分析 Binlog 的基本流程和使用方法。本文将继续深入探讨 Canal 的源码实现。 Canal 的架构设计 Canal 的整体架构可以分为三个部分:Client、Server、Store。其中,Client 是客户端,用于连接 MySQL 数据库并获取 Binlog;Server 是服务端,用于接收 Client 发送的 Binlog,并进行解析处理;Store 是存储层,用于保存解析后的 Binlog 信息。 在 Client 端,Canal 使用了阿里开源的 Cobar 连接池,保证了高并发的连接请求。对于每一个连接,Canal 都会为其分配一个线程,用于读取 Binlog 并发送到 Server 端。 在 Server 端,Canal 采用 NIO 的方式进行网络通信。当收到 Binlog 数据后,Canal 会将其解析为一条条的事件,并将事件发送给所有监听该实例的客户端。同时,Canal 还支持对 Binlog 进行过滤和转换,以满足不同的业务需求。 在 Store 层,Canal 提供了多种存储方式,包括内存、文件、Kafka、MQ 等。用户可以根据自己的需求进行选择。 Canal 的核心实现 在分析 Canal 的核心实现之前,我们需要先了解一下 Binlog 的结构。Binlog 是 MySQL 用于记录数据库变更的一种日志格式,其主要由 event header 和 event data 两部分构成。其中,event header 包含了事件类型、时间戳、server id 等信息,event data 则包含了具体的事件内容。 Canal 的核心实现主要包括两个部分:BinlogParser 和 CanalEventParser。 BinlogParser 用于解析 Binlog,并将其转化为事件对象。在解析 Binlog 时,BinlogParser 首先会读取 event header,然后根据 event header 中的事件类型选择相应的 CanalEventParser 进行处理。CanalEventParser 则负责将 event data 解析为具体的事件对象。 CanalEventParser 实现了一系列的事件解析器,包括 QueryEventParser、TableMapEventParser、WriteRowsEventParser 等。每个事件解析器都负责将 event data 转化为相应的事件对象,并填充事件对象中的各个字段。例如,WriteRowsEventParser 将 event data 解析为 WriteRowsEvent,并设置其对应的表名、列名、新插入的行等信息。 Canal 的事件模型 Canal 的事件模型主要由 CanalEvent 和 CanalEventSink 两部分构成。CanalEvent 表示一个数据库事件,包括事件类型、事件数据、表名、库名等信息。CanalEventSink 则表示一个事件处理器,用于接收并处理 CanalEvent。 在 Canal 中,每个客户端都会创建一个对应的 CanalEventSink,并将其注册到 Server 端。当 Server 端接收到 Binlog 数据并解析为 CanalEvent 后,会将 CanalEvent 发送给所有注册的 CanalEventSink。CanalEventSink 则根据事件类型进行相应的处理,例如将 InsertEvent 存储到数据库中。 Canal 的优点和缺点 Canal 的主要优点在于其高效、可扩展和灵活的架构设计。Canal 使用了阿里开源的 Cobar 连接池和 NIO 网络通信,保证了高并发的连接请求和网络数据传输。同时,Canal 的存储层也支持多种存储方式,可以根据用户需求进行选择。此外,Canal 还支持对 Binlog 进行过滤和转换,以满足不同的业务需求。 Canal 的缺点在于其对 MySQL 版本有一定的限制。由于 Binlog 格式在不同的 MySQL 版本之间存在差异,因此 Canal 只支持特定版本的 MySQL,且需要用户手动配置相应的 Binlog 格式。此外,Canal 无法保证数据的完整性,如果在解析 Binlog 过程中出现异常,可能会导致部分数据丢失。 总结 Canal 是一款高效、可扩展和灵活的 Binlog 解析工具,可以帮助用户实现对 MySQL 数据库变更的监控和同步。Canal 的核心实现包括 BinlogParser 和 CanalEventParser,其中 BinlogParser 用于解析 Binlog,CanalEventParser 则负责将 event data 转化为具体的事件对象。Canal 的事件模型主要由 CanalEvent 和 CanalEventSink 两部分构成,可以根据用户需求进行灵活配置。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值