slave同步master數據偏移量錯誤

一、背景

master broker.log中出現如下日誌:

2019-12-03 17:42:06 INFO BrokerControllerScheduledThread1 - Slave fall behind master: -6868909625984868736 bytes

slave同步master的數據落後的是負數,難以理解!

二、分析

1 上面的日誌對應DefaultMessageStore如下代碼:

public long slaveFallBehindMuch() {
    return this.commitLog.getMaxOffset() - this.haService.getPush2SlaveMaxOffset().get();
}

其中this.commitLog.getMaxOffset()這個是master的commit log的最大offset,這個不太可能出錯,如果出錯,那消息存儲麻煩就大了,commit log設計可以參考。

那麼就是this.haService.getPush2SlaveMaxOffset().get()這個有問題了。

2 push2SlaveMaxOffset來自於HAService中的屬性,顧名思義:推送給slave的最大偏移量,初始值爲0。

那麼它是什麼時候更新的,參考如下代碼:

public void notifyTransferSome(final long offset) {
    for (long value = this.push2SlaveMaxOffset.get(); offset > value; ) {
        boolean ok = this.push2SlaveMaxOffset.compareAndSet(value, offset);
        if (ok) {
            this.groupTransferService.notifyTransferSome();
            break;
        } else {
            value = this.push2SlaveMaxOffset.get();
        }
    }
}

push2SlaveMaxOffset來自於offset的值,offset是什麼?

  1. 如果slave未同步過master的數據,offset將使用master的commit文件組中最新的一個文件的最小offset,即:同步最新的一個commit文件給slave。
  2. 如果slave已經同步過master數據,那麼offset將使用slave commit文件組的最大的offset,其通過心跳包的方式上報給master。

很明顯,對於第一種情況(slave未同步過master的數據),offset值不可能大於master的最大offset。

對於第二種情況,是有可能的。

3 實驗

模擬slave上報給master的offset過大,很容易聯想到如下步驟:

  1. 啓動一個新的master。
  2. 增加一個新的slave。
  3. 發送一些消息。
  4. 關閉master,並重新部署一個新的同樣brokerName的master。

此時,slave自動變成新master的slave了,但是其commit log的offset是之前master的,肯定大於新master的。

結論:如果重部master,一定要部署新的slave,老的slave重啓沒用。

另外,如果已經發生這樣的情況,移除舊的slave,再部署新的slave並不起作用,因爲master已經持有舊的slave上報上來的offset,notifyTransferSome(final long offset)中的for (… offset > value; )會阻止push2SlaveMaxOffset更新。

4 影響

  1. 上面的情況影響的是同步雙寫模式的master,因爲此模式的master會通過push2SlaveMaxOffset檢測是否寫入到slave了。
  2. 其次,發送錯誤偏移量的slave將無法正確同步數據。
  3. storeerror.log出現大量錯誤日誌,broker.log和store.log出現詭異的日誌,影響主從數據同步情況的判斷。

三、解決方案

  1. broker遷移
    由於存儲文件巨大,一般broker遷移時,採取的方案是下掉舊的broker,部署新的broker的方案。那麼slave一定也要部署新的,即使slave的機器不用遷移,也不能用舊的slave。
  2. 已經存在的broker
    1. 執行停寫。
    2. 下掉slave。
    3. 停止master。
    4. 啓動master。
    5. 部署新的slave。
      注意以上順序,master必須重啓,否則此問題依然存在。

四、其餘情況

導致這樣的情況的還有一種可能,就是非rocketmq slave程序和master的slave端口進行了通信,進行如下測試(假設master在127.0.0.1的10916監聽slave請求,在127.0.0.2進行測試):

  1. telnet 127.0.0.1 10916
  2. 發送999999999
  3. 觀察master日誌:
       2019-12-04 11:35:12 INFO AcceptSocketService - HAService receive new connection, /127.0.0.2:38824
       2019-12-04 11:35:12 INFO ReadSocketService - ReadSocketService service started
       2019-12-04 11:35:12 INFO WriteSocketService - WriteSocketService service started
       2019-12-04 11:35:16 INFO ReadSocketService - slave[/127.0.0.2:38824] request offset 4123389851770370361
       2019-12-04 11:35:16 INFO WriteSocketService - master transfer data from 4123389851770370361 to slave[/127.0.0.2:38824], and slave request 4123389851770370361
       2019-12-04 11:35:36 WARN ReadSocketService - ha housekeeping, found this connection[/127.0.0.2:38824] expired, 20020
       2019-12-04 11:35:36 INFO ReadSocketService - makestop thread ReadSocketService
       2019-12-04 11:35:36 INFO ReadSocketService - makestop thread WriteSocketService
       2019-12-04 11:35:36 INFO ReadSocketService - ReadSocketService service end
       2019-12-04 11:35:36 INFO WriteSocketService - makestop thread WriteSocketService
       2019-12-04 11:35:36 INFO WriteSocketService - makestop thread ReadSocketService
       2019-12-04 11:35:36 INFO WriteSocketService - WriteSocketService service end
       2019-12-04 11:35:53 INFO BrokerControllerScheduledThread1 - slave fall behind master: 27539373 - 4123389851770370361 = -4123389851742830988 bytes

由於rocketmq的同步機制並未進行安全校驗,所以任何程序都有可能鏈接此端口進行通信,導致問題發生。

另附rocketmq解析通信數據的代碼:

if ((this.byteBufferRead.position() - this.processPostion) >= 8) {
    int pos = this.byteBufferRead.position() - (this.byteBufferRead.position() % 8);
    long readOffset = this.byteBufferRead.getLong(pos - 8);
    this.processPostion = pos;

    HAConnection.this.slaveAckOffset = readOffset;
    if (HAConnection.this.slaveRequestOffset < 0) {
        HAConnection.this.slaveRequestOffset = readOffset;
        log.info("slave[" + HAConnection.this.clientAddr + "] request offset " + readOffset);
    }

    HAConnection.this.haService.notifyTransferSome(HAConnection.this.slaveAckOffset);
}

即,只要連接master監聽的slave通信端口,發送數據大於8個字節,就可能導致該問題的產生。

發佈了62 篇原創文章 · 獲贊 23 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章