<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Apache BookKeeper 一致性協(xié)議解析

          共 15024字,需瀏覽 31分鐘

           ·

          2024-07-09 16:30


          導語

          Apache Pulsar 是一個多租戶、高性能的服務間消息傳輸解決方案,支持多租戶、低延時、讀寫分離、跨地域復制(GEO replication)、快速擴容、靈活容錯等特性。Pulsar 存儲層依托于 BookKeeper 組件,所以本文簡單探討一下 BookKeeper(下文簡稱 BK) 的一致性協(xié)議是如何實現(xiàn)的。


          背景


          Pulsar 相對于 Kafka 根本的區(qū)別在于數(shù)據(jù)一致性協(xié)議,這也是為什么 Pulsar 可以做到兩副本就能保障高可用、高可靠,在磁盤使用方面更均衡,也不會存在單分區(qū)容量上限,同時在擴縮容、故障屏蔽等日常運維方面更加靈活和方便。


          一致性協(xié)議簡介


          我們常見的一致性協(xié)議,比如 Raft、Kafka、ZAB 等,都是服務端集成協(xié)議(協(xié)議控制和數(shù)據(jù)存儲綁定),簡單來說一致性協(xié)議由服務端存儲節(jié)點來執(zhí)行。數(shù)據(jù)流向通常是客戶端寫數(shù)據(jù)到 Leader 節(jié)點,其他節(jié)點再通過推或拉的方式從 Leader 獲取數(shù)據(jù)。



          而 BK 的一致性協(xié)議控制和存儲是分開的,協(xié)議控制是在客戶端執(zhí)行,可以稱之為外部一致性協(xié)議,或者客戶端一致性協(xié)議。數(shù)據(jù)流向為客戶端向多臺存儲節(jié)點同時寫入數(shù)據(jù),存儲節(jié)點之間基本不通信。



          由于一致性協(xié)議主要是在客戶端執(zhí)行,本文聚焦于 BK 客戶端的實現(xiàn)。所以,外部一致性協(xié)議需要解決的問題就簡化成,給定 N 個 KV 存儲(Bookie 高性能、低可靠)和一個分布式協(xié)調系統(tǒng)(下文以 ZK 為例, 低性能,高可靠),如何實現(xiàn)高可用、高可靠、低時延的讀寫服務。這里引入 ZK 集群的原因是 Bookie 節(jié)點會動態(tài)增減,需要有注冊中心能讓客戶端拉取存活 Bookie 的 IPPort,同時客戶端不具備存儲能力,協(xié)議控制必要的元信息信息也需要存儲到外部。在詳細了解 BK 一致性協(xié)議之前, 我們先簡單介紹一下BK 提供的基礎能力。


          BookKeeper 基本能力


          協(xié)議作者設計 BK 初衷是提供分布式日志段(Ledger)而不是無界日志(如 Raft、Kafka)。所以 BK 提供是主要能力就是創(chuàng)建刪除 Ledger 以及 Ledger 的讀寫操作。代碼片段如下:


          public interface BookKeeper extends AutoCloseable {    CreateBuilder newCreateLedgerOp(); // 創(chuàng)建新 Ledger     OpenBuilder newOpenLedgerOp(); // 打開已存在Ledger    DeleteBuilder newDeleteLedgerOp(); // 刪除 Ledger   }
          public interface ReadHandle extends Handle { CompletableFuture<LedgerEntries> readAsync(long firstEntry, long lastEntry); // 從 ledger 里讀數(shù)據(jù)}
          public interface WriteHandle extends ReadHandle, ForceableHandle { CompletableFuture<Long> appendAsync(ByteBuf data); // 往ledger 寫數(shù)據(jù)}


          而 MQ 場景需要提供無界數(shù)據(jù)流,所以 Pulsar 為每一個分區(qū)維護一組 Ledger,每個 Ledger 只會寫一定量數(shù)據(jù)(條數(shù) | 大小 | 時間限制),寫滿就會創(chuàng)建新的 Ledger 來繼續(xù)寫入,只有最新的 Ledger 可以寫入,歷史的 Ledger 只能讀取。再根據(jù)用戶配置的數(shù)據(jù)保存策略逐步刪除歷史 Ledger。Pulsar 內分區(qū) Legder 組成如下:



          Pulsar 內分區(qū)的元數(shù)據(jù)如下:


          {  "lastLedgerCreatedTimestamp": "2024-06-06T17:03:54.666+08:00",  "waitingCursorsCount": 0,  "pendingAddEntriesCount": 2,  "lastConfirmedEntry": "11946613:185308",  "state": "LedgerOpened",  "ledgers": [{    "ledgerId": 11945498,    "entries": 194480,    "size": 420564873,    "offloaded": false,    "underReplicated": false  }, {    "ledgerId": 11946057,    "entries": 194817,    "size": 420583702,    "offloaded": false,    "underReplicated": false  }, {    "ledgerId": 11946613,    "entries": 0, // 未關閉的ledger,還沒有條數(shù)    "size": 0, // 未關閉的ledger,還沒有大小    "offloaded": false,    "underReplicated": false  }]}


          創(chuàng)建 Ledger


          BK 以 Ledger 維度對外提供服務,第一步就是創(chuàng)建 Ledger。一致性協(xié)議高可用是由數(shù)據(jù)的多副本存儲來實現(xiàn)的,所以創(chuàng)建 Ledger 時需要明確指定該 Ledger 的數(shù)據(jù)存儲在幾臺 Bookie(E),每條數(shù)據(jù)寫幾份(Qw),需要等到多少臺 Bookie 確認收到數(shù)據(jù)后才認為數(shù)據(jù)寫入成功(Qa)。代碼片段如下:


          public interface CreateBuilder extends OpBuilder<WriteHandle> {    // E: 接收該 Ledger 數(shù)據(jù)的節(jié)點數(shù) 默認值3    CreateBuilder withEnsembleSize(int ensembleSize);    /**      * Qw: 每條數(shù)據(jù)需要寫幾個節(jié)點, 默認值2     * 如果 E > Qw, 數(shù)據(jù)會條帶化均勻寫入到 Bookie 節(jié)點,按照默認配置,     * 寫入順序為: 消息0=0,1; 消息1=1,2; 消息3=2,0     */    CreateBuilder withWriteQuorumSize(int writeQuorumSize);    // Qa: 需要等待幾臺節(jié)點確認寫入成功,默認值2    CreateBuilder withAckQuorumSize(int ackQuorumSize);


          ps: 配置條帶化寫入(E > Qw)可以讓多個節(jié)點分攤該 Ledger 的讀寫壓力,但是由于數(shù)據(jù)會更分散且不連續(xù),會影響服務端讀寫優(yōu)化(順序寫、預讀、批讀等), 所以生產環(huán)境建議配置 E=Qw。而 Qa < Qw 可以讓客戶端在避免等待慢節(jié)點返回從而降低寫入時延,但是優(yōu)化效果有限,同時增加了存儲成本,可以配合客戶端熔斷以及服務端指標來剔除慢節(jié)點。簡單來說,建議生產環(huán)境配置 E=Qw=Qa。


          在創(chuàng)建 Ledger 時,客戶端會從 ZK 中拉取全部 Bookie,根據(jù) EnsemblePlacementPolicy 放置策略挑選節(jié)點,默認策略會隨機挑選出 E 個數(shù)的節(jié)點作為初始存儲節(jié)點以保證存儲節(jié)點使用均衡。同時 BK 提供豐富的放置策略滿足用戶實現(xiàn)跨機房、地區(qū)容災的需求。



          現(xiàn)在我們已經有了 Ledger 基本元數(shù)據(jù),包含 Ledger 配置信息以及初始的 Bookie 列表,由于 Ledger 需要全局唯一,所以還需要使用 ZK 獲取一個全局唯一的ID,最后把全部元數(shù)據(jù)寫入到 ZK 集群即可(創(chuàng)建過程無需與 Bookie 節(jié)點交互)。代碼如下:



          BK 中 Ledger 元數(shù)據(jù)信息如下:


          LedgerMetadata {  formatVersion = 3, ensembleSize = 3, writeQuorumSize = 3, ackQuorumSize = 3, state = CLOSED, length = 461178392, lastEntryId = 203045, digestType = CRC32C, password = base64: , ensembles = {    0 = [127.0 .0 .1: 3181, 127.0 .0 .2: 3181, 127.0 .0 .3: 3181]  }, customMetadata = {    component = base64: bWFYWdlZC1sZWRnZXI = ,    pulsar / managed - ledger = base64: cHVibGjL2RlZmF1bHQvcGVyc2lzdGVudC9kd2RfMjEwODQtcGFydGl0aW9uLTA = ,    application = base64: cHVsc2Fy  }}


          數(shù)據(jù)寫入


          Ledger 已經創(chuàng)建好了,現(xiàn)在可以往里面寫數(shù)據(jù)了。由于 Bookie 是個 KV 存儲,數(shù)據(jù)需要全局唯一的 Key, Ledger Id 已經是全局唯一了,并且只有創(chuàng)建這個 Ledger 的客戶端才能往里面寫數(shù)據(jù),所以數(shù)據(jù)的 Key 為 LedgerId + EntryId(客戶端單調自增即可)。



          每次寫入都從 E 個Bookie 中順序挑選出 Qw 個節(jié)點(writeSet)并行發(fā)送數(shù)據(jù)。



          可以看到客戶端并行寫入 Bookie 節(jié)點,Bookie 間也不需要做數(shù)據(jù)同步,所以 Pulsar 即使加了一層 Broker 層,但是寫入時延還是能做到和 Kafka 基本一致(2RTT)。


          正常返回


          發(fā)送數(shù)據(jù)后,如果收到 Qa 個數(shù)成功響應, 就可以認為這條數(shù)據(jù)寫入成功了。同時這條數(shù)據(jù)也可以被讀取。



          LAC(LastAddConfirmed)


          由于 EntryId 單調遞增,數(shù)據(jù)按順序寫入,當一個位置的數(shù)據(jù)成功寫入到 Qa 個存儲節(jié)點后,就意味著該條數(shù)據(jù)以及之前的所有數(shù)據(jù)都可以被消費走,這個位置就是可讀位置(不考慮事務),由于已寫入多副本,也意味著這些數(shù)據(jù)可以在部分存儲節(jié)點故障下存活,從而保證讀一致性。不同協(xié)議中有不同的叫法。在 BK 中,這個位置稱為 LAC(LastAddConfirmed),Kafka 中稱為 HW(High Watermark),在 Raft 中稱為 Commitindex。


          下面代碼可以看到,成功寫入一條數(shù)據(jù)后會立即更新可讀位置。



          LAC 是一致性協(xié)議至關重要的信息,正常情況下,LAC 只需要維護在內存中,寫入成功后更新。讀取時使用 LAC 來限制讀取位置即可。如果當前 Ledger 寫滿了,正常關閉,需要把 LAC 也保存在 ZK 的元數(shù)據(jù)中。這樣重新打開 Ledger 用于讀取時,才能加載 LAC 回內存中,用來正確限制讀取位置,同時 LAC 值也能標識 Ledger 中存儲的數(shù)據(jù)條數(shù)。


          而異常情況下,故障會直接導致內存中的 LAC 丟失,這是不可接受的。最簡單粗暴的方式是每次更新 LAC 都直接同步到 ZK 中,但顯然 ZK 性能無法滿足高頻率寫入,所以 BK 的做法是在每條數(shù)據(jù)都攜帶 LAC 信息一起寫入到 Bookie 中。



          如下圖,由于最新的 LAC 需要等下一輪的消息寫入才會一起存儲到 Bookie,所以 Bookie 中存儲的 LAC 與實際的 LAC 相比是存在一定滯后。



          上圖可看到初始時客戶端和 Bookie 中的 LAC 都為-1,在一輪寫入后,Bookie 中的 LAC 就會滯后于客戶端了,好在這個問題并不會影響到一致性協(xié)議的正確性,下文中會提到。


          故障處理


          因為需要容忍一定數(shù)據(jù)的節(jié)點故障,所以一致性協(xié)議復雜的部分都在故障處理邏輯。接下來我們先看寫入失敗場景。


          寫入失敗


          Kafka、Raft 等集成一致性協(xié)議,部分存儲節(jié)點異常處理相對簡單,如果是 Leader 節(jié)點故障,切換到其他節(jié)點進行讀寫即可。如果是 Follower 節(jié)點異常,通常不需要做任何操作,只需要等節(jié)點恢復后,從 Leader 節(jié)點同步數(shù)據(jù)補齊差異。由于節(jié)點是帶存儲的,所以可以容忍較長時間的節(jié)點故障。而 BK 是在客戶端實現(xiàn)的一致性協(xié)議,客戶端不帶存儲,沒寫成功的數(shù)據(jù)需要緩存在內存里,顯然可緩存的數(shù)據(jù)是非常有限的,同時沒有寫成功 LAC 是不能推進的,整個寫入也就停止了。所以 BK 在寫入失敗時,最好是方式就是挑選新的 Bookie 節(jié)點重新寫入,創(chuàng)建 Ledger 時已經指定了初始的 Bookie 列表,后續(xù)的 Bookie 列表變更都稱為 EnsembleChange。


          EnsembleChange


          如下圖所示,寫入失敗時會替換新節(jié)點重新寫入,同時也會把新元數(shù)據(jù)更新到 ZK。每次 EnsembleChange,Ensembles 中就會新增一個 Fragment,起始位置為當前 LAC+1。



          寫入存儲節(jié)點失敗簡單可以分兩種情況,如果還沒有收到Qa 個數(shù)成功響應前,收到了錯誤響應(比如超時等),會立即執(zhí)行 EnsembleChange。如果已經收到了 Qa 個數(shù)成功響應(更新 LAC),后續(xù)的錯誤響應只會記錄下失敗 Bookie 節(jié)點,在下一次寫入時再觸發(fā) EnsembleChange。如果是不可恢復的異常,會直接返回寫入失敗,不會做 EnsembleChange。



          EnsembleChange 和初始創(chuàng)建 Ledger 一樣,需要先選擇新的 Bookie 節(jié)點列表,替換掉失敗的 Bookie 節(jié)點列表,然后把替換后的元數(shù)據(jù)更新回 ZK,代碼如下:



          更新完元數(shù)據(jù)后,重寫數(shù)據(jù)到新節(jié)點,或者根據(jù)條件重新觸發(fā) EnsembleChange,代碼如下:



          簡而言之,在寫入 Bookie 異常時,BK 客戶端都會嘗試切換 Bookie 節(jié)點重寫數(shù)據(jù)。如果遇到無法恢復的狀態(tài)碼,就會直接往外層拋出異常。上層使用方比如 Pulsar 側接收到這個異常后,會正常關閉當前 Ledger,然后創(chuàng)建新的 Ledger 繼續(xù)寫入。


          客戶端故障


          如果存儲 Bookie 節(jié)點故障,客戶端可以切換其他 Bookie 繼續(xù)完成寫入。接下來我們看看如果正在寫入的客戶端故障了,應該怎么處理。在 Pulsar 架構里,BK 客戶端是 Pulsar Broker 內的一個組件,當 Pulsar Broker 故障后,分區(qū)的 Leader 會切換到新 Broker 繼續(xù)服務。由于 BK 客戶端故障時沒有來得及正確關閉 Ledger。當前 Ledger 數(shù)據(jù)是不可讀的,因為 LAC 信息已經丟失。所以新 Broker 主要任務就是要恢復 LAC 信息,然后正確關閉 Ledger,這樣 Ledger 內數(shù)據(jù)就可以正確讀取。最后再創(chuàng)建新的 Ledger 來恢復寫入。由于恢復過程對應分區(qū)無法寫入,所以要求恢復過程越快越好,不可存在長時間阻塞。


          Fence Bookie


          在恢復之前,由于老 Broker 有可能是短暫假死(比如長時間 gc、網絡隔離等),后續(xù)可能還會持續(xù)向當前 Ledger 寫入數(shù)據(jù)導致腦裂。所以恢復的第一步就是要通知對應 Bookie 節(jié)點,后續(xù)禁止往這個 ledger 里面寫任何數(shù)據(jù),這個過程稱為 Fence。過程相對簡單,就是向所有 Essemble 中的 Bookie 節(jié)點發(fā)送請求讓其禁止該 Ledger 的后續(xù)寫入。如下圖所示:



          由于當前 Bookie 節(jié)點不能保證全部存活,同時需要滿足快速恢復,所以需要考慮客戶端至少收到多少個成功響應,才能認為 Fence 操作執(zhí)行成功。這里可以反過來想一下,客戶端每次需要寫入 Qw 個節(jié)點,然后收到 Qa 個成功響應就能認為寫入成功,我們只需要讓其湊不足 Qa 個成功響應就好了,也就是我們 Fence 掉的節(jié)點數(shù)只要大于 Qw - Qa,那寫入客戶端就一定湊不足 Qa 個成功響應,我們暫且稱這個數(shù)量為 Qf。例子如下:


          Qw = 5, Qa = 3 ==> Qf = 5 - 3 + 1 = 3Qw = 3, Qa = 2 ==> Qf = 3 - 2 + 1 = 2 Qw = 3, Qa = 3 ==> Qf = 3 - 3 + 1 = 1  // Qw=Qa 場景需要fence的節(jié)點數(shù)最少,恢復最快


          由于每次寫入都要從 E 個 Bookie 中挑選出 Qw 個節(jié)點來條帶化寫入,所以還需要保證任意一組 Qw,我們都 Fence 掉了對應的 Qf 個節(jié)點。例子如下:


          假設 E=3,Qw=Qa=2,  Bookies列表為0,1,2, Qf=2-2+1=1由于會條帶化寫入,每次寫入挑選兩臺節(jié)點,全部組合為:0,1  // 0,1節(jié)點至少要成功fence 掉一臺1,2  // 1,2節(jié)點至少要成功fence 掉一臺     2,0  // 2,0節(jié)點至少要成功fence 掉一臺易得 0,1,2 三臺節(jié)點節(jié)點至少要成功fence 掉兩臺,同理可證如果按上文中推薦配置 E=Qw=Qa, 任何情況下都只需要fence掉任意1臺節(jié)點即可,fence 節(jié)點數(shù)量最少,恢復最快。


          實際 BK 中實現(xiàn)的代碼邏輯是統(tǒng)計任意 writeSet 未成功響應數(shù)大于等于 Qa 都認為未成功(功能一致),代碼如下:



          恢復 LAC


          恢復主要的任務就是恢復 LAC 信息,上文中已經介紹 LAC 信息位于每條數(shù)據(jù)中存儲在 Bookie 里面。接下來看看恢復整個過程。


          修改元數(shù)據(jù)狀態(tài)


          第一步: 把 Ledger 狀態(tài)改成 Recovery 狀態(tài)。



          讀取初始 LAC


          第二步: 讀取 Bookie 中的 LAC,下圖代碼中可以看到 Fence Bookie 操作其實與查詢最后 LAC 為同一個請求,只是攜帶了 Fence 標識。



          取最大的 Bookie LAC 值返回。



          初始 LAC = max(zkLac, bookieLac)作為初始 LAC。



          恢復真實 LAC


          由于 Bookie 中存儲的 LAC 有滯后性,也就是真實 LAC(故障前內存中的LAC)往往大于目前從 Bookie 查詢到的 LAC 最大值。所以我們還需要恢復真實的 LAC,在找到真實的 LAC之前,我們先確定一個基本的原則。最終恢復的 LAC 可以大于真實 LAC,但是絕對不能小于真實 LAC(會導致數(shù)據(jù)丟失)。比如一條數(shù)據(jù)已經成功寫入到 Qa 個節(jié)點中,但是客戶端還沒來得及接受到 Qa 個成功響應(不更新 LAC)就故障了,那么恢復時把這條數(shù)據(jù) EntryID 更新到 LAC 也是合理的。換句話說,當數(shù)據(jù)成功寫入到了 Qa 節(jié)點中的那一刻,真實 LAC 就應該立即更新,只不過客戶端內存中的 LAC 也存在一定的滯后(需等收到響應)。所以恢復過程可以以當前讀取到的 LAC 作為起始 LAC, 依次往后面查詢下一條數(shù)據(jù)(LAC + 1),如果這條數(shù)據(jù)已經存在到 Qa 個節(jié)點中,那這條數(shù)據(jù)就是可恢復的,向前推進 LAC 并查詢下一條數(shù)據(jù)。直到某條數(shù)據(jù)存在的節(jié)點數(shù)少于 Qa , 就可以認定這條數(shù)據(jù)是不可恢復的。那當前的 LAC 就是真實的 LAC, 恢復過程結束。以上方案判斷某條數(shù)據(jù)是否可恢復是根據(jù)收到的存在響應大于等于 Qa,所以對于每條數(shù)據(jù)都需要查詢全部節(jié)點。BK 客戶端實際上使用另一種更加快速的方式來判斷數(shù)據(jù)是否可恢復。數(shù)據(jù)是否可恢復判斷方式如下:


          • 串行發(fā)送讀請求

          • 如果收到存在響應,認定數(shù)據(jù)可恢復,推進 LAC, 繼續(xù)恢復下一條數(shù)據(jù)。

          • 其余響應依次讀取下一個 Bookie 存儲節(jié)點

          • 如果已經有 Qf (Fence 章節(jié)中定義)個節(jié)點明確返回不存在該數(shù)據(jù),表明該數(shù)據(jù)不可能存在于 Qa 個節(jié)點上,后續(xù)的數(shù)據(jù)也不用查詢了,恢復結束,當前 LAC 為最終需要恢復的 LAC。


          下面使用偽代碼描述如果判斷單條數(shù)據(jù)是否可恢復。


          int notFoundCount = 0;for(Bookie bookie : writeSet) {   result = bookie.query(lac + 1);    switch( result) {       case : "found"           return "數(shù)據(jù)可恢復"; // 可恢復,推進LAC 讀取下一條數(shù)據(jù)       case : "not found"              if( ++ notFoundCount >= Qf) {                // 終止恢復,當前 LAC 作為最終 LAC                return "數(shù)據(jù)無法恢復";               };           break;          case : "unkonwn" // 連接不上、超時等   }}throw Exception("多節(jié)點未知,終止恢復") // 無法恢復,等待重試


          舉例如下:


          Qw=3, Qa = 2, Qf = 3 - 2 + 1 = 2向bookie0, bookie1, bookie2 同時發(fā)出讀取請求
          1. 收到 bookie0 或 bookie1 或 bookie2 存在響應,可恢復2. bookie0 否定回答,bookie1 否定回答, bookie2 未知回答,不可恢復3. bookie0 否定回答,bookie1 未知回答, bookie2 未知回答,恢復失敗,需要重試


          需要特別注意,由于 LAC 非常重要,如果最終恢復的 LAC 小于實際 LAC,就會發(fā)生日志截斷,相當于這段數(shù)據(jù)就丟失了。所以我們恢復過程中,需要明確收到 Qf 個Bookie 節(jié)點返回數(shù)據(jù)不存在,而未知返回(節(jié)點暫時不可用,超時等)不能等同于數(shù)據(jù)不存在。


          以上方案為了快速恢復,只需要任意一臺節(jié)點返回數(shù)據(jù)存在,就認為可恢復,從而推進 LAC。假設這條數(shù)據(jù)實際上存在份數(shù)不足 Qa,就不能容忍后續(xù)對應的存儲節(jié)點故障了。所以會把這條數(shù)據(jù)覆蓋寫入全部的存儲節(jié)點(寫成功后自然 LAC 會更新),寫入失敗(未收到 Qa 個成功)會導致恢復失敗。Bookie 是 KV 存儲,支持冪等覆蓋寫。


          代碼如下:


          開始執(zhí)行恢復 LAC。



          發(fā)起讀請求。



          返回失敗后,向下一個節(jié)點發(fā)送讀請求,如果收到 Qf 個不存在響應,恢復終止。



          讀取成功(包含失敗重試其他節(jié)點): 回寫這條數(shù)據(jù)回 Bookie(LAC自然推進), 觸發(fā)下一條數(shù)據(jù)恢復 讀取失敗(Qf 個節(jié)點明確返回數(shù)據(jù)不存在): 當前 LAC 就為需要恢復的真正 LAC,恢復成功。



          消費


          消費邏輯相對簡單,當前 Ledger 在寫入時,只有當前寫入的客戶端可以讀取數(shù)據(jù),使用內存中的 LAC 防止讀取越線。其他客戶端只能讀取已關閉的 Ledger,首先從 ZK 中獲取元數(shù)據(jù)(包含 LAC),然后正常向對應 Bookie 發(fā)起請求即可。失敗后嘗試下一個 Bookie 節(jié)點,直到成功或者所有節(jié)點都嘗試過。恢復 LAC 的讀取過程和正常讀取數(shù)據(jù)使用同一套邏輯,且相對簡單,這里就不做代碼分析。


          可以看到 BK 客戶端讀取消息是按單條消息來讀取的,會造成請求數(shù)較多。高版本 BK 已經做了一定優(yōu)化,客戶端提供了批讀能力,可以和服務端一次交互就讀到多條消息。這里有個前提條件,就是數(shù)據(jù)不能條帶化寫入,因為條帶化寫入會讓數(shù)據(jù)分散到多臺節(jié)點,單臺節(jié)點內數(shù)據(jù)不連續(xù),所以生產環(huán)境還是建議配置 E=Qw=Qa 。


          結語


          本文主要從客戶端視角分析 BK 一致性協(xié)議設計理念以及實現(xiàn)原理,包含 Leger 創(chuàng)建、數(shù)據(jù)的寫入、讀取以及客戶端服務端故障恢復等內容。可以看到 BK 的一致性協(xié)議還是有一些有趣的地方,并且實實在在的解決了一些問題,也能理解到 Pulsar 的存算分離并不是簡單的加一層無狀態(tài)代理層來實現(xiàn)的。


          往期

          推薦


          PolarisMesh源碼系列--Polaris-Go注冊發(fā)現(xiàn)流程

          PolarisMesh源碼系列——服務如何注冊

          PolarisMesh系列文章——源碼系列(服務端啟動流程)》

          Flink 基于 TDMQ for Apache Pulsar 的離線場景使用實踐

          海量消息下王者榮耀在 TDMQ Pulsar 版的實踐

          騰訊云 TDMQ for Apache Pulsar 多地區(qū)高可用容災實踐

          可觀測新能力:TDMQ Pulsar 支持接入 Prometheus 監(jiān)控

          《Pulsar 產品新形態(tài):彈性存儲能力全新上線!




          掃描下方二維碼關注本公眾號,

          了解更多微服務、消息隊列的相關信息!

          解鎖超多鵝廠周邊!

          戳原文,查看更多消息隊列 Pulsar 版的信息!


          點個在看你最好看


          瀏覽 63
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  自拍青青在线视频 | 水蜜桃成人网站 | 天天澡日日久 | 少妇视频成人 | A片黄色视频 |