天天看點

DistributedLog

因為兩者都是處理日志,資料模型也類似,是以這篇文章主要從技術角度讨論Apache Kafka與DistributedLog的不同點。我們會盡量做到客觀,但由于我們不是Apache Kafka的專家,是以我們可能會對Apache Kafka存在誤解。如果發現有錯,也請大家直接指出。

相關廠商内容

相關贊助商

DistributedLog

首先,讓我們簡單地介紹一下Kafka和DistributedLog的概況。

DistributedLog

圖一:Apache Kafka與Apache DistributedLog

因為同類事物才有可比較的基礎,是以我們隻在本文中把Kafka分區和DistributedLog流相對比。下表列出了兩套系統之間最顯著的不同點。

(點選放大圖像)

DistributedLog

DistributedLog流是以一系列日志分片的形式存在的虛拟流。每個日志分片都以一條BookKeeper賬目的形式存在,并被複制到多個Bookie上。在任意時刻都隻有一個活躍的日志分片接受寫入請求。在特定的時間段過後,或者舊日志分片達到配置大小(由配置的日志分片政策決定)之後,或者日志的屬主出故障之後,舊的日志分片會被封存,一個新的日志分片會被開啟。

Kafka分區和DistributedLog流在資料分片和分布的不同點決定了它們在資料持久化政策和叢集操作(比如叢集擴充)上的不同。

圖二顯示了DistributedLog和Kafka資料模型的不同點

DistributedLog

圖二:Kafka分區與DistributedLog流

一個Kafka分區中的所有資料都儲存在一個代理伺服器上(并被複制到别的代理伺服器上)。在配置的有效期過後資料會失效并被删除。另外,也可以配置政策讓Kafka的分區保留每個主鍵的最新值。

資料分片和分布機制的不同也導緻了維護叢集操作上的不同,擴充叢集操作就是一個例子。

擴充Kafka叢集時,通常現有分區都要做重新分布。重新分布操作會将Kafka分區挪動到不同的副本上,以此達到均衡分布。這就要把整個流的資料從一個副本拷到另一個副本上。我們也說過很多次了,執行重新分布操作時必須非常小心,避免耗盡磁盤和網絡資源。

而擴充DistributedLog叢集的工作方式則截然不同。DistributedLog包含兩層:存儲層(Apache BooKeeper)和服務層(寫入和讀出代理)。在擴充存儲層時,我們隻需要添加更多的Bookie就好了。新的Bookie馬上會被寫入代理發現,并立刻用于寫入新的日志分片。在擴充資料存儲層時不會有任何的重新分布操作。隻在增加服務層時會有重新分布操作,但這個重新分布也隻是移動日志流的屬主權,以使網絡代寬可以在各個代理之間均衡分布。這個重新分布的過程隻與屬主權相關,沒有資料遷移操作。這種存儲層和服務層的隔離不僅僅是讓系統具備了自動擴充的機制,更讓各種不同類型的資源可以獨立擴充。

日志流的屬主會并發地以BookKeeper條目的形式向Bookie中寫入一批記錄,并等待多個Bookie的Quorum結果。Quorum的大小取決于BookKeeper賬目的ack_quorum_size參數,并且可以配置到DistributedLog流的級别。它提供了和Kafka生産者相似的在持久性上的靈活性。在接下來的“複制”一節我們會對比兩者在複制算法上的更多不同之處。

Kafka和DistributedLog都支援端到端的批量操作和壓縮機制。但兩者之間的一點微妙差別是對DistributedLog的寫入操作都是在收到響應之前都先通過fsync刷到硬碟上的,而我們并沒發現Kafka也提供了類似的可靠性保證。

Kafka消費者從主代理伺服器上讀出資料記錄。這個設計的前提就是主代理上在大多數情況下最新的資料都還在檔案系統頁緩存中。從充分利用檔案系統頁緩存和獲得高性能的角度來說這是一個好辦法。

DistributedLog則采用了完全不同的方法。因為各個存儲節點之間沒有明确的主從關系,DistributedLog可以從任意存儲着相關資料的存儲節點上讀出資料。為了獲得可預期的低延遲,DistributedLog引入了一個推理式讀機制,即在超出了配置的讀操作時限之後,它會在不同的副本上再次嘗試擷取資料。這就可能會對存儲節點導緻比Kafka更高的讀壓力。不過,如果将讀逾時時間配成可以讓99%的存儲節點的讀操作都不會逾時,那就可以極大程度地解決延遲問題,隻帶來1%的額外讀壓力。

對于讀的考慮和機制上的不同主要源于複制機制和存儲節點的I/O系統的不同,在下文會繼續讨論。

Kafka用的是ISR複制算法:将一個代理伺服器選為主。所有寫操作都被發送到主代理上,所有處于ISR集合中的從代理都從主代理上讀取和複制資料。主代理會維護一個高水位線(HW,High Watermark),即每個分區最新送出的資料記錄的偏移量。高水位線會不斷同步到從代理上,并周期性地在所有代理上記錄檢查點,以備恢複之用。在所有ISR集合中的副本都把資料寫入了檔案系統(并不必須是磁盤)并向主代理發回了響應之後,主代理才會更新高水位線。

DistributedLog使用的是Quorum投票複制算法,這在Zab、Raft以及Viewstamped Replication等一緻性算法中都很常見。日志流的屬主會并發地把資料記錄寫入所有存儲節點,并在得到超過配置數量的存儲節點投票确認之後,才認為資料已成功送出。存儲節點也隻在資料被顯式地調用flush操作刷入磁盤之後才會響應寫入請求。日志流的屬主也會維護一個日志流的最新送出的資料記錄的偏移量,就是大家知道的Apache BookKeeper中的LAC(LastAddConfirmed)。LAC也會儲存在資料記錄中(來節省額外的RPC調用開銷),并不斷複制到别的存儲節點上。DistributedLog中複本集合的大小是在每個流的每個日志分片級别可配置的。改變複制參數隻會影響新的日志分片,不會影響已有的。

每個Kafka分區都以若幹個檔案的形式儲存在代理的磁盤上。它利用檔案系統的頁緩存和I/O排程機制來得到高性能。Kafka也是是以利用Java的sendfile API來高效地從代理中寫入讀出資料的。不過,在某些情況下(比如消費者處理不及時、随機讀寫等),頁緩存中的資料淘汰很頻繁,它的性能也有很大的不确性性。

DistributedLog用的則是不同的I/O模型。圖三表示了Bookie(BookKeeper的存儲節點)的I/O機制。寫入(藍線)、末尾讀(紅線)和中間讀(紫線)這三種常見的I/O操作都被隔離到了三種實體上不同的I/O子系統中。所有寫入都被順序地追加到磁盤上的日志檔案,再批量送出到硬碟上。在寫操作持久化到磁盤上之後,它們就會放到一個Memtable中,再向用戶端發回響應。Memtable中的資料會被異步重新整理到交叉存取的索引資料結構中:記錄被追加到日志檔案中,偏移量則在分類賬目的索引檔案中根據記錄ID索引起來。最新的資料肯定在Memtable中,供末尾讀操作使用。中間讀會從記錄日志檔案中擷取資料。由于實體隔離的存在,Bookie節點可以充分利用網絡流入帶寬和磁盤的順序寫入特性來滿足寫請求,以及利用網絡流出代寬和多個磁盤共同提供的IOPS處理能力來滿足讀請求,彼此之間不會互相幹擾。

DistributedLog

圖三:BookKeeper的I/O隔離

Kafka和DistributedLog都是設計來處理日志流相關問題的。它們有相似性,但在存儲和複制機制上有着不同的設計理念,是以有了不同的實作方式。希望這篇文章能從技術角度解釋清楚它們的差別,回答一些問題。我們接下來也會再多寫一些文章來講講DistributedLog的性能名額