請問你知道分佈式系統設計模式的分割日誌思想麼?

分割日誌(Segmented Log)

將大文件切分爲更容易處理的多個更小的文件。

問題背景

單一的日誌文件可能會增長到很大,並且在程序啓動時讀取從而成爲性能瓶頸。老的日誌需要定時清理,但是對於一個大文件進行清理操作很費勁。

解決方案

將單一日誌切分爲多個,日誌在達到一定大小時,會切換到新文件繼續寫。

//寫入日誌
public Long writeEntry(WALEntry entry) {
    //判斷是否需要另起新文件
    maybeRoll();
    //寫入文件
    return openSegment.writeEntry(entry);
}

private void maybeRoll() {
    //如果當前文件大小超過最大日誌文件大小
    if (openSegment.
            size() >= config.getMaxLogSize()) {
        //強制刷盤
        openSegment.flush();
        //存入保存好的排序好的老日誌文件列表
        sortedSavedSegments.add(openSegment);
        //獲取文件最後一個日誌id
        long lastId = openSegment.getLastLogEntryId();
        //根據日誌id,另起一個新文件,打開
        openSegment = WALSegment.open(lastId, config.getWalDir());
    }
}

如果日誌做了切分,那麼需要快速以某個日誌位置(或者日誌序列號)定位到某個文件的機制。可以通過兩種方式實現:

  • 每一個日誌切分文件的名稱都是包含特定開頭以及日誌位置偏移量(或者日誌序列號)
  • 每一個日誌序列號包含文件名稱以及 transaction 偏移。
//創建文件名稱
public static String createFileName(Long startIndex) {
    //特定日誌前綴_起始位置_日誌後綴
    return logPrefix + "_" + startIndex + "_" + logSuffix;
}

//從文件名稱中提取日誌偏移量
public static Long getBaseOffsetFromFileName(String fileName) {
    String[] nameAndSuffix = fileName.split(logSuffix);
    String[] prefixAndOffset = nameAndSuffix[0].split("_");
    if (prefixAndOffset[0].equals(logPrefix))
        return Long.parseLong(prefixAndOffset[1]);

    return -1l;
}

在文件名包含這種信息之後,讀操作就分爲兩步:

  1. 給定一個偏移(或者 transaction id),獲取到大於這個偏移日誌所在文件
  2. 從文件中讀取所有大於這個偏移的日誌
//給定偏移量,讀取所有日誌
public List<WALEntry> readFrom(Long startIndex) {
    List<WALSegment> segments = getAllSegmentsContainingLogGreaterThan(startIndex);
    return readWalEntriesFrom(startIndex, segments);
}

//給定偏移量,獲取所有包含大於這個偏移量的日誌文件
private List<WALSegment> getAllSegmentsContainingLogGreaterThan(Long startIndex) {
    List<WALSegment> segments = new ArrayList<>();
    //Start from the last segment to the first segment with starting offset less than startIndex
    //This will get all the segments which have log entries more than the startIndex
    for (int i = sortedSavedSegments.size() - 1; i >= 0; i--) {
        WALSegment walSegment = sortedSavedSegments.get(i);
        segments.add(walSegment);

        if (walSegment.getBaseOffset() <= startIndex) {
            break; // break for the first segment with baseoffset less than startIndex
        }
    }

    if (openSegment.getBaseOffset() <= startIndex) {
        segments.add(openSegment);
    }

    return segments;
}

舉例

基本所有主流 MQ 的存儲,例如 RocketMQ,Kafka 還有 Pulsar 的底層存儲 BookKeeper,都運用了分段日誌。

RocketMQ: image

Kafka: image

Pulsar存儲實現BookKeeper: image

另外,基於一致性協議 Paxos 或者 Raft 的存儲,一般會採用分段日誌,例如 Zookeeper 以及 TiDB。

每日一刷,輕鬆提升技術,斬獲各種offer:

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章