首先說一下我對canal中位點的理解。什么是位點?位點是 binlog事件在binlog文件中的位置。但是對于canal而言,canal server發(fā)送dump請求前需要確定mysql的同步位點
,主要包括canal server啟動,mysql主備切換,canal server主備切換,dump異常后重啟等情況。 同時,在canal client不斷從canal server讀取數(shù)據(jù)的過程中, canal client
需要告知 canal server自己消費成功的位點
,這樣當(dāng)發(fā)生canal client崩潰或者canal server崩潰重啟后,都會考慮是否按照原來消費成功的位點之后繼續(xù)消費或dump。下面我將通過canal server dump前找mysql同步位點
的過程分析我對canal中位點的理解。
對于HA模式的canal server,我們先看下有哪些位點管理器。
FailbackLogPositionManager
:實現(xiàn)基于failover查找的機制完成meta的操作,應(yīng)用場景:比如針對內(nèi)存buffer,出現(xiàn)HA切換,先嘗試從內(nèi)存buffer區(qū)中找到lastest position,如果不存在才嘗試找一下meta里消費的信息。初始化過程為 :
//eventParser中的成員變量logPositionManager
<bean class="com.alibaba.otter.canal.parse.index.FailbackLogPositionManager">
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MemoryLogPositionManager" />
</constructor-arg>
<constructor-arg>
<bean class="com.alibaba.otter.canal.parse.index.MetaLogPositionManager">
<constructor-arg ref="metaManager"/>
</bean>
</constructor-arg>
</bean>
//上面logPositionManager中的成員變量secondary
<bean id="metaManager" class="com.alibaba.otter.canal.meta.PeriodMixedMetaManager">
<property name="zooKeeperMetaManager">
<bean class="com.alibaba.otter.canal.meta.ZooKeeperMetaManager">
<property name="zkClientx" ref="zkClientx" />
</bean>
</property>
<property name="period" value="${canal.zookeeper.flush.period:1000}" />
</bean>
主要包括兩個成員變量:
//內(nèi)存
private final CanalLogPositionManager primary;
private final CanalLogPositionManager secondary;
其中primary
是在內(nèi)存中記錄位點變化的管理器MemoryLogPositionManager
,對應(yīng)canal server中dump下來的binlog event最新位點。
secondary
是位點管理器MetaLogPositionManager
,它有一個成員變量metaManager
是PeriodMixedMetaManager
。
PeriodMixedMetaManager
:canal client消費信息管理器,如上是定時刷新canal client消費位點信息到zk上的位點管理器。主要成員變量:
//內(nèi)存中記錄的canal client ack position
protected Map<ClientIdentity, Position> cursors;
//定時刷新線程池
private ScheduledExecutorService executor;
//與zk交互的位點管理器,更新ack position等
private ZooKeeperMetaManager zooKeeperMetaManager;
//定時刷新到zk ack position的時間間隔
private long period = 1000; // 單位ms
private Set<ClientIdentity> updateCursorTasks;
canal server dump前找mysql同步位點
在特定instance上激活的canal server變開始啟動自己的CanalInstanceWithSpring實例,其中會啟動自己的eventParser。如官方文檔所說,parser的啟動和解析過程為:
1.Connection獲取上一次解析成功的位置 (如果第一次啟動,則獲取初始指定的位置或者是當(dāng)前數(shù)據(jù)庫的binlog位點)
2.Connection建立鏈接,發(fā)送BINLOG_DUMP指令
3.Mysql開始推送Binaly Log
4.接收到的Binaly Log的通過Binlog parser進行協(xié)議解析,補充一些特定信息
// 補充字段名字,字段類型,主鍵信息,unsigned類型處理
傳遞給EventSink模塊進行數(shù)據(jù)存儲,是一個阻塞操作,直到存儲成功
5.存儲成功后,定時記錄Binaly Log位置
在此,我們聚焦在步驟一,即canal server dump前找mysql同步位點。代碼如下:
//MysqlEventParser.findStartPosition
protected EntryPosition findStartPosition(ErosaConnection connection) throws IOException {
if (isGTIDMode()) {
//步驟一
// GTID模式下,CanalLogPositionManager里取最后的gtid,沒有則取instanc配置中的
LogPosition logPosition = getLogPositionManager().getLatestIndexBy(destination);
if (logPosition != null) {
//1.3
// 如果以前是非GTID模式,后來調(diào)整為了GTID模式,那么為了保持兼容,需要判斷gtid是否為空
if (StringUtils.isNotEmpty(logPosition.getPostion().getGtid())) {
return logPosition.getPostion();
}
}else {
//1.4
if (masterPosition != null && StringUtils.isNotEmpty(masterPosition.getGtid())) {
return masterPosition;
}
}
}
//步驟二
EntryPosition startPosition = findStartPositionInternal(connection);
if (needTransactionPosition.get()) {
//步驟三
logger.warn("prepare to find last position : {}", startPosition.toString());
Long preTransactionStartPosition = findTransactionBeginPosition(connection, startPosition);
if (!preTransactionStartPosition.equals(startPosition.getPosition())) {
logger.warn("find new start Transaction Position , old : {} , new : {}",
startPosition.getPosition(),
preTransactionStartPosition);
startPosition.setPosition(preTransactionStartPosition);
}
needTransactionPosition.compareAndSet(true, false);
}
return startPosition;
}
代碼邏輯為:
步驟一
.如果是GTID Mode
,找到位點就直接返回。
步驟二
.否則調(diào)用findStartPositionInternal(connection)
繼續(xù)尋找位點。
步驟三
.如果needTransactionPosition=true
,則必須要求找到事務(wù)開啟使的binlog位點作為起始位點返回。
在具體分析上述步驟前,我們先看一下位點類的結(jié)構(gòu):
public class LogPosition extends Position {
//偽裝成slave的canal server信息
private LogIdentity identity;
//位點的包裝類
private EntryPosition postion;
}
public class EntryPosition extends TimePosition {
//
private boolean included = false;
//binlog文件名稱
private String journalName;
//所在binlog文件中的位點位置
private Long position;
// 記錄一下位點對應(yīng)的mysql serverId
private Long serverId = null;
//偽裝成slave的canal server的gtid集合
private String gtid = null;
}
//基于時間的位置,position數(shù)據(jù)不唯一
public class TimePosition extends Position {
private static final long serialVersionUID = 6185261261064226380L;
protected Long timestamp;
可以看出位點也是考慮了兩種dump方式,一種是 binlog filename + position
,一種是GTID
。下面我們先看步驟一中的GTID模式找起始位點。
//FailbackLogPositionManager. getLatestIndexBy
public LogPosition getLatestIndexBy(String destination) {
//1.1
LogPosition logPosition = primary.getLatestIndexBy(destination);
if (logPosition != null) {
return logPosition;
}
//1.2
return secondary.getLatestIndexBy(destination);
}
1.1先從內(nèi)存位點管理器primary
中尋找instance對應(yīng)的dump位點,能找到就返回。
//MemoryLogPositionManager.getLatestIndexBy
public LogPosition getLatestIndexBy(String destination) {
return positions.get(destination);
}
1.2如果內(nèi)存位點管理器中找不到,則到位點管理器secondary
中尋找位點并返回。
//MetaLogPositionManager.getLatestIndexBy
public LogPosition getLatestIndexBy(String destination) {
List<ClientIdentity> clientIdentities = metaManager.listAllSubscribeInfo(destination);
LogPosition result = null;
if (!CollectionUtils.isEmpty(clientIdentities)) {
// 嘗試找到一個最小的logPosition
for (ClientIdentity clientIdentity : clientIdentities) {
//1.2.1
LogPosition position = (LogPosition) metaManager.getCursor(clientIdentity);
if (position == null) {
continue;
}
if (result == null) {
result = position;
} else {
result = CanalEventUtils.min(result, position);
}
}
}
return result;
}
1.2.1.主要邏輯是到metaManager
,也就是PeriodMixedMetaManager
中尋找內(nèi)存中記錄的canal client消費位點信息,找不到則到zk上cursor節(jié)點找。
//PeriodMixedMetaManager.getCursor
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
//從內(nèi)存中獲取該client對應(yīng)的消費位點
Position position = super.getCursor(clientIdentity);
if (position == nullCursor) {
return null;
} else {
return position;
}
}
// super.getCursor
public Position getCursor(ClientIdentity clientIdentity) throws CanalMetaManagerException {
return cursors.get(clientIdentity);
}
cursors中找不到數(shù)據(jù)時將會調(diào)用apply方法初始化client對應(yīng)的數(shù)據(jù)。
cursors = MigrateMap.makeComputingMap(new Function<ClientIdentity, Position>() {
public Position apply(ClientIdentity clientIdentity) {
Position position = zooKeeperMetaManager.getCursor(clientIdentity);
if (position == null) {
return nullCursor; // 返回一個空對象標(biāo)識,避免出現(xiàn)異常
} else {
return position;
}
}
});
1.3.如果上述找到的位點不為空 ,且位點中的gtid也不為空,說明以前就是gtid模式,可以使用這個gtid set作為gtid dump的位點,也就是找到了gtid模式的位點。
1.4.如果上述找到的位點為空,則判斷masterPosition
是否不為空且gtid也不為空,如果滿足則使用masterPosition
作為gtid模式的位點。其中 masterPosition是在該instance配置文件instance.properties
中配置的mysql dump位點。
# position info
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=binlog.000008
canal.instance.master.position=2993
canal.instance.master.timestamp=99999999999999
canal.instance.master.gtid=c6704a02-8354-11e9-a0c3-f054d220296f:1-16
總結(jié)一下
:步驟一找GTID位點的邏輯為:先到primary中尋找canal server中dump下來的binlog event最新位點(內(nèi)存中 ),找不到就到secondary中尋找canal client成功消費位點。如果找到了就判斷位點的gtid是否為空,如果為空則說明以前不是gtid模式,則不支持gtid模式,繼續(xù)步驟二。如果上述找不到位點,則判斷masterPosition
是否不為空,且gtid也不為空,如果滿足則使用masterPosition
作為gtid模式的位點。否則繼續(xù)步驟二。
接下來看步驟二
是如何尋找非GTID模式下的起始位點的。
//MysqlEventParser.findStartPositionInternal
protected EntryPosition findStartPositionInternal(ErosaConnection connection) {
MysqlConnection mysqlConnection = (MysqlConnection) connection;
//2.1
LogPosition logPosition = logPositionManager.getLatestIndexBy(destination);
if (logPosition == null) {// 找不到歷史成功記錄
EntryPosition entryPosition = null;
if (masterInfo != null && mysqlConnection.getConnector().getAddress().equals(masterInfo.getAddress())) {
//2.2.1
entryPosition = masterPosition;
} else if (standbyInfo != null
&& mysqlConnection.getConnector().getAddress().equals(standbyInfo.getAddress())) {
//2.2.2
entryPosition = standbyPosition;
}
if (entryPosition == null) {
//2.2.3
entryPosition = findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默認從當(dāng)前最后一個位置進行消費
}
// 判斷一下是否需要按時間訂閱
if (StringUtils.isEmpty(entryPosition.getJournalName())) {
//2.2.4
// 如果沒有指定binlogName,嘗試按照timestamp進行查找
if (entryPosition.getTimestamp() != null && entryPosition.getTimestamp() > 0L) {
logger.warn("prepare to find start position {}:{}:{}",
new Object[] { "", "", entryPosition.getTimestamp() });
return findByStartTimeStamp(mysqlConnection, entryPosition.getTimestamp());
} else {
logger.warn("prepare to find start position just show master status");
return findEndPositionWithMasterIdAndTimestamp(mysqlConnection); // 默認從當(dāng)前最后一個位置進行消費
}
} else {
//2.2.5
if (entryPosition.getPosition() != null && entryPosition.getPosition() > 0L) {
// 如果指定binlogName + offest,直接返回
entryPosition = findPositionWithMasterIdAndTimestamp(mysqlConnection, entryPosition);
logger.warn("prepare to find start position {}:{}:{}",
new Object[] { entryPosition.getJournalName(), entryPosition.getPosition(),
entryPosition.getTimestamp() });
return entryPosition;
} else {
//2.2.6
EntryPosition specificLogFilePosition = null;
if (entryPosition.getTimestamp() != null && entryPosition.getTimestamp() > 0L) {
// 如果指定binlogName +
// timestamp,但沒有指定對應(yīng)的offest,嘗試根據(jù)時間找一下offest
EntryPosition endPosition = findEndPosition(mysqlConnection);
if (endPosition != null) {
logger.warn("prepare to find start position {}:{}:{}",
new Object[] { entryPosition.getJournalName(), "", entryPosition.getTimestamp() });
specificLogFilePosition = findAsPerTimestampInSpecificLogFile(mysqlConnection,
entryPosition.getTimestamp(),
endPosition,
entryPosition.getJournalName(),
true);
}
}
//2.2.7
if (specificLogFilePosition == null) {
// position不存在,從文件頭開始
entryPosition.setPosition(BINLOG_START_OFFEST);
return entryPosition;
} else {
return specificLogFilePosition;
}
}
}
} else {
//2.1.1
if (logPosition.getIdentity().getSourceAddress().equals(mysqlConnection.getConnector().getAddress())) {
if (dumpErrorCountThreshold >= 0 && dumpErrorCount > dumpErrorCountThreshold) {
//2.1.1.1
// binlog定位位點失敗,可能有兩個原因:
// 1. binlog位點被刪除
// 2.vip模式的mysql,發(fā)生了主備切換,判斷一下serverId是否變化,針對這種模式可以發(fā)起一次基于時間戳查找合適的binlog位點
boolean case2 = (standbyInfo == null || standbyInfo.getAddress() == null)
&& logPosition.getPostion().getServerId() != null
&& !logPosition.getPostion().getServerId().equals(findServerId(mysqlConnection));
if (case2) {
long timestamp = logPosition.getPostion().getTimestamp();
long newStartTimestamp = timestamp - fallbackIntervalInSeconds * 1000;
logger.warn("prepare to find start position by last position {}:{}:{}", new Object[] { "", "",
logPosition.getPostion().getTimestamp() });
EntryPosition findPosition = findByStartTimeStamp(mysqlConnection, newStartTimestamp);
// 重新置為一下
dumpErrorCount = 0;
return findPosition;
}
Long timestamp = logPosition.getPostion().getTimestamp();
if (isRdsOssMode() && (timestamp != null && timestamp > 0)) {
// 如果binlog位點不存在,并且屬于timestamp不為空,可以返回null走到oss binlog處理
return null;
}
}
// 其余情況
//2.1.1.2
logger.warn("prepare to find start position just last position\n {}",
JsonUtils.marshalToString(logPosition));
return logPosition.getPostion();
} else {
//2.1.2
// 針對切換的情況,考慮回退時間
long newStartTimestamp = logPosition.getPostion().getTimestamp() - fallbackIntervalInSeconds * 1000;
logger.warn("prepare to find start position by switch {}:{}:{}", new Object[] { "", "",
logPosition.getPostion().getTimestamp() });
return findByStartTimeStamp(mysqlConnection, newStartTimestamp);
}
}
}
這是一段蠻長的代碼。。。其實我們需要思考的問題是:如何根據(jù)能夠查到的LogPosition的信息(數(shù)據(jù)庫信息,binlog信息)中提煉出最精確的dump位點。廢話不多說,看代碼。
2.1.從logPositionManager中尋找內(nèi)存中最精確的dump位點(先尋找canal server中dump下來的binlog event最新位點,找不到就尋找canal client成功消費位),前面分析過。分為找到位點和找不到位點兩種情況。我們先看找不到的情況 。
2.2.如果從logPositionManager中找不到位點,說明canal server是第一次從該instance dump。那么就要從instance.properties文件中找可能位點。該文件是可以配置一主一從mysql的,這樣可以實現(xiàn)主備切換的mysql dump。上面介紹了masterPosition
的配置選項,standbyPosition
也是同理。
# mysql多節(jié)點解析配置
#canal.instance.standby.address = 10.20.144.29:3306
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
2.2.1.如果mysql主庫信息masterInfo不為null并且當(dāng)前mysqlConnection連接的是主庫,則entryPosition = masterPosition。
2.2.2.否則如果mysql從庫信息masterInfo不為null并且當(dāng)前mysqlConnection連接是從庫,則entryPosition = standbyPosition。
2.2.3.如果此時entryPosition為空,表明canal server沒有指定任何位點,則將當(dāng)前mysqlConnection連接的數(shù)據(jù)庫的binlog最后一個位置作為dump位點并返回。
如果entryPosition能夠從instance.properties中獲得位點信息,則說明entryPosition中journalName
,position
,timestamp
不為空,可通過這些值進一步確定精確的dump位點。
2.2.4.如果entryPosition中journalName
為空但是timestamp
不為空,則嘗試按照timestamp進行查找小于timestamp最接近的binlog事件。findByStartTimeStamp
的邏輯就是返回從最后一個binlog文件往前依次找滿足條件的事務(wù)開始的位點。如果能找到,就返回該位點,找不到則將當(dāng)前mysqlConnection連接的數(shù)據(jù)庫的binlog最后一個位置作為dump位點并返回。
2.2.5.如果entryPosition中journalName
不為空并且position
不為空,則直接返回該位點。
2.2.6.如果entryPosition中journalName
不為空但是position
不為空,則只會在該binlog文件中根據(jù)timestamp
查找小于timestamp最接近的binlog事件。找到就返回該位點。
2.2.7.上述找不到則將當(dāng)前mysqlConnection連接的數(shù)據(jù)庫的binlog最后一個位置作為dump位點并返回。
至此,根據(jù)instance.properties文件中配置的位點信息確定最終dump的位點分析完畢,主旨就是如果binlog filename+position
存在,則直接作為dump位點,否則根據(jù)timestamp
確定。如果最終根據(jù)binlog filename,position,timestamp
確定不了位點,則使用當(dāng)前mysqlConnection連接的數(shù)據(jù)庫的binlog最后一個位置
作為dump位點
回到2.1,如果從logPositionManager中從內(nèi)存中找到位點logPosition
,則說明之前這個instance是從mysql中 dump過數(shù)據(jù)的,需要結(jié)合連接狀態(tài)確定當(dāng)前canal server的狀態(tài)是怎么樣的,然后才能進一步確定dump位點。
2.1.1.logPosition
中mysql數(shù)據(jù)庫信息和當(dāng)前mysqlConnection連接的數(shù)據(jù)庫信息
一致,說明logPosition
就是當(dāng)前mysqlConnection連接的數(shù)據(jù)庫的位點信息。那么為什么又會再次確定dump位點呢?可能原因是eventParser解析過程中發(fā)生dump異常,導(dǎo)致關(guān)閉整個parser過程,再重新啟動parser解析過程。發(fā)生異常的原因可能有:binlog定位位點失敗,mysql的連接臨時斷開又重連上,canal server HA切換等
。
2.1.1.1. dumpErrorCount默認超過2次,則說明是binlog定位位點失敗導(dǎo)致的。可能原因:
binlog位點被刪除
:此時canal server會不停打印“errno = 1236.....”的錯誤日志,代表該位點的binlog在mysql中已經(jīng)找不到了。
vip模式的mysql,發(fā)生了主備切換
:判斷一下serverId是否變化,針對這種模式可以發(fā)起一次基于時間戳查找合適的binlog位點。(應(yīng)該是和阿里云mysql rds vip的模式相關(guān)的,沒用過表示不懂這種場景。。)
2.1.1.2.可能是mysql的連接臨時斷開又重連上,canal server HA切換等
導(dǎo)致的重新parser過程,確定了dump位點后接著用就行。
2.1.2.如果logPosition
中mysql數(shù)據(jù)庫信息和當(dāng)前mysqlConnection連接的數(shù)據(jù)庫信息
不一致,說明發(fā)生了mysql dump主備切換。此時需要根據(jù)timestamp
確定在新的mysql上的dump位點,而不能依賴原來連接的數(shù)據(jù)庫的binlog filename 和position。
步驟三:
在步驟一和步驟二中解析出來的位點不一定是事務(wù)起始事件處的位點,此時在dump過程中可能找不到binlog事件所在table信息,會拋出TableIdNotFoundException
異常,同時將needTransactionPosition設(shè)置為true。到了步驟三會根據(jù)步驟一和步驟二中解析出來的位點確定小于它的最近的事務(wù)起始事件處的位點,作為最終的dump位點。
至此,關(guān)于canal server dump前找mysql同步位點的代碼分析完了,總結(jié)一下:
步驟一:
如果是GTID Mode,則根據(jù) logPositionManager的 primary或者secondary位點管理器
的從內(nèi)存中找位點,找不到從配置文件instance.properties中找。
步驟二:
如果步驟一找不到,說明是根據(jù)binlog filename + position確定dump位點。同理先根據(jù) logPositionManager的 primary或者secondary位點管理器
的從內(nèi)存中找位點,找不到從配置文件instance.properties中找。當(dāng)然會考慮canal server HA切換,dump異常,mysql連接切換對確定位點作出一些調(diào)整。
步驟三:
如果將needTransactionPosition
設(shè)置為true
,會根據(jù)步驟一和步驟二中解析出來的位點確定小于它的最近的事務(wù)起始事件處的位點,作為最終的dump位點。
如果你看懂了上面的碎碎念,不知道是否會疑惑 logPositionManager的 primary或者secondary位點管理器
管理的內(nèi)存位點是如何第一次創(chuàng)建的?之后是如何更新的?下面我們再捋一捋這些過程。
primary的位點管理
上文我們提到primary的初始化過程,primary是在內(nèi)存中記錄位點變化的管理器MemoryLogPositionManager
,對應(yīng)canal server中dump下來的binlog event最新位點。所以每當(dāng)dump到binlog數(shù)據(jù)時便會更新primary位點信息。如何定義canal server dump到binlog數(shù)據(jù)呢?在文章開頭的parser過程步驟5
中,binlog數(shù)據(jù)經(jīng)過Binlog parser—>EventSink傳遞—>存儲到EventStore后,會記錄Binaly Log位點。代碼中會回調(diào)TransactionFlushCallback.flush
方法刷新位點。
public AbstractEventParser(){
// 初始化一下
transactionBuffer = new EventTransactionBuffer(new TransactionFlushCallback() {
public void flush(List<CanalEntry.Entry> transaction) throws InterruptedException {
boolean successed = consumeTheEventAndProfilingIfNecessary(transaction);
if (!running) {
return;
}
if (!successed) {
throw new CanalParseException("consume failed!");
}
//返回的是最大的TRANSACTIONEND類型的事件位點,盡量記錄一個事務(wù)做為position
LogPosition position = buildLastTransactionPosition(transaction);
if (position != null) { // 可能position為空
logPositionManager.persistLogPosition(AbstractEventParser.this.destination, position);
}
}
});
}
EventParser初始化時會初始化一個TransactionFlushCallback匿名類對象,其中flush方法中logPositionManager.persistLogPosition
便會刷新primary中位點。
//FailbackLogPositionManager.persistLogPosition
public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException {
try {
primary.persistLogPosition(destination, logPosition);
} catch (CanalParseException e) {
logger.warn("persistLogPosition use primary log position manager exception. destination: {}, logPosition: {}",
destination,
logPosition,
e);
secondary.persistLogPosition(destination, logPosition);
}
}
secondary位點管理
上文我們提到secondary的初始化過程,secondary是位點管理器MetaLogPositionManager
,它有一個成員變量metaManager是PeriodMixedMetaManager
,是canal client消費信息管理器,如上是定時刷新canal client消費位點信息到zk上的位點管理器。所以每當(dāng)canal client ack時會更新該位點。
//CanalServerWithEmbedded.ack
//客戶端ack時canal server的處理
public void ack(ClientIdentity clientIdentity, long batchId) throws CanalServerException {
checkStart(clientIdentity.getDestination());
checkSubscribe(clientIdentity);
CanalInstance canalInstance = canalInstances.get(clientIdentity.getDestination());
PositionRange<LogPosition> positionRanges = null;
//注意,客戶端是按批次去binlog 數(shù)據(jù),一個batchId可能對應(yīng)多條binlog數(shù)據(jù),由服務(wù)端根據(jù)batchId確定最終ack的位點是什么
positionRanges = canalInstance.getMetaManager().removeBatch(clientIdentity, batchId); // 更新位置
if (positionRanges == null) { // 說明是重復(fù)的ack/rollback
throw new CanalServerException(String.format("ack error , clientId:%s batchId:%d is not exist , please check",
clientIdentity.getClientId(),
batchId));
}
// 更新cursor
if (positionRanges.getAck() != null) {
canalInstance.getMetaManager().updateCursor(clientIdentity, positionRanges.getAck());
if (logger.isInfoEnabled()) {
logger.info("ack successfully, clientId:{} batchId:{} position:{}",
clientIdentity.getClientId(),
batchId,
positionRanges);
}
}
// 可定時清理數(shù)據(jù)
canalInstance.getEventStore().ack(positionRanges.getEnd(), positionRanges.getEndSeq());
}
更新cursor時會更新secondary的位點信息
PeriodMixedMetaManager. updateCursor
public void updateCursor(ClientIdentity clientIdentity, Position position) throws CanalMetaManagerException {
//更新成員變量cursors中的位點
super.updateCursor(clientIdentity, position);
// 添加到任務(wù)隊列中,定時觸發(fā)zk上該instance的位點更新,/otter/canal/destinations/{destination}/{clientId}/cursor節(jié)點
updateCursorTasks.add(clientIdentity);
}
此外,我們知道/otter/canal/destinations/{destination}/{clientId}/cursor節(jié)點是zk上的持久節(jié)點,不知道的參考canal源碼解析-HA模式的實現(xiàn)。那么當(dāng)發(fā)生canal server HA切換時,canal server應(yīng)該從zk上初始化原來的client ack位點信息,避免無用的dump。上面canal server dump前解析位點時步驟1.2.1
便會從zk上拉取原來的client ack位點信息。