<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>

          Redis、Kafka 和 Pulsar 消息隊列對比

          共 14226字,需瀏覽 29分鐘

           ·

          2021-05-10 18:58


          導語 | 市面上有非常多的消息中間件,rabbitMQ、kafka、rocketMQ、pulsar、 redis等等,多得令人眼花繚亂。它們到底有什么異同,你應該選哪個?本文嘗試通過技術演進的方式,以redis、kafka和 pulsar為例,逐步深入,講講它們架構和原理,幫助你更好地理解和學習消息隊列。文章作者:劉德恩,騰訊IEG研發(fā)工程師。


          一、最基礎的隊列



          最基礎的消息隊列其實就是一個雙端隊列,我們可以用雙向鏈表來實現(xiàn),如下圖所示:



          • push_front:添加元素到隊首;
          • pop_tail:從隊尾取出元素。


          有了這樣的數(shù)據結構之后,我們就可以在內存中構建一個消息隊列,一些任務不停地往隊列里添加消息,同時另一些任務不斷地從隊尾有序地取出這些消息。添加消息的任務我們稱為producer,而取出并使用消息的任務,我們稱之為consumer。


          要實現(xiàn)這樣的內存消息隊列并不難,甚至可以說很容易。但是如果要讓它能在應對海量的并發(fā)讀寫時保持高效,還是需要下很多功夫的。

          二、Redis的隊列



          redis剛好提供了上述的數(shù)據結構——list。redis list支持:


          • lpush:從隊列左邊插入數(shù)據;
          • rpop:從隊列右邊取出數(shù)據。


          這正好對應了我們隊列抽象的push_front和pop_tail,因此我們可以直接把redis的list當成一個消息隊列來使用。而且redis本身對高并發(fā)做了很好的優(yōu)化,內部數(shù)據結構經過了精心地設計和優(yōu)化。所以從某種意義上講,用redis的list大概率比你自己重新實現(xiàn)一個list強很多。


          但另一方面,使用redis list作為消息隊列也有一些不足,比如:


          • 消息持久化:redis是內存數(shù)據庫,雖然有aof和rdb兩種機制進行持久化,但這只是輔助手段,這兩種手段都是不可靠的。當redis服務器宕機時一定會丟失一部分數(shù)據,這對于很多業(yè)務都是沒法接受的。
          • 熱key性能問題:不論是用codis還是twemproxy這種集群方案,對某個隊列的讀寫請求最終都會落到同一臺redis實例上,并且無法通過擴容來解決問題。如果對某個list的并發(fā)讀寫非常高,就產生了無法解決的熱key,嚴重可能導致系統(tǒng)崩潰。
          • 沒有確認機制:每當執(zhí)行rpop消費一條數(shù)據,那條消息就被從list中永久刪除了。如果消費者消費失敗,這條消息也沒法找回了。你可能說消費者可以在失敗時把這條消息重新投遞到進隊列,但這太理想了,極端一點萬一消費者進程直接崩了呢,比如被kill -9,panic,coredump…
          • 不支持多訂閱者:一條消息只能被一個消費者消費,rpop之后就沒了。如果隊列中存儲的是應用的日志,對于同一條消息,監(jiān)控系統(tǒng)需要消費它來進行可能的報警,BI系統(tǒng)需要消費它來繪制報表,鏈路追蹤需要消費它來繪制調用關系……這種場景redis list就沒辦法支持了。
          • 不支持二次消費:一條消息rpop之后就沒了。如果消費者程序運行到一半發(fā)現(xiàn)代碼有bug,修復之后想從頭再消費一次就不行了。


          對于上述的不足,目前看來第一條(持久化)是可以解決的。很多公司都有團隊基于rocksdb leveldb進行二次開發(fā),實現(xiàn)了支持redis協(xié)議的kv存儲。這些存儲已經不是redis了,但是用起來和redis幾乎一樣。它們能夠保證數(shù)據的持久化,但對于上述的其他缺陷也無能為力了。


          其實redis 5.0開始新增了一個stream數(shù)據類型,它是專門設計成為消息隊列的數(shù)據結構,借鑒了很多kafka的設計,但是依然還有很多問題…直接進入到kafka的世界它不香嗎?

          三、Kafka



          從上面你可以看到,一個真正的消息中間件不僅僅是一個隊列那么簡單。尤其是當它承載了公司大量業(yè)務的時候,它的功能完備性、吞吐量、穩(wěn)定性、擴展性都有非常苛刻的要求。kafka應運而生,它是專門設計用來做消息中間件的系統(tǒng)。


          前面說redis list的不足時,雖然有很多不足,但是如果你仔細思考,其實可以歸納為兩點:


          • 熱key的問題無法解決,即:無法通過加機器解決性能問題;
          • 數(shù)據會被刪除:rpop之后就沒了,因此無法滿足多個訂閱者,無法重新從頭再消費,無法做ack。


          這兩點也是kafka要解決的核心問題。


          熱key的本質問題是數(shù)據都集中在一臺實例上,所以想辦法把它分散到多個機器上就好了。為此,kafka提出了partition的概念。一個隊列(redis中的list),對應到kafka里叫topic。kafka把一個topic拆成了多個partition,每個partition可以分散到不同的機器上,這樣就可以把單機的壓力分散到多臺機器上。因此topic在kafka中更多是一個邏輯上的概念,實際存儲單元都是partition。


          其實redis的list也能實現(xiàn)這種效果,不過這需要在業(yè)務代碼中增加額外的邏輯。比如可以建立n個list,key1, key2, ..., keyn,客戶端每次往不同的key里push,消費端也可以同時從key1到keyn這n個list中rpop消費數(shù)據,這就能達到kafka多partition的效果。所以你可以看到,partition就是一個非常樸素的概念,用來把請求分散到多臺機器。


          redis list中另一個大問題是rpop會刪除數(shù)據,所以kafka的解決辦法也很簡單,不刪就行了嘛。kafka用游標(cursor)解決這個問題。



          和redis list不同的是,首先kafka的topic(實際上是partion)是用的單向隊列來存儲數(shù)據的,新數(shù)據每次直接追加到隊尾。同時它維護了一個游標cursor,從頭開始,每次指向即將被消費的數(shù)據的下標。每消費一條,cursor+1 。通過這種方式,kafka也能和redis list一樣實現(xiàn)先入先出的語義,但是kafka每次只需要更新游標,并不會去刪數(shù)據。


          這樣設計的好處太多了,尤其是性能方面,順序寫一直是最大化利用磁盤帶寬的不二法門。但我們主要講講游標這種設計帶來功能上的優(yōu)勢。


          首先可以支持消息的ACK機制了。由于消息不會被刪除,因此可以等消費者明確告知kafka這條消息消費成功以后,再去更新游標。這樣的話,只要kafka持久化存儲了游標的位置,即使消費失敗進程崩潰,等它恢復時依然可以重新消費


          第二是可以支持分組消費:



          這里需要引入一個消費組的概念,這個概念非常簡單,因為消費組本質上就是一組游標。對于同一個topic,不同的消費組有各自的游標。監(jiān)控組的游標指向第二條,BI組的游標指向第4條,trace組指向到了第10000條……各消費者游標彼此隔離,互不影響。


          通過引入消費組的概念,就可以非常容易地支持多業(yè)務方同時消費一個topic,也就是說所謂的1-N的“廣播”,一條消息廣播給N個訂閱方。


          最后,通過游標也很容易實現(xiàn)重新消費。因為游標僅僅就是記錄當前消費到哪一條數(shù)據了,要重新消費的話直接修改游標的值就可以了。你可以把游標重置為任何你想要指定的位置,比如重置到0重新開始消費,也可以直接重置到最后,相當于忽略現(xiàn)有所有數(shù)據。


          因此你可以看到,kafka這種數(shù)據結構相比于redis的雙向鏈表有了一個質的飛躍,不僅是性能上,同時也是功能上,全面的領先。


          我們可以來看看kafka的一個簡單的架構圖:



          從這個圖里我們可以看出,topic是一個邏輯上的概念,不是一個實體。一個topic包含多個partition,partition分布在多臺機器上。這個機器,kafka中稱之為broker。(kafka集群中的一個broker對應redis集群中的一個實例)。對于一個topic,可以有多個不同的消費組同時進行消費。一個消費組內部可以有多個消費者實例同時進行消費,這樣可以提高消費速率。


          但是這里需要非常注意的是,一個partition只能被消費組中的一個消費者實例來消費。換句話說,消費組中如果有多個消費者,不能夠存在兩個消費者同時消費一個partition的場景。


          為什么呢?其實kafka要在partition級別提供順序消費的語義,如果多個consumer消費一個partition,即使kafka本身是按順序分發(fā)數(shù)據的,但是由于網絡延遲等各種情況,consumer并不能保證按kafka的分發(fā)順序接收到數(shù)據,這樣達到消費者的消息順序就是無法保證的。因此一個partition只能被一個consumer消費。kafka各consumer group的游標可以表示成類似這樣的數(shù)據結構:


          {    "topic-foo": {        "groupA": {            "partition-0": 0,            "partition-1": 123,            "partition-2": 78        },        "groupB": {            "partition-0": 85,            "partition-1": 9991,            "partition-2": 772        },    }}


          了解了kafka的宏觀架構,你可能會有個疑惑,kafka的消費如果只是移動游標并不刪除數(shù)據,那么隨著時間的推移數(shù)據肯定會把磁盤打滿,這個問題該如何解決呢?這就涉及到kafka的retention機制,也就是消息過期,類似于redis中的expire。


          不同的是,redis是按key來過期的,如果你給redis list設置了1分鐘有效期,1分鐘之后redis直接把整個list刪除了。而kafka的過期是針對消息的,不會刪除整個topic(partition),只會刪除partition中過期的消息。不過好在kafka的partition是單向的隊列,因此隊列中消息的生產時間都是有序的。因此每次過期刪除消息時,從頭開始刪就行了。


          看起來似乎很簡單,但仔細想一下還是有不少問題。舉例來說,假如topicA-partition-0的所有消息被寫入到一個文件中,比如就叫topicA-partition-0.log。我們再把問題簡化一下,假如生產者生產的消息在topicA-partition-0.log中一條消息占一行,很快這個文件就到200G了。現(xiàn)在告訴你,這個文件前x行失效了,你應該怎么刪除呢?非常難辦,這和讓你刪除一個數(shù)組中的前n個元素一樣,需要把后續(xù)的元素向前移動,這涉及到大量的CPU copy操作。假如這個文件有10M,這個刪除操作的代價都非常大,更別說200G了。


          因此,kafka在實際存儲partition時又進行了一個拆分。topicA-partition-0的數(shù)據并不是寫到一個文件里,而是寫到多個segment文件里。假如設置的一個segment文件大小上限是100M,當寫滿100M時就會創(chuàng)建新的segment文件,后續(xù)的消息就寫到新創(chuàng)建的segment文件,就像我們業(yè)務系統(tǒng)的日志文件切割一樣。這樣做的好處是,當segment中所有消息都過期時,可以很容易地直接刪除整個文件。而由于segment中消息是有序的,看是否都過期就看最后一條是否過期就行了。


          1. Kafka中的數(shù)據查找


          topic的一個partition是一個邏輯上的數(shù)組,由多個segment組成,如下圖所示:



          這時候就有一個問題,如果我把游標重置到一個任意位置,比如第2897條消息,我怎么讀取數(shù)據呢?

          根據上面的文件組織結構,你可以發(fā)現(xiàn)我們需要確定兩件事才能讀出對應的數(shù)據:

          • 第2897條消息在哪個segment文件里;

          • 第2897條消息在segment文件里的什么位置。


          為了解決上面兩個問題,kafka有一個非常巧妙的設計。首先,segment文件的文件名是以該文件里第一條消息的offset來命名的。一開始的segment文件名是 0.log,然后一直寫直到寫了18234條消息后,發(fā)現(xiàn)達到了設置的文件大小上限100M,然后就創(chuàng)建一個新的segment文件,名字是18234.log……


          - /kafka/topic/order_create/partition-0    - 0.log    - 18234.log #segment file    - 39712.log    - 54101.log

          當我們要找offset為x的消息在哪個segment時,只需要通過文件名做一次二分查找就行了。比如offset為2879的消息(第2880條消息),顯然就在0.log這個segment文件里。

          定位到segment文件之后,另一個問題就是要找到該消息在文件中的位置,也就是偏移量。如果從頭開始一條條地找,這個耗時肯定是無法接受的!kafka的解決辦法就是索引文件。

          就如mysql的索引一樣,kafka為每個segment文件創(chuàng)建了一個對應的索引文件。索引文件很簡單,每條記錄就是一個kv組,key是消息的offset,value是該消息在segment文件中的偏移量:

          offset
          position
          0
          0
          1
          124
          2
          336

          每個segment文件對應一個索引文件:


          - /kafka/topic/order_create/partition-0    - 0.log    - 0.index     - 18234.log #segment file    - 18234.index #index file     - 39712.log    - 39712.index     - 54101.log    - 54101.index

          有了索引文件,我們就可以拿到某條消息具體的位置,從而直接進行讀取。再捋一遍這個流程:

          • 當要查詢offset為x的消息

          • 利用二分查找找到這條消息在y.log

          • 讀取y.index文件找到消息x的y.log中的位置

          • 讀取y.log的對應位置,獲取數(shù)據


          通過這種文件組織形式,我們可以在kafka中非常快速地讀取出任何一條消息。但這又引出了另一個問題,如果消息量特別大,每條消息都在index文件中加一條記錄,這將浪費很多空間。

          可以簡單地計算一下,假如index中一條記錄16個字節(jié)(offset 8 + position 8),一億條消息就是16*10^8字節(jié)=1.6G。對于一個稍微大一點的公司,kafka用來收集日志的話,一天的量遠遠不止1億條,可能是數(shù)十倍上百倍。這樣的話,index文件就會占用大量的存儲。因此,權衡之下kafka選擇了使用”稀疏索引“。

          所謂稀疏索引就是并非所有消息都會在index文件中記錄它的position,每間隔多少條消息記錄一條,比如每間隔10條消息記錄一條offset-position:

          offset

          position

          00
          101852
          204518
          306006
          408756
          5010844

          這樣的話,如果當要查詢offset為x的消息,我們可能沒辦法查到它的精確位置,但是可以利用二分查找,快速地確定離他最近的那條消息的位置,然后往后多讀幾條數(shù)據就可以讀到我們想要的消息了。

          比如,當我們要查到offset為33的消息,按照上表,我們可以利用二分查找定位到offset為30的消息所在的位置,然后去對應的log文件中從該位置開始向后讀取3條消息,第四條就是我們要找的33。這種方式其實就是在性能和存儲空間上的一個折中,很多系統(tǒng)設計時都會面臨類似的選擇,犧牲時間換空間還是犧牲空間換時間。

          到這里,我們對kafka的整體架構應該有了一個比較清晰的認識了。不過在上面的分析中,我故意隱去了kafka中另一個非常非常重要的點,就是高可用方面的設計。因為這部分內容比較晦澀,會引入很多分布式理論的復雜性,妨礙我們理解kafka的基本模型。在接下來的部分,將著重討論這個主題。

          2. Kafka高可用


          高可用(HA)對于企業(yè)的核心系統(tǒng)來說是至關重要的。因為隨著業(yè)務的發(fā)展,集群規(guī)模會不斷增大,而大規(guī)模集群中總會出現(xiàn)故障,硬件、網絡都是不穩(wěn)定的。當系統(tǒng)中某些節(jié)點各種原因無法正常使用時,整個系統(tǒng)可以容忍這個故障,繼續(xù)正常對外提供服務,這就是所謂的高可用性。對于有狀態(tài)服務來說,容忍局部故障本質上就是容忍丟數(shù)據(不一定是永久,但是至少一段時間內讀不到數(shù)據)。

          系統(tǒng)要容忍丟數(shù)據,最樸素也是唯一的辦法就是做備份,讓同一份數(shù)據復制到多臺機器,所謂的冗余,或者說多副本。為此,kafka引入 leader-follower的概念。topic的每個partition都有一個leader,所有對這個partition的讀寫都在該partition leader所在的broker上進行。partition的數(shù)據會被復制到其它broker上,這些broker上對應的partition就是follower:


          producer在生產消息時,會直接把消息發(fā)送到partition leader上,partition leader把消息寫入自己的log中,然后等待follower來拉取數(shù)據進行同步。具體交互如下:

           
          上圖中對producer進行ack的時機非常關鍵,這直接關系到kafka集群的可用性和可靠性。

          • 如果producer的數(shù)據到達leader并成功寫入leader的log就進行ack

            優(yōu)點:不用等數(shù)據同步完成,速度快,吞吐率高,可用性高;

            缺點:如果follower數(shù)據同步未完成時leader掛了,就會造成數(shù)據丟失,可靠性低。

          • 如果等follower都同步完數(shù)據時進行ack
            優(yōu)點:當leader掛了之后follower中也有完備的數(shù)據,可靠性高;

            缺點:等所有follower同步完成很慢,性能差,容易造成生產方超時,可用性低。


          而具體什么時候進行ack,對于kafka來說是可以根據實際應用場景配置的。

          其實kafka真正的數(shù)據同步過程還是非常復雜的,本文主要是想講一講kafka的一些核心原理,數(shù)據同步里面涉及到的很多技術細節(jié),HW epoch等,就不在此一一展開了。最后展示一下kafka的一個全景圖:


          最后對kafka進行一個簡要地總結:kafka通過引入partition的概念,讓topic能夠分散到多臺broker上,提高吞吐率。但是引入多partition的代價就是無法保證topic維度的全局順序性,需要這種特性的場景只能使用單個partition。在內部,每個partition以多個segment文件的方式進行存儲,新來的消息append到最新的segment log文件中,并使用稀疏索引記錄消息在log文件中的位置,方便快速讀取消息。當數(shù)據過期時,直接刪除過期的segment文件即可。為了實現(xiàn)高可用,每個partition都有多個副本,其中一個是leader,其它是follower,分布在不同的broker上。對partition的讀寫都在leader所在的broker上完成,follower只會定時地拉取leader的數(shù)據進行同步。當leader掛了,系統(tǒng)會選出和leader保持同步的follower作為新的leader,繼續(xù)對外提供服務,大大提高可用性。在消費端,kafka引入了消費組的概念,每個消費組都可以互相獨立地消費topic,但一個partition只能被消費組中的唯一一個消費者消費。消費組通過記錄游標,可以實現(xiàn)ACK機制、重復消費等多種特性。除了真正的消息記錄在segment中,其它幾乎所有meta信息都保存在全局的zookeeper中。

          3. 優(yōu)缺點


          (1)優(yōu)點:kafka的優(yōu)點非常多

          • 高性能:單機測試能達到 100w tps;

          • 低延時:生產和消費的延時都很低,e2e的延時在正常的cluster中也很低;

          • 可用性高:replicate + isr + 選舉 機制保證;

          • 工具鏈成熟:監(jiān)控 運維 管理 方案齊全;

          • 生態(tài)成熟:大數(shù)據場景必不可少 kafka stream.


          (2)不足

          • 無法彈性擴容:對partition的讀寫都在partition leader所在的broker,如果該broker壓力過大,也無法通過新增broker來解決問題;

          • 擴容成本高:集群中新增的broker只會處理新topic,如果要分擔老topic-partition的壓力,需要手動遷移partition,這時會占用大量集群帶寬;

          • 消費者新加入和退出會造成整個消費組rebalance:導致數(shù)據重復消費,影響消費速度,增加e2e延遲;

          • partition過多會使得性能顯著下降:ZK壓力大,broker上partition過多讓磁盤順序寫幾乎退化成隨機寫。


          在了解了kafka的架構之后,你可以仔細想一想,為什么kafka擴容這么費勁呢?其實這本質上和redis集群擴容是一樣的!當redis集群出現(xiàn)熱key時,某個實例扛不住了,你通過加機器并不能解決什么問題,因為那個熱key還是在之前的某個實例中,新擴容的實例起不到分流的作用。kafka類似,它擴容有兩種:新加機器(加broker)以及給topic增加partition。給topic新加partition這個操作,你可以聯(lián)想一下mysql的分表。比如用戶訂單表,由于量太大把它按用戶id拆分成1024個子表user_order_{0..1023},如果到后期發(fā)現(xiàn)還不夠用,要增加這個分表數(shù),就會比較麻煩。因為分表總數(shù)增多,會讓user_id的hash值發(fā)生變化,從而導致老的數(shù)據無法查詢。所以只能停服做數(shù)據遷移,然后再重新上線。kafka給topic新增partition一樣的道理,比如在某些場景下msg包含key,那producer就要保證相同的key放到相同的partition。但是如果partition總量增加了,根據key去進行hash,比如 hash(key) % parition_num,得到的結果就不同,就無法保證相同的key存到同一個partition。當然也可以在producer上實現(xiàn)一個自定義的partitioner,保證不論怎么擴partition相同的key都落到相同的partition上,但是這又會使得新增加的partition沒有任何數(shù)據。

          其實你可以發(fā)現(xiàn)一個問題,kafka的核心復雜度幾乎都在存儲這一塊。數(shù)據如何分片,如何高效的存儲,如何高效地讀取,如何保證一致性,如何從錯誤中恢復,如何擴容再平衡……

          上面這些不足總結起來就是一個詞:scalebility。通過直接加機器就能解決問題的系統(tǒng)才是大家的終極追求。Pulsar號稱云原生時代的分布式消息和流平臺,所以接下來我們看看pulsar是怎么樣的。

          四、Pulsar



          kafka的核心復雜度是它的存儲,高性能、高可用、低延遲、支持快速擴容的分布式存儲不僅僅是kafka的需求,應該是現(xiàn)代所有系統(tǒng)共同的追求。而apache項目底下剛好有一個專門就是為日志存儲打造的這樣的系統(tǒng),它叫bookeeper!

          有了專門的存儲組件,那么實現(xiàn)一個消息系統(tǒng)剩下的就是如何來使用這個存儲系統(tǒng)來實現(xiàn)feature了。pulsar就是這樣一個”計算-存儲 分離“的消息系統(tǒng):

           
          pulsar利用bookeeper作為存儲服務,剩下的是計算層。這其實是目前非常流行的架構也是一種趨勢,很多新型的存儲都是這種”存算分離“的架構。比如tidb,底層存儲其實是tikv這種kv存儲。tidb是更上層的計算層,自己實現(xiàn)sql相關的功能。還有的例子就是很多"持久化"redis產品,大部分底層依賴于rocksdb做kv存儲,然后基于kv存儲關系實現(xiàn)redis的各種數(shù)據結構。

          在pulsar中,broker的含義和kafka中的broker是一致的,就是一個運行的pulsar實例。但是和kafka不同的是,pulsar的broker是無狀態(tài)服務,它只是一個”API接口層“,負責處理海量的用戶請求,當用戶消息到來時負責調用bookeeper的接口寫數(shù)據,當用戶要查詢消息時從bookeeper中查數(shù)據,當然這個過程中broker本身也會做很多緩存之類的。同時broker也依賴于zookeeper來保存很多元數(shù)據的關系。

          由于broker本身是無狀態(tài)的,因此這一層可以非常非常容易地進行擴容,尤其是在k8s環(huán)境下,點下鼠標的事兒。至于消息的持久化,高可用,容錯,存儲的擴容,這些都通通交給bookeeper來解決。

          但就像能量守恒定律一樣,系統(tǒng)的復雜性也是守恒的。實現(xiàn)既高性能又可靠的存儲需要的技術復雜性,不會憑空消失,只會從一個地方轉移到另一個地方。就像你寫業(yè)務邏輯,產品經理提出了20個不同的業(yè)務場景,就至少對應20個if else,不論你用什么設計模式和架構,這些if else不會被消除,只會從從一個文件放到另一個文件,從一個對象放到另一個對象而已。所以那些復雜性一定會出現(xiàn)在bookeeper中,并且會比kafka的存儲實現(xiàn)更為復雜。

          但是pulsar存算分離架構的一個好處就是,當我們在學習pulsar時可以有一個比較明確的界限,所謂的concern segregation。只要理解bookeeper對上層的broker提供的API語義,即使不了解bookeeper內部的實現(xiàn),也能很好的理解pulsar的原理。

          接下來你可以思考一個問題:既然pulsar的broker層是無狀態(tài)的服務,那么我們是否可以隨意在某個broker進行對某個topic的數(shù)據生產呢?

          看起來似乎沒什么問題,但答案還是否定的——不可以。為什么呢?想一想,假如生產者可以在任意一臺broker上對topic進行生產,比如生產3條消息a b c,三條生產消息的請求分別發(fā)送到broker A B C,那最終怎么保證消息按照a b c的順序寫入bookeeper呢?這是沒辦法保證,只有讓a b c三條消息都發(fā)送到同一臺broker,才能保證消息寫入的順序。

          既然如此,那似乎又回到和kafka一樣的問題,如果某個topic寫入量特別特別大,一個broker扛不住怎么辦?所以pulsar和kafka一樣,也有partition的概念。一個topic可以分成多個partition,為了每個partition內部消息的順序一致,對每個partition的生產必須對應同一臺broker。


          這里看起來似乎和kafka沒區(qū)別,也是每個partition對應一個broker,但是其實差別很大。為了保證對partition的順序寫入,不論kafka還是pulsar都要求寫入請求發(fā)送到partition對應的broker上,由該broker來保證寫入的順序性。然而區(qū)別在于,kafka同時會把消息存儲到該broker上,而pulsar是存儲到bookeeper上。這樣的好處是,當pulsar的某臺broker掛了,可以立刻把partition對應的broker切換到另一個broker,只要保證全局只有一個broker對topic-partition-x有寫權限就行了,本質上只是做一個所有權轉移而已,不會有任何數(shù)據的搬遷。

          當對partition的寫請求到達對應broker時,broker就需要調用bookeeper提供的接口進行消息存儲。和kafka一樣,pulsar在這里也有segment的概念,而且和kafka一樣的是,pulsar也是以segment為單位進行存儲的(respect respect respect)。

          為了說清楚這里,就不得不引入一個bookeeper的概念,叫l(wèi)edger,也就是賬本。可以把ledger類比為文件系統(tǒng)上的一個文件,比如在kafka中就是寫入到xxx.log這個文件里。pulsar以segment為單位,存入bookeeper中的ledger。

          在bookeeper集群中每個節(jié)點叫bookie(為什么集群的實例在kafka叫broker在bookeeper又叫bookie……無所謂,名字而已,作者寫了那么多代碼,還不能讓人開心地命個名啊)。在實例化一個bookeeper的writer時,就需要提供3個參數(shù):

          • 節(jié)點數(shù)n:bookeeper集群的bookie數(shù);

          • 副本數(shù)m:某一個ledger會寫入到n個bookie中的m個里,也就是說所謂的m副本;

          • 確認寫入數(shù)t:每次向ledger寫入數(shù)據時(并發(fā)寫入到m個bookie),需要確保收到t個acks,才返回成功。


          bookeeper會根據這三個參數(shù)來為我們做復雜的數(shù)據同步,所以我們不用擔心那些副本啊一致性啊的東西,直接調bookeeper的提供的append接口就行了,剩下的交給它來完成。


          如上圖所示,parition被分為了多個segment,每個segment會寫入到4個bookie其中的3個中。比如segment1就寫入到了bookie1,2,4中,segment2寫入到bookie1,3,4中…

          這其實就相當于把kafka某個partition的segment均勻分布到了多臺存儲節(jié)點上。這樣的好處是什么呢?在kafka中某個partition是一直往同一個broker的文件系統(tǒng)中進行寫入,當磁盤不夠用了,就需要做非常麻煩的擴容+遷移數(shù)據的操作。而對于pulsar,由于partition中不同segment可以保存在bookeeper不同的bookies上,當大量寫入導致現(xiàn)有集群bookie磁盤不夠用時,我們可以快速地添加機器解決問題,讓新的segment尋找最合適的bookie(磁盤空間剩余最多或者負載最低等)進行寫入,只要記住segment和bookies的關系就好了。


          由于partition以segment為粒度均勻的分散到bookeeper上的節(jié)點上,這使得存儲的擴容變得非常非常容易。這也是Pulsar一直宣稱的存算分離架構的先進性的體現(xiàn):

          • broker是無狀態(tài)的,隨便擴容;

          • partition以segment為單位分散到整個bookeeper集群,沒有單點,也可以輕易地擴容;

          • 當某個bookie發(fā)生故障,由于多副本的存在,可以另外t-1個副本中隨意選出一個來讀取數(shù)據,不間斷地對外提供服務,實現(xiàn)高可用。


          其實在理解kafka的架構之后再來看pulsar,你會發(fā)現(xiàn)pulsar的核心就在于bookeeper的使用以及一些metadata的存儲。但是換個角度,正是這個恰當?shù)拇鎯陀嬎惴蛛x的架構,幫助我們分離了關注點,從而能夠快速地去學習上手。

          消費模型


          Pulsar相比于kafka另一個比較先進的設計就是對消費模型的抽象,叫做subscription。通過這層抽象,可以支持用戶各種各樣的消費場景。還是和kafka進行對比,kafka中只有一種消費模式,即一個或多個partition對一個consumer。如果想要讓一個partition對多個consumer,就無法實現(xiàn)了。pulsar通過subscription,目前支持4種消費方式:

           
          可以把pulsar的subscription看成kafka的consumer group,但subscription更進一步,可以設置這個”consumer group“的消費類型:

          • exclusive:消費組里有且僅有一個consumer能夠進行消費,其它的根本連不上pulsar;

          • failover:消費組里的每個消費者都能連上每個partition所在的broker,但有且僅有一個consumer能消費到數(shù)據。當這個消費者崩潰了,其它的消費者會被選出一個來接班;

          • shared:消費組里所有消費者都能消費topic中的所有partition,消息以round-robin的方式來分發(fā);

          • key-shared:消費組里所有消費者都能消費到topic中所有partition,但是帶有相同key的消息會保證發(fā)送給同一個消費者。


          這些消費模型可以滿足多種業(yè)務場景,用戶可以根據實際情況進行選擇。通過這層抽象,pulsar既支持了queue消費模型,也支持了stream消費模型,還可以支持其它無數(shù)的消費模型(只要有人提pr),這就是pulsar所說的統(tǒng)一了消費模型。

          其實在消費模型抽象的底下,就是不同的cursor管理邏輯。怎么ack,游標怎么移動,怎么快速查找下一條需要重試的msg……這都是一些技術細節(jié),但是通過這層抽象,可以把這些細節(jié)進行隱藏,讓大家更關注于應用。

          五、存算分離架構



          其實技術的發(fā)展都是螺旋式的,很多時候你會發(fā)現(xiàn)最新的發(fā)展方向又回到了20年前的技術路線了。

          在20年前,由于普通計算機硬件設備的局限性,對大量數(shù)據的存儲是通過NAS(Network Attached Storage)這樣的“云端”集中式存儲來完成。但這種方式的局限性也很多,不僅需要專用硬件設備,而且最大的問題就是難以擴容來適應海量數(shù)據的存儲。

          數(shù)據庫方面也主要是以Oracle小型機為主的方案。然而隨著互聯(lián)網的發(fā)展,數(shù)據量越來越大,Google后來又推出了以普通計算機為主的分布式存儲方案,任意一臺計算機都能作為一個存儲節(jié)點,然后通過讓這些節(jié)點協(xié)同工作組成一個更大的存儲系統(tǒng),這就是HDFS。

          然而移動互聯(lián)網使得數(shù)據量進一步增大,并且4G 5G的普及讓用戶對延遲也非常敏感,既要可靠,又要快,又要可擴容的存儲逐漸變成了一種企業(yè)的剛需。而且隨著時間的推移,互聯(lián)網應用的流量集中度會越來越高,大企業(yè)的這種剛需訴求也越來越強烈。

          因此,可靠的分布式存儲作為一種基礎設施也在不斷地完善。它們都有一個共同的目標,就是讓你像使用filesystem一樣使用它們,并且具有高性能高可靠自動錯誤恢復等多種功能。然而我們需要面對的一個問題就是CAP理論的限制,線性一致性(C),可用性(A),分區(qū)容錯性(P),三者只能同時滿足兩者。因此不可能存在完美的存儲系統(tǒng),總有那么一些“不足”。我們需要做的其實就是根據不同的業(yè)務場景,選用合適的存儲設施,來構建上層的應用。這就是pulsar的邏輯,也是tidb等newsql的邏輯,也是未來大型分布式系統(tǒng)的基本邏輯,所謂的“云原生”。

          1. 牛x!基于Springboot 2.0 + LayUI開發(fā)的物流管理系統(tǒng)(已開源)

          2. 漫畫 | 辭職前與老板的最后一次談話有哪些禁忌?

          3. 互聯(lián)網公司理想架構探討

          4. IDEA 的 debug 怎么實現(xiàn)?出于這個好奇心,我越挖越深!

          最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數(shù)據庫、數(shù)據結構等等。

          獲取方式:點“在看”,關注公眾號并回復 Java 領取,更多內容陸續(xù)奉上。

          文章有幫助的話,在看,轉發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产又粗又大又硬又长又爽的视频 | 成人免费在线视频 | 美女影视123区 | 看看操逼片| 啊 v在线 免费 |