讨论的前提是 CanalMetaManager 和 CanalLogPositionManager 都使用 file、zookeeper或者mix(memory+zookeeper)类型的实现类来保存。
两个概念:meta和logPosition
meta:记录ack()的位置
logPosition:记录拉取binlog的位置
当调用CanalServer.get()时,开始位置会先取meta(但是并不作为起始位置,没理解meta有何用处)
CanalServer.ack()时,再更新meta的位置
1.多个url来源的处理
监控数据binlog是canal的任务。CanalInstance创建多个CanalEventParser来监控多个url来源的数据。
- 一个进程有一个CanalServer;
- 一个CanalServer有一个或多个CanalInstance;
- 一个CanalInstance有一个或多个CanalEventParser(当CanalInstance的getGroupSize()>1时,有多个CanalEventParser,即一个CanalInstance监控多个url的binlog),一个CanalEventStore(以RingBuffer的形式存储从binlog转换而来的对象),一个CanalMetaManager。
- 一个CanalEventParser有一个CanalLogPositionManager。
2.事务的处理
由于CanalInstance可能需要处理多个url来源的数据,但是一个CanalInstance只有一个CanalEventStore,这意味着多个url来源的数据需要放到同一个对象存储里面,但是这些数据的最小单位并不是一个事务,所以需要使用EventTransactionBuffer将一个完整的事务放到CanalEventStore中,避免多个url来源的数据在一个事务中被拆散:
- AbstractEventParser.parseThread 线程中获取到数据后,会调用EventTransactionBuffer.add(entry)利用CanalEventSink将一个完整的事务放到CanalEventStore中。
3.停机后,从上次停机的位置执行的处理
实现过程
CanalEventParser有自己的CanalLogPositionManager来保存位置,且某些实现可以持久化到文件或者zookeeper。当停机重启后,可以获取上次位置继续执行:
- 每个CanalEventParser从自己对应的url拉取binlog时,开始位置会先取CanalLogPositionManager中的LogPosition,如果没有才会利用show master status从mysql中取binlog的当前位置。当获取数据并将放入CanalEventStore后,会更新CanalLogPositionManager的logPosition。
数据重复问题
- 由于file、zookeeper、mix类型的CanalLogPositionManager可以持久或存储上次的位置到文件中,所以下次启动项目后可以从原来的位置继续运行。但是logPosition的位置是先放入内存,再有定时器定时持久化到文件或者zookeeper中的,所以是不是会有一些延迟,从而导致下次开始时会有一些已处理过的重复数据?
数据丢失问题
- CanalEventStore使用RingBuffer的形式进行存储,操作数据有put()/get()/ack()三个方法。上面说到“当获取数据并将放入CanalEventStore后,会更新CanalLogPositionManager的logPosition”,这意味着CanalLogPositionManager中记录的是put()的位置。而put()到CanalEventStore中后,这些数据可能还没有处理就down机了。这些数据没有处理,而下次开始后不会再次获取这些数据?
canal原代码中group类型的CanalInstance从上次停机位置继续执行有问题(已修改)
当canalinstance为group类型时会有问题,问题原因是同一个canalInstance的多个CanalLogPositionManager持久化到文件或zookeeper时使用了相同的存储路径。
- 以file类型的CanalLogPositionManager实现FileMixedLogPositionManager为例:Canal代码中将多个CanalLogPositionManager的位置都放在类同一个destination下的同名文件中,即:/{logPositionRoot}/{destionation}/parse.dat。导致CanalInstance为group时,虽然CanalInstance的多个CanalParser的存储位置的对象CanalLogPositionManager是分开的,但是多个对象却使用了同一个存储路径造成数据覆盖,从而使CanalInstance最终只有一个CanalEventParser能继续从上次位置开始执行。
特别注意:在切换过程中(即双方都正在提供线上服务时),不能停机。如果出现停机,则直接将任务废弃掉重新开始迁移数据,千万不要从上次位置继续执行,否则会出现数据覆盖。
-------------
目前canal-1.0.26-SNAPSHOT 对n:1的支持有问题。
n:1即:一个canalInstance中有多个数据来源,如多个不同地址的mariadb(10.1.200.144:3306/10.1.200.145:3306)
1.GroupEventSink 中的TimelineTransactionBarrier 中两个方法不匹配 isPermit()/clear()
需要将 clear()中 if (isTransactionEnd(event)) 改为 if (isTransactionEnd(event) && inTransaction.get())
2.使用group时,CanalMetaManager 和 CanalLogPositionManager都只会保存一个canalInstance实例中的一个地址,但是group类型的canalInstance有多个地址
如何解决?
CanalMetaManager 和 CanalLogPositionManager
1)canalInstance->CanalMetaManager 1对1
2)canalInstance->CanalLogPositionManager 1对多 当groupDbAddresses的groupSize()>1时 为1对多
因为:
canalInstance->CanalEventParser 1对多
CanalEventParser->CanalLogPositionManager 1对1
CanalLogPositionManager
1.persistLogPosition() 从 binlog拉取数据,并放入CanalEventStore后 set位置
1)
AbstractEventParser.parseThread.run()
EventTransactionBuffer.add()
EventTransactionBuffer.TransactionFlushCallback.flush()
logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position);
2.getLatestIndexBy() 从 binlog拉取数据前 get位置
2)
AbstractEventParser.parseThread.run()
MysqlEventParser.findStartPosition()
MysqlEventParser.findStartPositionInternal()
logPositionManager.getLatestIndexBy(destination)
CanalMetaManager
1.getCursor()
1)CanalServer.subscribe()
2)CanalServer.get()
3)AbstractCanalStoreScavenge()// getCursor()之后,会 cleanUtil(),即ack() 貌似没被调用过
2.updateCursor()
1)CanalServer.ack()
2)CanalServer.subscribe()
当调用CanalServer.get()时,开始位置会先取meta(但是并不作为起始位置,没理解meta有何用处)
CanalServer.ack()时,再更新meta的位置