rocketmq之消息同步與異步刷盤學(xué)習(xí)筆記

一、刷新服務(wù)

image.png

二、GroupCommitService

(一) GroupCommitService核心屬性

  • List<GroupCommitRequest> requestsWrite:請求寫入隊列

  • List<GroupCommitRequest> requestsRead:請求讀隊列

上面兩個隊列會進行交換,每次刷盤請求是寫到requestsWrite隊列中,GroupCommitService處理刷盤請求之前,會執(zhí)行隊列交換

(二) 添加刷盤請求

當消息寫到緩沖池以后,會調(diào)用下面方面進行磁盤刷新和主從復(fù)制

handleDiskFlush(result, putMessageResult, msg);
handleHA(result, putMessageResult, msg);

如果刷盤方式是同步,那么就構(gòu)造一個GroupCommitRequest請求,GroupCommitService服務(wù)異步刷新數(shù)據(jù),如果是異步刷盤,就喚醒異步刷盤服務(wù)

public void handleDiskFlush(AppendMessageResult result, PutMessageResult putMessageResult, MessageExt messageExt) {
    // 同步刷盤
    if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
        final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
        if (messageExt.isWaitStoreMsgOK()) {
            GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());
            // 將刷盤請求放到隊列中
            service.putRequest(request);
            // 使用了CountDownLatch機制,等待刷盤成功
            boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
            if (!flushOK) {
                log.error("do groupcommit, wait for flush failed, topic: " + messageExt.getTopic() + " tags: " + messageExt.getTags()
                    + " client address: " + messageExt.getBornHostString());
                putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
            }
        } else {
            service.wakeup();
        }
    }
    // 異步刷盤
    else {
        if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
            flushCommitLogService.wakeup();
        } else {
            commitLogService.wakeup();
        }
    }
}

(三) 刷盤請求處理

GroupCommitService從隊列中取出刷盤請求,并執(zhí)行刷盤操作

public void run() {
    CommitLog.log.info(this.getServiceName() + " service started");
?
    while (!this.isStopped()) {
        try {
            // 每隔10ms,交換讀寫隊列
            this.waitForRunning(10);
            // 真正的刷盤
            this.doCommit();
        } catch (Exception e) {
            CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }
?
    // Under normal circumstances shutdown, wait for the arrival of the
    // request, and then flush
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        CommitLog.log.warn("GroupCommitService Exception, ", e);
    }
?
    synchronized (this) {
        this.swapRequests();
    }
?
    this.doCommit();
?
    CommitLog.log.info(this.getServiceName() + " service end");
}

每隔10ms,會進行一次讀寫隊列的交換,并調(diào)用doCommit()方法,執(zhí)行真正的磁盤刷新操作。

private void doCommit() {
    synchronized (this.requestsRead) {
        // 讀隊列不為空,遍歷所有刷盤請求,執(zhí)行數(shù)據(jù)刷盤操作
        if (!this.requestsRead.isEmpty()) {
            for (GroupCommitRequest req : this.requestsRead) {
                // There may be a message in the next file, so a maximum of
                // two times the flush
                boolean flushOK = false;
                for (int i = 0; i < 2 && !flushOK; i++) {
                    flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
                    if (!flushOK) {
                        CommitLog.this.mappedFileQueue.flush(0);
                    }
                }
                req.wakeupCustomer(flushOK);
            }
            // 設(shè)置檢查點
            long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
            if (storeTimestamp > 0) {
                CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
            }
            // 處理完刷盤請求以后,執(zhí)行隊列清空操作
            this.requestsRead.clear();
        } else {
            // Because of individual messages is set to not sync flush, it
            // will come to this process
            CommitLog.this.mappedFileQueue.flush(0);
        }
    }
}

三、FlushRealTimeService

(一) 核心屬性

  • long lastFlushTimestamp:最后一次刷盤時間戳

  • long printTimes = 0:

(二) 異步刷盤流程

public void run() {
    while (!this.isStopped()) {
        // 刷新策略(默認是實時刷盤)
        boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();
        // 刷盤間隔
        int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();
        // 每次刷盤至少需要多少個page(默認是4個)
        int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();
        // 徹底刷盤間隔時間(默認10s)
        int flushPhysicQueueThoroughInterval =
            CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();
?
        boolean printFlushProgress = false;
        // Print flush progress
        long currentTimeMillis = System.currentTimeMillis();
        // 當前時間 >(最后一次刷盤時間 + 徹底刷盤間隔時間(10s)),則將最新一次刷盤時間更新為當前時間
        if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {
            this.lastFlushTimestamp = currentTimeMillis;
            flushPhysicQueueLeastPages = 0;
            printFlushProgress = (printTimes++ % 10) == 0;
        }
?
        try {
            if (flushCommitLogTimed) {
                Thread.sleep(interval);
            } else {
                this.waitForRunning(interval);
            }
?
            if (printFlushProgress) {
                this.printFlushProgress();
            }
?
            long begin = System.currentTimeMillis();
            CommitLog.this.org.apache.rocketmq.store.MappedFileQueue#flush;
            long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
            if (storeTimestamp > 0) {
                CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
            }
            long past = System.currentTimeMillis() - begin;
            if (past > 500) {
                log.info("Flush data to disk costs {} ms", past);
            }
        } catch (Throwable e) {
            CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
            this.printFlushProgress();
        }
    }
?
    // Normal shutdown, to ensure that all the flush before exit
    boolean result = false;
    for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {
        result = CommitLog.this.mappedFileQueue.flush(0);
        CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));
    }
?
    this.printFlushProgress();
?
    CommitLog.log.info(this.getServiceName() + " service end");
}

如果是實時刷盤,每隔一定時間間隔,該線程休眠500毫秒,如果不是實時刷盤,則調(diào)用waitForRunning,即每隔500毫秒或該刷盤服務(wù)線程調(diào)用了wakeup()方法之后結(jié)束阻塞。最后調(diào)用 CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages)進行刷盤 。

數(shù)據(jù)刷盤調(diào)用了MappedFileQueue.flush(int flushLeastPages)方法,進行刷盤

public boolean flush(final int flushLeastPages) {
    boolean result = true;
    // 根據(jù)offset,找到對應(yīng)的MappedFile
    MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
    if (mappedFile != null) {
        long tmpTimeStamp = mappedFile.getStoreTimestamp();
        // 調(diào)用MappedFile的刷盤方法
        int offset = mappedFile.flush(flushLeastPages);
        long where = mappedFile.getFileFromOffset() + offset;
        result = where == this.flushedWhere;
        this.flushedWhere = where;
        if (0 == flushLeastPages) {
            this.storeTimestamp = tmpTimeStamp;
        }
    }
    return result;
}

數(shù)據(jù)刷盤,最終是調(diào)用 了MappedFile.flush(int flushLeastPages)方法實現(xiàn)數(shù)據(jù)持久化,具體實現(xiàn)如下:

/**
 * @return The current flushed position
 */
public int flush(final int flushLeastPages) {
    // 判斷是否可以刷盤
    if (this.isAbleToFlush(flushLeastPages)) {
        if (this.hold()) {
            int value = getReadPosition();
            try {
                //We only append data to fileChannel or mappedByteBuffer, never both.
                if (writeBuffer != null || this.fileChannel.position() != 0) {
                    this.fileChannel.force(false);
                } else {
                    this.mappedByteBuffer.force();
                }
            } catch (Throwable e) {
                log.error("Error occurred when force data to disk.", e);
            }
            this.flushedPosition.set(value);
            this.release();
        } else {
            log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());
            this.flushedPosition.set(getReadPosition());
        }
    }
    return this.getFlushedPosition();
}

調(diào)用isAbleToFlush方法判斷是否可以刷盤,判斷邏輯如下:

private boolean isAbleToFlush(final int flushLeastPages) {
    int flush = this.flushedPosition.get();
    int write = getReadPosition();
    // 如果文件已滿
    if (this.isFull()) {
        return true;
    }
    // 需要刷盤頁數(shù) > 最少刷盤頁數(shù)
    if (flushLeastPages > 0) {
        return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages;
    }
    return write > flush;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 今天看到一位朋友寫的mysql筆記總結(jié),覺得寫的很詳細很用心,這里轉(zhuǎn)載一下,供大家參考下,也希望大家能關(guān)注他原文地...
    信仰與初衷閱讀 4,745評論 0 30
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,120評論 1 32
  • 1 概述2 相關(guān)類介紹3 同步刷盤原理4 異步刷盤 1 概述 RocketMQ和其他存儲系統(tǒng)類似,如Redis等,...
    persisting_閱讀 2,619評論 0 1
  • 早上,心情棒棒噠!一位相識的孩子同學(xué)的家長送了一箱橙子,好意外,好感動,禮輕情意重,感恩生活中充滿美好! 投射閨女...
    一陣暖風(fēng)曼曼閱讀 100評論 0 0
  • 習(xí)慣性的討好,會使人迷失自我。究竟哪個才是真實的自己,再茫茫迷霧中,褪去層層的面具,不知道了,似乎已經(jīng)迷失了。
    寫給巧克力的歌閱讀 91評論 0 0