问题描述
新增Mysql数据时,ES、Hbase数据会同步成功;当删除Mysql数据,或者修改Mysql数据时同步ES、Hbase数据无变化(PS:修改和删除加上LIMIT xxx 就可以成功。)
问题分析
通过查看日志发现新增和删除记录的日志区别:新增data有数据,修改、删除 data为空
2021-09-22 10:29:03.551 [pool-1-thread-1] DEBUG c.a.o.c.client.adapter.hbase.service.HbaseSyncService - DML: {"data":[{"id":"d9a63f30-1b4c-11ec-99c4-708cb6f5eaa6","name":"aaabbbcccc","age":null,"age_2":null,"message":null,"insert_time":null}],"database":"test2","destination":"example","es":1632277742000,"groupId":"g1","isDdl":false,"old":null,"pkNames":["id"],"sql":"","table":"testsync","ts":1632277743140,"type":"INSERT"}
2021-09-22 10:19:36.703 [pool-1-thread-1] DEBUG c.a.o.c.client.adapter.hbase.service.HbaseSyncService - DML: {"data":null,"database":"test2","destination":"example","es":1632277176000,"groupId":"g1","isDdl":false,"old":null,"pkNames":[],"sql":"DELETE FROM testsync where name = 'aaabbbcccc'","table":"testsync","ts":1632277176566,"type":"DELETE"}
2021-09-22 10:19:36.704 [pool-1-thread-1] DEBUG c.a.o.canal.client.adapter.es.core.service.ESSyncService - DML: {"data":null,"database":"test2","destination":"example","es":1632277176000,"groupId":"g1","isDdl":false,"old":null,"pkNames":[],"sql":"DELETE FROM testsync where name = 'aaabbbcccc'","table":"testsync","ts":1632277176566,"type":"DELETE"}
通过查看源码我们发现,当data为null时,直接return,不进行下一步操作
/*** 更新操作dml** @param config es配置* @param dml dml数据*/private void update(ESSyncConfig config, Dml dml) {List<Map<String, Object>> dataList = dml.getData();List<Map<String, Object>> oldList = dml.getOld();if (dataList == null || dataList.isEmpty() || oldList == null || oldList.isEmpty()) {return;}....
问题找到,原因就是我们没有记录修改的数据。
再次查看源码,找一下是从什么时候data没有的。
发现在MessageUtil中有数据转换,加入日志,重新打包将 connector.core-1.1.5.jar 替换掉,重启服务,我们查看下从数据接收进来是不是就已经没有了
/*** Message对象解析工具类** @author rewerma 2018-8-19 下午06:14:23* @version 1.0.0*/
public class MessageUtil {private static final Logger logger = LoggerFactory.getLogger(MessageUtil.class);public static List<CommonMessage> convert(Message message) {if (message == null) {return null;}List<CanalEntry.Entry> entries = message.getEntries();List<CommonMessage> msgs = new ArrayList<>(entries.size());for (CanalEntry.Entry entry : entries) {if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {continue;}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);}// 加入日志,重新打包将 connector.core-1.1.5.jar 替换掉,重启服务,我们查看下从数据接收进来是不是就已经没有了logger.info("row:{}",rowChange.getRowDatasList());....
重启后在adapter日志中发现,在接收数据时已经没有了。
随后我们从 Mysql方面分析,根据Mysql Binlog三种格式我们知道,当类型设置Statement或者Mixed时会导致数据记录不是全部列。
这里简单介绍下三种区别
MySQL 5.5 中对于二进制日志 (binlog) 有 3 种不同的格式可选:Mixed,Statement,Row,默认格式是 Statement。总结一下这三种格式日志的优缺点。MySQL Replication 复制可以是基于一条语句 (Statement Level) ,也可以是基于一条记录 (Row Level),可以在 MySQL 的配置参数中设定这个复制级别,不同复制级别的设置会影响到 Master 端的 bin-log 日志格式。1. Row
日志中会记录成每一行数据被修改的形式,然后在 slave 端再对相同的数据进行修改。优点:在 row 模式下,bin-log 中可以不记录执行的 SQL 语句的上下文相关的信息,仅仅只需要记录那一条记录被修改了,修改成什么样了。所以 row 的日志内容会非常清楚的记录下每一行数据修改的细节,非常容易理解。而且不会出现某些特定情况下的存储过程或 function ,以及 trigger 的调用和触发无法被正确复制的问题。缺点:在 row 模式下,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如有这样一条 update 语句:UPDATE product SET owner_member_id = 'b' WHERE owner_member_id = 'a'
执行之后,日志中记录的不是这条 update 语句所对应的事件 (MySQL 以事件的形式来记录 bin-log 日志) ,而是这条语句所更新的每一条记录的变化情况,这样就记录成很多条记录被更新的很多个事件。自然,bin-log 日志的量就会很大。尤其是当执行 alter table 之类的语句的时候,产生的日志量是惊人的。因为 MySQL 对于 alter table 之类的表结构变更语句的处理方式是整个表的每一条记录都需要变动,实际上就是重建了整个表。那么该表的每一条记录都会被记录到日志中。2. Statement
每一条会修改数据的 SQL 都会记录到 master 的 bin-log 中。slave 在复制的时候 SQL 进程会解析成和原来 master 端执行过的相同的 SQL 再次执行。优点:在 statement 模式下,首先就是解决了 row 模式的缺点,不需要记录每一行数据的变化,减少了 bin-log 日志量,节省 I/O 以及存储资源,提高性能。因为他只需要记录在 master 上所执行的语句的细节,以及执行语句时候的上下文的信息。缺点:在 statement 模式下,由于他是记录的执行语句,所以,为了让这些语句在 slave 端也能正确执行,那么他还必须记录每条语句在执行的时候的一些相关信息,也就是上下文信息,以保证所有语句在 slave 端杯执行的时候能够得到和在 master 端执行时候相同的结果。另外就是,由于 MySQL 现在发展比较快,很多的新功能不断的加入,使 MySQL 的复制遇到了不小的挑战,自然复制的时候涉及到越复杂的内容,bug 也就越容易出现。在 statement 中,目前已经发现的就有不少情况会造成 MySQL 的复制出现问题,主要是修改数据的时候使用了某些特定的函数或者功能的时候会出现,比如:sleep() 函数在有些版本中就不能被正确复制,在存储过程中使用了 last_insert_id() 函数,可能会使 slave 和 master 上得到不一致的 id 等等。由于 row 是基于每一行来记录的变化,所以不会出现类似的问题。3. Mixed
从官方文档中看到,之前的 MySQL 一直都只有基于 statement 的复制模式,直到 5.1.5 版本的 MySQL 才开始支持 row 复制。从 5.0 开始,MySQL 的复制已经解决了大量老版本中出现的无法正确复制的问题。但是由于存储过程的出现,给 MySQL Replication 又带来了更大的新挑战。另外,看到官方文档说,从 5.1.8 版本开始,MySQL 提供了除 Statement 和 Row 之外的第三种复制模式:Mixed,实际上就是前两种模式的结合。在 Mixed 模式下,MySQL 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 statement 和 row 之间选择一种。新版本中的 statment 还是和以前一样,仅仅记录执行的语句。而新版本的 MySQL 中对 row 模式也被做了优化,并不是所有的修改都会以 row 模式来记录,比如遇到表结构变更的时候就会以 statement 模式来记录,如果 SQL 语句确实就是 update 或者 delete 等修改数据的语句,那么还是会记录所有行的变更。
最后去查看我们配置文件,发现我们的配置文件设置了两个 binlog_format (复制之前的配置文件,在上面加了一个 binlog_format=ROW,但是未生效,被下面的覆盖了。。。。。)
最终删除
log_bin = mysql-bin
binlog_format = mixed
保留
log_bin=mysql-bin # 开启 binlog
binlog_format=ROW # 选择 ROW 模式
binlog_row_image=FULL
重启Mysql、canal 再次执行修改删除,data中已经有记录
2021-09-22 11:20:45.456 [pool-1-thread-1] DEBUG c.a.o.c.client.adapter.hbase.service.HbaseSyncService - DML: {"data":[{"id":"10d7e4b8-1b54-11ec-8673-708cb6f5eaa6","name":"bbb","age":null,"age_2":null,"message":null,"insert_time":1632280844000}],"database":"test2","destination":"example","es":1632280844000,"groupId":"g1","isDdl":false,"old":[{"name":"aaabbbcccc","insert_time":null}],"pkNames":["id"],"sql":"","table":"testsync","ts":1632280845389,"type":"UPDATE"}
2021-09-22 11:20:45.517 [pool-1-thread-1] DEBUG c.a.o.canal.client.adapter.es.core.service.ESSyncService - DML: {"data":[{"id":"10d7e4b8-1b54-11ec-8673-708cb6f5eaa6","name":"bbb","age":null,"age_2":null,"message":null,"insert_time":1632280844000}],"database":"test2","destination":"example","es":1632280844000,"groupId":"g1","isDdl":false,"old":[{"name":"aaabbbcccc","insert_time":null}],"pkNames":["id"],"sql":"","table":"testsync","ts":1632280845389,"type":"UPDATE"}
文章开头有说:PS:修改和删除加上LIMIT xxx 就可以成功。原因就是ROW和mixed的区别。
其他类似问题
当为mixed时,在Navicat直接点+新增数据时data也同样为空。改为ROW即可