成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

你管這破玩意兒叫 MQ?

存儲 數據管理
由于隊列在生產者所在服務內存,其他消費者不得不從生產者中取,也就意味著生產者與消費者緊耦合,這顯然不合理。

幸福的煩惱

張大胖最近是又喜又憂,喜的是業務量發展猛增,憂的是由于業務量猛增,一些原來不是問題的問題變成了大問題,比如說新會員注冊吧,原來注冊成功只要發個短信就行了,但隨著業務的發展,現在注冊成功也需要發 push,發優惠券,…等。

這樣光注冊用戶這一步就需要調用很多服務,導致用戶注冊都需要花不少時間,假設每個服務調用需要 50 ms,那么光以上服務就需要調用 200 ms,而且后續產品還有可能再加一些發新人紅包等活動,每加一個功能,除了引入額外的服務增加耗時外,還需要額外集成服務,重發代碼,實在讓人煩不勝煩,張大胖想一勞永逸地解決這個問題,于是找了 CTO Bill 來商量一下,看能否提供一些思路。

Bill 一眼就看出了問題的所在:你這個系統存在三個問題:同步,耦合,流量暴增時系統被壓垮的風險。

  • 同步: 我們可以看到在注冊用戶后,需要同步調用其他模塊后才能返回,這是耗時高的根本原因!
  • 耦合:注冊用戶與其他模塊嚴重耦合,體現在每調用一個模塊,都需要在注冊用戶代碼處集成其他模塊的代碼并重新發布,此時在這些流程中只有注冊用戶這一步是核心流程,其他都是次要流程,核心流程應該與次要流程解耦,否則只要其中一個次要流程調用失敗,整個流程也就失敗了,體現在前端就是明明已經注冊成功了,但返回給用戶的卻是失敗的。
  • 流量暴增風險:如果某天運營搞活動,比如注冊后送新人紅包,那么很有可能導致用戶注冊的流量暴增,那么由于我們的注冊用戶流程過長,很有可能導致注冊用戶的服務無法承載相應的流量壓力而導致系統雪崩。

不愧是 CTO,一眼看出問題所在,「那該怎么解決呢」張大胖問到。

「大胖,你應該聽說過一句話:任何軟件問題都可以通過添加一層中間層來解決,如果不能,那就再加一層,同樣的針對以上問題我們也可以添加一個中間層來解決,比如添加個隊列,把用戶注冊這個事件放到隊列中,讓其他模塊去這個隊列里取這個事件然后再做相應的操作」Bill 邊說邊畫出了他所說的中間層隊列

可以看到,這是個典型的生產者-消費者模型,用戶注冊后只要把注冊事件丟給這個隊列就可以立即返回,實現了將同步變了異步,其他服務只要從這個隊列中拉取事件消費即可進行后續的操作,同時也實現了注冊用戶邏輯與其他服務的解耦,另外即使流量暴增也沒有影響,因為注冊用戶將事件發給隊列后馬上返回了,這一發消息可能只要 5 ms,也就是說總耗時是 50ms+5ms = 55 ms,而原來的總耗時是 200 ms,系統的吞吐量和響應速度提升了近 4 倍,大大提升了系統的負責能力,這一步也就是我們常說的削峰,將暴增的流量放入隊列中以實現平穩過渡。

「妙啊,加了一層隊列就達到了異步,解藕,削峰的目的,也完美地解決了我的問題」張大胖興奮地說。

「先別高興得太早,你想想這個隊列該用哪個,JDK 的內置隊列是否可行,或者說什么樣的隊列才能滿足我們的條件呢」Bill 提醒道。

張大胖想了一下如果直接使用 JDK 的隊列(Queue)可能會有以下問題:

  • 由于隊列在生產者所在服務內存,其他消費者不得不從生產者中取,也就意味著生產者與消費者緊耦合,這顯然不合理。
  • 消息丟失:現在是把消息存儲在隊列中,而隊列是在內存中的,那如果機器宕機,隊列中的消息不就丟失了嗎,顯然不可接受。
  • 單個隊列中的消息只能被一個服務消費,也就是說如果某個服務從隊列中取消息消費后,其他服務就取不了這個消息了,有一個辦法倒是可以,為每一個服務準備一個隊列,這樣發送消息的時候只發送給一個隊列,再通過這個隊列把完整消息復制給其他隊列即可。

這種做法雖然理論上可以,但實踐起來顯然有問題,因為這就意味著每對接一個服務都要準備一份一模一樣的隊列,而且復制多份消息性能也存在嚴重問題,還得保證復制中消息不丟失,無疑增加了技術上的實現難度。

broker

針對以上問題 Bill 和張大胖商量了一下決定自己設計一個獨立于生產者和消費者的消息隊列(姑且把中間這個保存消息的組件稱為 Broker),這樣的話就解決了問題一,生產者把消息發給 Broker,消費者只需把消息從 Broker 里拉出來消費即可,生產者和消費者就徹底解耦了,如下:

那么這個 Broker 應該如何設計才能滿足我們的要求呢,顯然它應該滿足以下幾個條件:

  • 消息持久化:不能因為 Broker 宕機了消息就都丟失了,所以消息不能只保存在內存中,應該持久化到磁盤上,比如保存在文件里,這樣由于消息持久化了,它也可以被多個消費者消費,只要每個消費者保存相應的消費進度,即可實現多個消費者的獨立消費。
  • 高可用:如果 Broker 宕機了,producer 就發不了消息了,consumer 也無法消費,這顯然是不可接受的,所以必須保證 Broker 的高可用。
  • 高性能:我們定一個指標,比如 10w TPS,那么要實現這個目的就得滿足以下三個條件:
  • producer 發送消息要快(或者說 broker 接收消息要快)
  • 持久化到文件要快
  • consumer 拉取消息要快

接下來我們再來看 broker 的整體設計情況。

針對問題一,我們可以把消息存儲在文件中,消息通過順序寫入文件的方式來保證寫入文件的高性能。

順序寫文件的性能很高,接近于內存中的隨機寫,如下圖示:

這樣 consumer 如果要消費的話,就可以從存儲文件中讀取消息了。好了,現在問題來了,我們都知道消息文件是存在硬盤中的,如果每次 broker 接收消息都寫入文件,每次 consumer 讀取消息都從硬盤讀取文件,由于都是磁盤 IO,是非常耗時的,有什么辦法可以解決呢?

page cache

磁盤 IO 是很慢的,為了避免 CPU 每次讀寫文件都得和磁盤交互,一般先將文件讀取到內存中,然后再由 CPU 訪問,這樣 CPU 直接在內存中讀寫文件就快多了,那么文件怎么從磁盤讀取入內存呢,首先我們需要明白文件是以 block(塊)的形式讀取的,而 Linux 內核在內存中會以頁大小(一般為 4KB)為分配單位。對文件進行讀寫操作時,內核會申請內存頁(內存頁即 page,多個 page 組成 page cache,即頁緩存),然后將文件的 block 加載到頁緩存中(n block size = 1 page size,如果一個 block 大小等于一個 page,則 n = 1)如下圖示:


這樣的話讀寫文件的過程就一目了解:

  • 對于讀文件:CPU 讀取文件時,首先會在 page cache 中查找是否有相應的文件數據,如果有直接對 page cache 進行操作,如果沒有則會觸發一個缺頁異常(fault page)將磁盤上的塊加載到 page cache 中,同時由于程序局部性原理,會一次性加載多個 page(讀取數據所在的 page 及其相鄰的 page )到 page cache 中以保證讀取效率。
  • 對于寫文件:CPU 首先會將數據寫入 page cache 中,然后再將 page cache 刷入磁盤中。

CPU 對文件的讀寫操作就轉化成了對頁緩存的讀寫操作,這樣只要讓 producer/consumer 在內存中讀寫消息文件,就避免了磁盤 IO。

mmap

需要注意的是 page cache 是存在內核空間中的,還不能直接為應用程序所用,必須經由 CPU 將內核空間 page cache 拷貝到用戶空間中才能為進程所用(同樣的如果是寫文件,也是先寫到用戶空間的緩沖區中,再拷貝到內核空間的 page cache,然后再刷盤)。

畫外音:為啥要將 page cache 拷貝到用戶空間呢,這主要是因為頁緩存處在內核空間,不能被用戶進程直接尋址。

上圖為程序讀取文件完整流程:

  • 首先是硬盤中的文件數據載入處于內核空間中的 page cache(也就是我們平常所說的內核緩沖區)。
  • CPU 將其拷貝到用戶空間中的用戶緩沖區中。
  • 程序通過用戶空間的虛擬內存來映射操作用戶緩沖區(兩者通過 MMU 來轉換),進而達到了在內存中讀寫文件的目的。

將以上流程簡化如下:

以上是傳統的文件讀 IO 流程,可以看到程序的一次讀文件經歷了一次 read 系統調用和一次 CPU 拷貝,那么從內核緩沖區拷貝到用戶緩沖區的這一步能否取消掉呢,答案是肯定的。

只要將虛擬內存映射到內核緩存區即可,如下:

可以看到使用這種方式有兩個好處:

  1. 省去了 CPU 拷貝,原本需要 CPU 從內核緩沖區拷貝到用戶緩沖區,現在這一步省去了。
  2. 節省了一半的空間: 因為不需要將 page cache 拷貝到用戶空間了,可以認為用戶空間和內核空間共享 page cache。

我們把這種通過將文件映射到進程的虛擬地址空間從而實現在內存中讀寫文件的方式稱為 mmap(Memory Mapped Files)。

上面這張圖畫得有點簡單了,再來看一下 mmap 的細節。

  • 先把磁盤上的文件映射到進程的虛擬地址上(此時還未分配物理內存),即調用 mmap 函數返回指針 ptr,它指向虛擬內存中的一個地址,這樣進程無需再調用 read 或 write 對文件進行讀寫,只需要通過 ptr 就能操作文件,所以如果需要對文件進行多次讀寫,顯然使用 mmap 更高效,因為只會進行一次系統調用,比起多次 read 或 write 造成的多次系統調用顯然開銷會更低。
  • 但需要注意的是此時的 ptr 指向的是邏輯地址,并未真正分配物理內存,只有通過 ptr 對文件進行讀寫操作時才會分配物理內存,分配之后會更新頁表,將虛擬內存與物理內存映射起來,這樣虛擬內存即可通過 MMU 找到物理內存,分配完內存后即可將文件加載到 page cache,于是進程就可在內存中愉快地讀寫文件了。

使用 mmap 有力地提升了文件的讀寫性能,它也是我們常說的零拷貝的一種實現方式,既然 mmap 這么好,可能有人就要問了,那為什么文件讀寫不都用 mmap 呢,天下沒有免費的午餐,mmap 也是有成本的,它有如下缺點。

文件無法完成拓展:因為執行 mmap 的時候,你所能操作的范圍就已經確定了,無法增加文件長度。

地址映射的開銷:為了創建并維持虛擬地址空間與文件的映射關系,內核中需要有特定的數據結構來實現這一映射。內核為每個進程維護一個任務結構 task_struct,task_struct 中的 mm_struct 描述了虛擬內存的信息,mm_struct 中的 mmap 字段是一個 vm_area_struct 指針,內核中的 vm_area_struct 對象被組織成一個鏈表 + 紅黑樹的結構。如下圖示:

所以理論上,進程調用一次 mmap 就會產生一個 vm_area_struct 對象(不考慮內核自動合并相鄰且符合條件的內存區域),vm_area_struct 數量的增加會增大內核的管理工作量,增大系統開銷。

  • 缺頁中斷(page fault)的開銷: 調用 mmap 內核只是建立了邏輯地址(虛擬內存)到物理地址(物理內存)的映射表,實際并沒有任何數據加載到物理內存中,只有在主動讀寫文件的時候發現數據所在分頁不在內存中時才會觸發缺頁中斷,分配物理內存,缺頁中斷一次讀寫只會觸發一個 page 的加載,一個 page 只有 4k,想象一次,如果一個文件是 1G,那就得觸發 256 次缺頁中斷!中斷的開銷是很大的,那么對于大文件來說,就會發生很多次的缺頁中斷,這顯然是不可接受的,所以一般 mmap 得配合另一個系統調用 madvise,它有個文件預熱的功能可以建議內核一次性將一大段文件數據讀取入內存,這樣就避免了多次的缺頁中斷,同時為了避免文件從內存中 swap 到磁盤,也可以對這塊內存區域進行鎖定,避免換出。
  • mmap 并不適合讀取超大型文件,mmap 需要預先分配連續的虛擬內存空間用于映射文件,如果文件較大,對于 32 位地址空間(4 G)的系統來說,可能找不到足夠大的連續區域,而且如果某個文件太大的話,會擠壓其他熱點小文件的 page cache 空間,影響這些文件的讀寫性能。

綜上考慮,我們給每一個消息文件定為固定的 1G 大小,如果文件滿了的話再創建一個即可,我們把這些存儲消息的文件集合稱為 commitlog。這樣的設計還有另一個好處:在刪除過期文件的時候會很方便,直接把之前的文件整個刪掉即可,最新的文件無需改動,而如果把所有消息都寫到一個文件里,顯然刪除之前的過期消息會非常麻煩。

consumeQueue 文件

通過 mmap 的方式我們極大地提高了讀寫文件的效率,這樣的話即可將 commitlog 采用 mmap 的方式加載到 page cache 中,然后再在 page cache 中讀寫消息,如果是寫消息直接寫入 page cache 當然沒問題,但如果是讀消息(消費者根據消費進度拉取消息)的話可就沒這么簡單了,當然如果每個消息的大小都一樣,那么文件讀取到內存中其實就相當于數組了,根據消息進度就能很快地定位到其在文件的位置(假設消息進度為 offset,每個消息的大小為 size,則所要消費的位置為 offset * size),但很顯然每個消息的大小基本不可能相同,實際情況很可能是類似下面這樣:

如圖示:這里有三個消息,每個消息的消息體分別為 2kb,3kb,4kb,消息大小都不一樣。

這樣的話會有兩個問題

  • 消息邊界不清,無法區分相鄰的兩個消息。
  • 即使解決了以上問題,也無法解決根據消費進度快速定位其所對應消息在文件的位置。假設 broker 重啟了,然后讀取消費進度(消費進度可以持久化到文件中),此時不得不從頭讀取文件來定位消息在文件的位置,這在效率上顯然是不可接受的。

那能否既能利用到數組的快速尋址,又能快速定位消費進度對應消息在文件中的位置呢,答案是可以的,我們可以新建一個索引文件(我們將其稱為 consumeQueue 文件),每次寫入 commitlog 文件后,都把此消息在 commitlog 文件中的 offset(我們將其稱為 commit offset,8 字節) 及其大小(size,4 字節)還有一個 tag hashcode(8 字節,它的作用后文會提到)這三個字段順序寫入 consumeQueue 文件中。

這樣每次追加寫入 consumeQueue 文件的大小就固定為 20 字節了,由于大小固定,根據數組的特性,就能迅速定位消費進度在索引文件中的位置,然后即可獲取 commitlog offset 和 size,進而快速定位其在 commitlog 中消息。

這里有個問題,我們上文提到 commitlog 文件固定大小 1G,寫滿了會再新建一個文件,為了方便根據 commitlog offset 快速定位消息是在哪個 commitlog 的哪個位置,我們可以以消息偏移量來命名文件,比如第一個文件的偏移量是 0,第二個文件的偏移量為 1G(1024*1024*1024 = 1073741824 B),第三個文件偏移量為 2G(2147483648 B),如下圖示:

同理,consumeQueue 文件也會寫滿,寫滿后也要新建一個文件再寫入,我們規定 consumeQueue 可以保存 30w 條數據,也就是 30w * 20 byte = 600w Byte = 5.72 M,為了便于定位消費進度是在哪個 consumeQueue文件中,每個文件的名稱也是以偏移量來命名的,如下:

知道了文件的寫入與命名規則,我們再來看下消息的寫入與消費過程

  • 消息寫入:首先是消息被順序寫入 commitlog 文件中,寫入后此消息在文件中的偏移(commitlog offset)和大小(size)會被順序寫入相應的 consumeQueue 文件中。
  • 消費消息:每個消費者都有一個消費進度,由于每個 consumeQueue 文件是根據偏移量來命名的,首先消費進度可根據二分查找快速定位到進度是在哪個 consumeQueue 文件,進一步定義到是在此文件的哪個位置,由此可以讀取到消息的 commitlog offset 和 size,然后由于 commitlog 每個文件的命名都是按照偏移量命名的,那么根據 commitlog offset 顯然可以根據二分查找快速定位到消息是在哪個 commitlog 文件,進而再獲取到消息在文件中的具體位置從而讀到消息。

同樣的為了提升性能, consumeQueue 也利用了 mmap 進行讀寫。

有人可能會說這樣查找了兩次文件,性能可能會有些問題,實際上并不會,根據前文所述,可以使用 mmap + 文件預熱 + 鎖定內存來將文件加載并一直保留到內存中,這樣不管是 commitlog 還是 consumeQueue 都是在 page cache 中的,既然是在內存中查找文件那性能就不是問題了。

對 ConsumeQueue 的改進--數據分片

目前為止我們討論的場景是多個消費者獨立消費消息的場景,這種場景我們將其稱為廣播模式,這種情況下每個消費者都會全量消費消息,但還有一種更常見的場景我們還沒考慮到,那就是集群模式,集群模式下每個消費者只會消費部分消息,如下圖示:

集群模式下每個消費者采用負載均衡的方式分別并行消費一部分消息,主要目的是為了加速消息消費以避免消息積壓,那么現在問題來了,Broker 中只有一個 consumerQueue,顯然沒法滿足集群模式下并行消費的需求,該怎么辦呢,我們可以借鑒分庫分表的設計理念:將數據分片存儲,具體做法是創建多個 consumeQueue,然后將數據平均分配到這些 consumerQueue 中,這樣的話每個 consumer 各自負責獨立的 consumerQueue 即可做到并行消費。

如圖示: Producer 把消息負載均衡分別發送到 queue 0 和 queue 1 隊列中,consumer A 負責 queue 0,consumer B 負責 queue 1 中的消息消費,這樣可以做到并行消費,極大地提升了性能。

topic

現在所有消息都持久化到 Broker 的文件中,都能被 consumer 消費了,但實際上某些 consumer 可能只對某一類型的消息感興趣,比如只對訂單類的消息感興趣,而對用戶注冊類的消息無感,那么現在的設計顯然不合理,所以需要對消息進行進一步的細分,我們把同一種業務類型的的消息集合稱為 Topic。這樣消費者就可以只訂閱它感興趣的 Topic 進行消費,因此也不難理解 consumeQueue 是針對 Topic 而言的,producer 發送消息時都會指定消息的 Topic,消息到達 Broker 后會發送到 Topic 中對應的 consumeQueue,這樣消費者就可以只消費它感興趣的消息了。

tag

把消息按業務類型劃分成 Topic 粒度還是有點大,以訂單消息為例,訂單有很多種狀態,比如訂單創建,訂單關閉,訂單完結等,某些消費者可能只對某些訂單狀態感興趣,所以我們有時還需要進一步對某個 Topic 下的消息進行分類,我們將這些分類稱為 tag,比如訂單消息可以進一步劃分為訂單創建,訂單關閉,訂單完結等 tag。

topic 與 tag 關系

producer 在發消息的時候會指定 topic 和 tag,Broker 也會把 topic, tag 持久化到文件中,那么 consumer 就可以只訂閱它感興趣的 topic + tag 消息了,現在問題來了,consumer 來拉消息的時候,Broker 怎么只傳給 consumer 根據 topic + tag 訂閱的消息呢。

還記得上文中提到消息持久化到 commitlog 后寫入 consumeQueue 的信息嗎?

主要寫入三個字段,最后一個字段為 tag 的 hashcode,這樣的話由于 consumer 在拉消息的時候會把 topic,tag 發給 Broker ,Broker 就可以先根據 tag 的 hashcode 來對比一下看看此消息是否符合條件,如果不是略過繼續往后取,如果是再從 commitlog 中取消息后傳給 consumer,有人可能會問為什么存的是 tag hashcode 而不是 tag,主要有兩個原因。

  • hashcode 是整數,整數對比更快。
  • 為了保證此字段為固定的字節大小(hashcode 為 int 型,固定為 4 個字節),這樣每次寫入 consumeQueue 的三個字段即為固定的 20 字節,即可利用數組的特性快速定位消息進度在文件中的位置,如果用 tag 的話,由于 tag 是字符串,是變長的,沒法保證固定的字節大小。

至此我們簡單總結下消息的發送,存儲與消息流程。

  • 首先 producer 發送 topic,queueId,message 到 Broker 中,Broker 將消息通過順序寫的形式持久化到 commitlog 中,這里的 queueId 是 Topic 中指定的 consumeQueue 0,consumeQueue 1,consumeQueue …,一般通過負載均衡的方式輪詢寫入對應的隊列,比如當前消息寫入 consumeQueue 0,下一條寫入 consumeQueue 1,…,不斷地循環。
  • 持久化之后可以知道消息在 commitlog 文件中的偏移量和消息體大小,如果 consumer 指定訂閱了 topic 和 tag,還會算出 tag hashCode,這樣的話就可以將這三者順序寫入 queueId 對應的 consumeQueue 中。
  • 消費者消費:每一個 consumeQueue 都能找到每個消費者的消息進度(consumeOffset),據此可以快速定位其所在的 consumeQueue 的文件位置,取出 commitlog offset,size,tag hashcode 這三個值,然后首先根據 tag hashcode 來過濾消息,如果匹配上了再根據 commitlog offset,size 這兩個元素到commitlog 中去查找相應的消息然后再發給消費者。

注意:所有 Topic 的消息都寫入同一個 commitlog 文件(而不是每個 Topic 對應一個 commitlog 文件),然后消息寫入后會根據 topic,queueId 找到 Topic 所在的 consumeQueue 再寫入。

需要注意的是我們的 Broker 是要設定為高性能的(10 w QPS)那么上面這些步驟有兩個瓶頸點。

  • producer 發送消息到持久化至 commitlog 文件的性能問題

如圖示,Broker 收到消息后是先將消息寫到了內核緩沖區 的 page cache 中,最終將消息刷盤,那么消息是寫到 page cache 返回 ack,還是刷盤后再返回呢,這取決于你消息的重要性,如果是像日志這樣的消息,丟了其實也沒啥影響,這種情況下顯然可以選擇寫到 page cache 后就馬上返回,OS 會擇機將其刷盤,這種刷盤方式我們將其稱為異步刷盤,這也是大多數業務場景選擇的刷盤方式,這種方式其實已經足夠安全了,哪怕 JVM 掛掉了,由于 page cache 是由 OS 管理的,OS 也能保證將其刷盤成功,除非 Broker 機器宕機。當然對于像轉賬等安全性極高的金融場景,我們可能還是要將消息從 page cache 刷盤后再返回 ack,這種方式我們稱為同步刷盤,顯然這種方式會讓性能大大降低,使用要慎重。

  • consumer 拉取消息的性能問題

很顯然這一點不是什么問題,上文提到,不管是 commitlog 還是 consumeQueue 文件,都緩存在 page cache 中,那么直接從 page cache 中讀消息即可,由于是基于內存的操作,不存在什么瓶頸,當然這是基于消費進度與生產進度差不多的前提,如果某個消費者指定要從某個進度開始消費,且此進度對應的 commitlog 文件不在 page cache 中,那就會觸發磁盤 IO。

Broker 的高可用

上文我們都是基于一個 Broker 來討論的,這顯然有問題,Broker 如果掛了,依賴它的 producer,consumer 不就也嗝屁了嗎,所以 broker 的高可用是必須的,一般采用主從模式來實現 broker 的高可用。

如圖示:Producer 將消息發給 主 Broker ,然后 consumer 從主 Broker 里拉消息,而 從 Broker 則會從主 Broker 同步消息,這樣的話一旦主 Broker 宕機了,consumer 可以從 Broker 里拉消息,同時在 RocketMQ 4.5 以后,引入一種 dledger 模式,這種模式要求一主多從(至少 3 個節點),這樣如果主 Broker 宕機后,另外多個從 Broker 會根據 Raft 協議選舉出一個主 Broker,Producer 就可以向這個新選舉出來的主節點發送消息了。

如果 QPS 很高只有一個主 Broker 的話也存在性能上的瓶頸,所以生產上一般采用多主的形式,如下圖示:

這樣的話 Producer 可以負載均衡地將消息發送到多個 Broker 上,提高了系統的負載能力,不難發現這意味著 Topic 是分布式存儲在多個 Broker 上的,而 Topic 在每個 Broker 上的存儲都是以多個 consumeQueue 的形式存在的,這極大地提升了 Topic 的水平擴展與系統的并發執行能力。

nameserver

目前為止我們的設計貌似不錯,通過一系列設計讓 Broker 滿足了高性能,高擴展的要求,但我們似乎忽略了一個問題,Producer,Consumer 該怎么和 Broker 通信呢,一種做法是在 Producer,Consumer 寫死要通信的 Broker ip 地址,雖然可行,但這么做的話顯然會有很大的問題,配置死板,擴展性差,考慮以下場景。

  • 如果擴容(新增 Broker),producer 和 consumer 是不是也要跟著新增 Broker ip 地址。
  • 每次新增 Topic 都要指定在哪些 Broker 存儲,我們知道 producer 在發消息consumer 在訂閱消息的時候都要指定對應的 Topic ,那就意味著每次新增 Topic 后都需要在 producer,consumer 做相應變更(記錄 topic -> broker 地址)。
  • 如果 broker 宕機了,producer 和 consumer 需要將其從配置中移除,這就意味著 producer,consumer 需要與相關的 brokers 通過心跳來通信以便知道其存活與否,這樣無疑增加了設計的復雜度。

參考下 dubbo 這類 RPC 框架,你會發現基本上都會新增一個類似 Zookeeper 這樣的注冊中心的中間層(一般稱其為 nameserver),如下:

主要原理如下:

為了保證高可用,一般 nameserver 以集群的形式存在(至少兩個),Broker 啟動后不管主從都會向每一個 nameserver 注冊,注冊的信息有哪些呢,想想看 producer 要發消息給 broker 需要知道哪些信息呢,首先發消息要指定 Topic,然后要指定 Topic 所在的 broker,再然后是知道 Topic 在 Broker 中的隊列數量(可以這樣負載均衡地將消息發送到這些 queue 中),所以 broker 向 nameserver 注冊的信息中應該包含以下信息。

這樣的話 producer 和 consumer 就可以通過與 nameserver 建立長連接來定時(比如每隔 30 s)拉取這些路由信息從而更新到本地,發送/消費消息的時候就可以依據這些路由信息進行發送/消費。

那么加了一個 nameserver 和原來的方案相比有什么好處呢,可以很明顯地看出:producer/consumer 與具體的 broker 解耦了,極大提升了整體架構的可擴展性:

  • producer/consumer 的所有路由信息都能通過 nameserver 得到,比如現在要在 brokers 上新建一個 Topic,那么 brokers 會把這些信息同步到 nameserver,而 producer/consumer 會定時去 nameserver 拉取這些路由信息更新到本地,做到了路由信息配置的自動化。
  • 同樣的如果某些 broker 宕機了,由于 broker 會定時上報心跳到 nameserver 以告知其存活狀態,一旦 nameserver 監測到 broker 失效了,producer/consumer 也能從中得到其失效信息,從而在本地路由中將其剔除。

可以看到通過加了一層 nameserver,producer/consumer 路由信息做到了配置自動化,再也不用手動去操作了,整體架構甚為合理。

總結

以上即我們所要闡述的 RocketMQ 的設計理念,基本上涵蓋了重要概念的介紹,我們再來簡單回顧一下:

首先根據業務場景我們提出了 RocketMQ 設計的三大目標:消息持久化,高性能,高可用,毫無疑問 broker 的設計是實現這三大目標的關鍵,為了消息持久化,我們設計了 commitlog 文件,通過順序寫的方式保證了文件寫入的高性能,但如果每次 producer 寫入消息或者 consumer 讀取消息都從文件來讀寫,由于涉及到磁盤 IO 顯然性能會有很大的問題,于是我們了解到操作系統讀寫文件會先將文件加載到內存中的 page cache 中。對于傳統的文件 IO,由于 page cache 存在內核空間中,還需要將其拷貝到用戶空間中才能為進程所用(同樣的,寫入消息也要寫將消息寫入用戶空間的 buffer,再拷貝到 內核空間中的 page cache),于是我們使用了 mmap 來避免了這次拷貝,這樣的話 producer 發送消息只要先把消息寫入 page cache 再異步刷盤,而 consumer 只要保證消息進度能跟得上 producer 產生消息的進度,就可以直接從 page cache 中讀取消息進行消費,于是 producer 與 consumer 都可以直接從 page cache 中讀寫消息,極大地提升了消息的讀寫性能,那怎么保證 consumer 消費足夠快以跟上 producer 產生消息的速度的,顯然,讓消息分布式,分片存儲是一種通用方案,這樣的話通過增加 consumer 即可達到并發消費消息的目的。

最后,為了避免每次創建 Topic 或者 broker 宕機都得修改 producer/consumer 上的配置,我們引入了 nameserver, 實現了服務的自動發現功能。。

仔細與其它 RPC 框架橫向對比后,你會發現這些 RPC 框架用的思想其實都很類似,比如數據使用分片存儲以提升數據存儲的水平擴展與并發執行能力,使用 zookeeper,nameserver 等注冊中心來達到服務注冊與自動發現的目的,所以掌握了這些思想, 我們再去觀察學習或設計 RPC 時就能達到事半功倍的效果。

責任編輯:武曉燕 來源: 碼海
相關推薦

2021-05-17 18:27:20

Token驗證HTTP

2022-02-07 09:40:10

高可用高并發高性能

2021-07-14 18:21:50

負載均衡TCP網關

2025-01-21 14:11:32

2024-05-29 08:56:31

2022-03-14 17:56:15

云廠商系統阿里云

2021-03-11 12:27:36

java 變量數量

2021-04-26 08:16:18

CPU 語言編寫

2021-03-04 13:14:54

文件系統存儲

2021-02-04 11:01:59

計算機信號轉換

2023-05-15 10:03:00

Redis緩存穿透

2021-01-14 09:04:24

線程池工具類面試

2018-05-04 15:57:42

AI智慧谷歌

2022-07-08 15:13:21

DockerLinux命令

2022-10-09 09:38:10

高可用設計

2010-06-28 15:58:45

EclipseJavaIDE

2010-06-29 13:39:26

Eclipse什么玩意兒

2010-07-02 10:10:09

Eclipse

2010-07-05 15:56:01

EclipseRCPECF

2018-01-26 08:54:29

存儲SSDHDD
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久综合伊人 | 国产成人精品亚洲日本在线观看 | 国产免费观看视频 | 久久久久国产精品一区 | 国产第二页| 久久精品亚洲国产奇米99 | 黄色av网站免费看 | 免费在线看黄 | 黄色毛片视频 | 精品中文字幕一区 | 91精品国产一区二区三区动漫 | 国产91 在线播放 | 精品美女视频在免费观看 | 中文字幕第二十页 | 日韩欧美精品一区 | 99久久久久久99国产精品免 | 黄色一级毛片免费看 | 国产成人精品亚洲日本在线观看 | 免费黄色的网站 | 久久新 | 黄色大片毛片 | 91久久精品国产91久久 | 在线观看成人精品 | 久久综合久久自在自线精品自 | 成人在线观看免费视频 | 欧美激情精品久久久久久变态 | 亚洲精品日日夜夜 | 国产一区二区三区www | 欧美天堂在线观看 | 亚洲激情一区二区三区 | 欧美一二三区 | 中文字幕在线一区二区三区 | jlzzjlzz国产精品久久 | 韩三级在线观看 | 国产三级精品三级在线观看四季网 | 男女视频在线免费观看 | 99免费在线观看视频 | 在线不卡视频 | 亚洲一区二区视频在线观看 | 国产在线一区二区 | 东方伊人免费在线观看 |