簡介
parser模塊下,AbstractEventParser類中的定時任務不斷的向Mysql的Master節點發送dump()請求,接收binlog數據,把接收到的binlog日誌數據存儲在一個大小爲1024的ringBuffer中,然後再flush到sink模塊,本文將對ringbuffer的操作流程做一個梳理。
核心流程
Ringbuffer的核心包含幾個變量
// 數據實際存儲的數組
private CanalEntry.Entry[] entries;
// 代表當前put操作最後一次寫操作發生的位置
private AtomicLong putSequence = new AtomicLong(INIT_SQEUENCE);
// 代表滿足flush條件後最後一次數據flush的時間
private AtomicLong flushSequence = new AtomicLong(INIT_SQEUENCE);
private static final long INIT_SQEUENCE = -1;
private int bufferSize = 1024;
從上文可以看出,ringbuffer實際上是一個數組entries,下標從0開始,數組大小爲1024,通過putSequence和flushSequence兩個變量(初始化爲-1),可以把這個數組作爲一個環來使用
初始狀態
初始狀態下,putSequence和flushSequence都爲-1
put操作
put操作,會向ringbuffer中添加一個元素,如下圖所示:
上圖是給ringbuffer下標爲0的位置添加了一個元素,put操作的流程如下:
- 檢測是否有空位
- 如果有空位則進行如下操作
- 先給數組裏添加值
- putSequence+1
- 如果沒有空位,則進行如下操作:
- 執行flush()
- 重新執行put()
源碼如下:
private void put(CanalEntry.Entry data) throws InterruptedException {
// 首先檢查是否有空位
if (checkFreeSlotAt(putSequence.get() + 1)) {
long current = putSequence.get();
long next = current + 1;
// 先寫數據,再更新對應的cursor,併發度高的情況,putSequence會被get請求可見,拿出了ringbuffer中的老的Entry值
entries[getIndex(next)] = data;
putSequence.set(next);
} else {
flush();// buffer區滿了,刷新一下
put(data);// 繼續加一下新數據
}
}
查詢是否有空位的代碼如下:
private boolean checkFreeSlotAt(final long sequence) {
final long wrapPoint = sequence - bufferSize;
if (wrapPoint > flushSequence.get()) { // 剛好追上一輪
return false;
} else {
return true;
}
}
下圖是put 3次之後的ringbuffer
flush操作
flush操作會把ringbuffer中的數據一次性全部推送出去,根據上文描述,flush之前ringbuffer中有3條數據,flush之後會全部推送出去,推送後的數組如下圖所示
flush源碼如下
private void flush() throws InterruptedException {
long start = this.flushSequence.get() + 1;
long end = this.putSequence.get();
if (start <= end) {
List<CanalEntry.Entry> transaction = new ArrayList<>();
for (long next = start; next <= end; next++) {
transaction.add(this.entries[getIndex(next)]);
}
flushCallback.flush(transaction);
flushSequence.set(end);// flush成功後,更新flush位置
}
}
flushCallback.flush(transaction)
其實就是在調用sink模塊
ringbuffer資源的重複利用機制
上文中提到,ringbuffer實際上是一個數組,數組本身是沒有首尾相連的,canal通過一定的機制將數組變成了一個邏輯上的圓環,核心邏輯就是getInex()方法:
private int getIndex(long sequcnce) {
return (int) sequcnce & indexMask;
}
該方法中,ringbuffer大小爲1024,因此indexMask=1023,如上圖所示,當給entries中put數據時,會先調用getIndex()獲取當前操作應該使用的下標位置,
比如,當前sequcnce=1023時,實際上已經到了數組的最後一個位置,下一個序列是1024,它對應的下標是1024& 1023=0,
也就是下標爲0的位置,而此時flushSequence位於下標爲2的數組位置,表明數組還沒有滿,下標爲0的位置是空的,可以使用,如此則實現了ringbuffer的資源重複利用
需要注意的是
-
ringbuffer的大小必須是2^n,否則無法實現循環。
-
flush操作只是改變了flushSequence的值,events數組中的數據並沒有被清理,因此events數組在第一次滿了之後會一直處於滿了的狀態,只是會不斷被覆蓋。
Ringbuffer滿了以後如何操作?
如下圖所示,當ringbuffer中已經存滿了之後,新的數據1027無法再添加
此時會先調用一次flush(),將數據推送出去,然後再將1027添加到隊列,如下圖所示