消息隊列 -- -- 入門

目錄

消息隊列 -- -- 入門 

消息隊列(Message Queue 簡稱 MQ)簡介

科普:

 把數據放到消息隊列叫做生產者

 從消息隊列裏邊取數據叫做消費者

消息中間件

1.消息中間件的簡介

2.消息中間件的組成

3.消息中間件模式分類

3.1 點對點(P2P):使用queue作爲通信載體 

應用場景:如果希望發送的每個消息都會被成功處理的話,那麼需要P2P模式。

3.2 發佈/訂閱(Pub/Sub 廣播):使用topic作爲通信載體 

應用場景:如果希望發送的消息可以不被做任何處理、或者只被一個消息者處理、或者可以被多個消費者處理的話,那麼可以採用Pub/Sub模型。

4.爲什麼要使用消息中間件?

 一般互聯網項目用戶請求不超過200ms體驗是最好的

          還是舉個簡單的例子:

1.解耦

2.提速

3.廣播

4.削峯

5.消息中間件的優勢?

6.消息中間件應用場景

7.消息中間件常用協議

8.常見消息中間件MQ介紹

Kafka一般應用在大數據日誌處理或對實時性(少量延遲),可靠性(少量丟數據)要求稍低的場景使用。

9.消息隊列相關技術要點

9.1如何保證消息隊列是高可用的?

以rcoketMQ爲例,

以Kafka爲例,

9.2如何保證消息不被重複消費?

9.3如何保證消費的可靠性傳輸?

RabbitMQ

kafka

9.4如何保證消息的順序性?

1)rabbitmq保證數據的順序性

2)kafka保證數據的順序性


                                 消息隊列 -- -- 入門 



消息隊列(Message Queue 簡稱 MQ)簡介

消息隊列(又稱 報文隊列):指在消息的傳輸過程中用來保存消息的容器站在我們專業的角度上來講就是:我們要把傳輸在計算機間的數據單位,用隊列的存儲方式保存起來。

消息(Message):指在一組(兩臺)計算機間傳送的數據單位。  消息可以非常簡單,例如只包含文本字符串;也可以更復雜,可能包含嵌入對象.

隊列(Queue):隊列雖然和棧(stack)一樣,是一種操作受限制的線性表,但是隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,所以只有最早進入隊列的元素才能最先從隊列中刪除,因此隊列又被稱爲先進先出線性表。

FIFO(first in first out):新元素(等待進入隊列的元素)總是被插入到鏈表的尾部,而讀取的時候總是從鏈表的頭部開始讀取;每次讀取一個元素,釋放一個元素。


科普:

  •  把數據放到消息隊列叫做生產者

  •  從消息隊列裏邊取數據叫做消費者


消息中間件

  • 1.消息中間件的簡介

目前對消息中間件(MOM)的定義還未形成統一的行業標準,我國也正加快對消息中間件技術的標準化研究工作。一般認爲,消息中間件是一種由消息傳送機制消息隊列模式組成的消息中間件,利用高效可靠的消息傳遞機制進行平臺無關的數據交流,並基於數據通信來進行分佈式系統的集成。與其它消息中間件不同(例如ORB 和RPC),一般來說,消息中間件並不要求系統具備一個可靠的底部傳輸層,而是通過以消息的形式收發應用程序數據來連接運行於不同系統上的應用程序。信息可以同步傳送,也支持異步傳送。在異步方式下,應用程序並不需要消息即時即刻傳送到對方,只是由MOM 確保把信息以消息的方式傳送到適當的目的地,並且只傳一次。

消息中間件屬於中間件的一種,在Java中消息中間件一般指的是將具體業務和底層邏輯解耦的軟件。擁有中間件的主要特點,但是自身的工作機制又具有特殊性,主要特點包括以下6 個方面:(1)異步傳送;(2)防禦通信;(3)併發執行;(4)日誌通信;(5)多種通信方式;(6)應用程序與網絡複雜性相隔離。

  • 2.消息中間件的組成

2.1 Broker:消息服務器,作爲server提供消息核心服務

2.2 Producer:消息生產者,業務的發起方,負責生產消息傳輸給broker,

2.3 Consumer:消息消費者,業務的處理方,負責從broker獲取消息並進行業務邏輯處理

2.4 Topic:主題,發佈訂閱模式下的消息統一彙集地,不同生產者向topic發送消息,由MQ服務器分發到不同的訂閱者,實現消息的廣播

2.5 Queue:隊列,PTP模式下,特定生產者向特定queue發送消息,消費者訂閱特定的queue完成指定消息的接收

2.6 Message:消息體,根據不同通信協議定義的固定格式進行編碼的數據包,來封裝業務數據,實現消息的傳輸



  • 3.消息中間件模式分類

3.1 點對點(P2P:使用queue作爲通信載體 

它好比是兩個人打電話,這兩個人是獨享這一條通信鏈路的。一方發送消息,另外一方接收,就這麼簡單。在點對點模式下,消息被保留在隊列中。 一個或多個消費者可以消耗隊列中的消息,但是特定消息只能由最多一個消費者消費。 一旦消費者讀取隊列中的消息,它就從該隊列中消失。 該模式的典型示例,如訂單處理系統,其中每個訂單將由一個訂單處理器處理,但多個訂單處理器也可以同時工作。

總的來說:queue實現了負載均衡,將producer生產的消息發送到消息隊列中,由多個消費者消費。但一個消息只能被一個消費者接受,當沒有消費者可用時,這個消息會被保存直到有一個可用的消費者。 

  P2P模式

  P2P模式包含三個角色:消息隊列(Queue),發送者(Sender),接收者(Receiver)。每個消息都被髮送到一個特定的隊列,接收者從隊列中獲取消息。隊列保留着消息,直到他們被消費或超時。

  P2P的特點

  • 每個消息只有一個消費者(Consumer)(即一旦被消費,消息就不再在消息隊列中)
  • 發送者和接收者之間在時間上沒有依賴性,也就是說當發送者發送了消息之後,不管接收者有沒有正在運行,它不會影響到消息被髮送到隊列
  • 接收者在成功接收消息之後需向隊列應答成功

應用場景:如果希望發送的每個消息都會被成功處理的話,那麼需要P2P模式。


3.2 發佈/訂閱Pub/Sub 廣播):使用topic作爲通信載體 

該模式有點類似於我們日常生活中訂閱報紙。對於每一個訂閱者來說,可以選擇一份或者多份報紙。那麼這些我們訂閱的報紙,就相當於發佈訂閱模式裏的topic。有很多個人訂閱報紙,也有人可能和我訂閱了相同的報紙。多人訂閱了相同的報紙相當於多人在同一個topic裏註冊了。對於一份報紙發行方來說,它和所有的訂閱者就構成了一個1對多的關係。在這種模式下,消息被保留在主題中。 與點對點模式不同,消費者可以訂閱一個或多個主題並使用該主題中的所有消息。 該模式下消息生產者稱爲發佈者,消息使用者稱爲訂閱者。

總的來說:topic實現了發佈和訂閱,當你發佈一個消息,所有訂閱這個topic的服務都能得到這個消息,所以從1到N個訂閱者都能得到一個消息的拷貝。

  包含三個角色:主題(Topic),發佈者(Publisher),訂閱者(Subscriber) 。多個發佈者將消息發送到Topic,系統將這些消息傳遞給多個訂閱者。

  Pub/Sub的特點

  • 每個消息可以有多個消費者
  • 發佈者和訂閱者之間有時間上的依賴性。針對某個主題(Topic)的訂閱者,它必須創建一個訂閱者之後,才能消費發佈者的消息。
  • 爲了消費消息,訂閱者必須保持運行的狀態。

應用場景:如果希望發送的消息可以不被做任何處理、或者只被一個消息者處理、或者可以被多個消費者處理的話,那麼可以採用Pub/Sub模型。


消息中間件解決的就是分佈式系統之間消息傳遞的問題。

                   

                    ①生產者和消費者之間通過某種方式(點對點或者訂閱)實現"綁定"!

                    ②生產者生產數據,併發送到消息中間件,消息中間件進行落庫處理!

                    ③訂閱了消息的消費者通過監聽器監聽消息中間件,如果有屬於自己的消息,進行消費!



  • 4.爲什麼要使用消息中間件?

回答這個問題個人建議可以從這三個關鍵點(或者說應用場景)去回答:解耦、異步、削峯。

個人認爲消息隊列的主要特點是異步處理,主要目的是減少請求響應時間和解耦。比方說,在高併發環境下,由於來不及同步處理,請求往往會發生堵塞,例如有大量的insert,update之類的請求同時到達MySql,將會直接導致無數的行鎖表鎖,甚至最後請求會堆積過多,從而觸發too many connections錯誤。所以使用消息隊列主要的使用場景就是將比較耗時而且不需要即時(同步)返回結果的操作作爲消息放入消息隊列,從而緩解系統的壓力。由於使用了消息隊列,我們只需要保證消息格式不變,消息的發送方和接收方並不需要彼此聯繫,也不需要受對方的影響,即解耦和。

解耦
異步

                一般互聯網項目用戶請求不超過200ms體驗是最好的

削峯

還是舉個簡單的例子:-----(來自知乎 祁達方的經典回答

業務場景:翠花是小強的姐姐,翠花希望小強多讀書,所有經常尋找好書給小強看。之前的方式(傳統模式)是這樣:翠花問小強什麼時候有空,把書給小強送去,並親眼監督小強讀完書才走。久而久之,兩人都覺得麻煩;後來的方式(中間件模式)改成了:翠花對小強說「我放到書架上的書你都要看」,然後翠花每次發現不錯的書都放到書架上,小強則看到書架上有書就拿下來看。

書架就相當於我們所說的  一個消息隊列(容器),而翠花是生產者,小強則是消費者。

這帶來的好處有:

1.翠花想給小強書的時候,不必問小強什麼時候有空,親手把書交給他了,翠花只把書放到書架上就行了。這樣翠花小強的時間都更自由。

2.翠花相信小強的讀書自覺和讀書能力,不必親眼觀察小強的讀書過程,翠花只要做一個放書的動作,很節省時間。

3.當明天有另一個愛讀書的小夥伴小強加入,翠花仍舊只需要把書放到書架上,小強和小強從書架上取書即可(唔,姑且設定成多個人取一本書可以每人取走一本吧,可能是拷貝電子書或複印,暫不考慮版權問題)。

4.書架上的書放在那裏,小強閱讀速度快就早點看完,閱讀速度慢就晚點看完,沒關係,比起翠花把書遞給小強並監督小強讀完的方式,小強的壓力會小一些。

這就是我們消息隊列的四大優點

1.解耦

每個成員不必受其他成員影響,可以更獨立自主,只通過一個簡單的容器來聯繫。

翠花甚至可以不知道從書架上取書的是誰,小強也可以不知道往書架上放書的人是誰,在他們眼裏,都只有書架,沒有對方。

毫無疑問,與一個簡單的容器打交道,比與複雜的人打交道容易一萬倍,翠花小強可以自由自在地追求各自的人生。

2.提速

翠花選擇相信「把書放到書架上,別的我不問」,爲自己節省了大量時間。

翠花很忙,只能抽出五分鐘時間,但這時間足夠把書放到書架上了。

3.廣播

翠花只需要勞動一次,就可以讓多個小夥伴有書可讀,這大大地節省了她的時間,也讓新的小夥伴的加入成本很低。

4.削峯

假設小強讀書很慢,如果採用翠花每給一本書都監督小強讀完的方式,小強有壓力,翠花也不耐煩。

反正翠花給書的頻率也不穩定,如果今明兩天連給了五本,之後隔三個月才又給一本,那小強只要在三個月內從書架上陸續取走五本書讀完就行了,壓力就不那麼大了。

當然,使用消息隊列也有其成本:

1.引入複雜度

毫無疑問,「書架」這東西是多出來的,需要地方放它,還需要防盜。

2.暫時的不一致性

假如媽媽問翠花「小強最近讀了什麼書」,在以前的方式裏,翠花因爲親眼監督小強讀完書了,可以底氣十足地告訴媽媽,但新的方式裏,翠花回答媽媽之後會心想「小強應該會很快看完吧……」

這中間存在着一段「媽媽認爲小強看了某書,而小強其實還沒看」的時期,當然,小強最終的閱讀狀態與媽媽的認知會是一致的,這就是所謂的「最終一致性」。

那麼,該使用消息隊列的情況需要滿足什麼條件呢?

1.生產者不需要從消費者處獲得反饋

引入消息隊列之前的直接調用,其接口的返回值應該爲空,這才讓明明下層的動作還沒做,上層卻當成動作做完了繼續往後走——即所謂異步——成爲了可能。

翠花放完書之後小強到底看了沒有,翠花根本不問,她默認他是看了,否則就只能用原來的方法監督到看完了。

2.容許短暫的不一致性

媽媽可能會發現「有時候據說小強看了某書,但事實上他還沒看」,只要媽媽滿意於「反正他最後看了就行」,異步處理就沒問題。

如果媽媽對這情況不能容忍,對翠花大發雷霆,翠花也就不敢用書架方式了。

3.確實是用了有效果

即解耦、提速、廣播、削峯這些方面的收益,超過放置書架、監控書架這些成本。

否則如果是盲目照搬,「聽說老趙家買了書架,咱們家也買一個」,買回來卻沒什麼用,只是讓步驟變多了,還不如直接把書遞給對方呢,那就不對了。

所以迴歸現實:當我們在軟件的正常功能開發中,並不需要去刻意的尋找消息隊列的使用場景,而是當出現性能瓶頸時,去查看業務邏輯是否存在可以異步處理的耗時操作,如果存在的話便可以引入消息隊列來解決。否則盲目的使用消息隊列可能會增加維護和開發的成本卻無法得到可觀的性能提升,那就得不償失了。



5.消息中間件的優勢?

5.1 系統解耦

交互系統之間沒有直接的調用關係,只是通過消息傳輸,故系統侵入性不強,耦合度低。

5.2 提高系統響應時間

例如原來的一套邏輯,完成支付可能涉及先修改訂單狀態、計算會員積分、通知物流配送幾個邏輯才能完成;通過MQ架構設計,就可將緊急重要(需要立刻響應)的業務放到該調用方法中,響應要求不高的使用消息隊列,放到MQ隊列中,供消費者處理。

5.3 爲大數據處理架構提供服務

通過消息作爲整合,大數據的背景下,消息隊列還與實時處理架構整合,爲數據處理提供性能支持。

5.4 Java消息服務——JMS

Java消息服務(Java Message Service,JMS)應用程序接口是一個Java平臺中關於面向消息中間件(MOM)的API,用於在兩個應用程序之間,或分佈式系統中發送消息,進行異步通信。 
JMS中的P2P和Pub/Sub消息模式:點對點(point to point, queue)與發佈訂閱(publish/subscribe,topic)最初是由JMS定義的。這兩種模式主要區別或解決的問題就是發送到隊列的消息能否重複消費(多訂閱)。



  • 6.消息中間件應用場景

6.1 異步通信

有些業務不想也不需要立即處理消息。消息隊列提供了異步處理機制,允許用戶把一個消息放入隊列,但並不立即處理它。想向隊列中放入多少消息就放多少,然後在需要的時候再去處理它們。

6.2 解耦

降低工程間的強依賴程度,針對異構系統進行適配。在項目啓動之初來預測將來項目會碰到什麼需求,是極其困難的。通過消息系統在處理過程中間插入了一個隱含的、基於數據的接口層,兩邊的處理過程都要實現這一接口,當應用發生變化時,可以獨立的擴展或修改兩邊的處理過程,只要確保它們遵守同樣的接口約束。

6.3 冗餘

有些情況下,處理數據的過程會失敗。除非數據被持久化,否則將造成丟失。消息隊列把數據進行持久化直到它們已經被完全處理,通過這一方式規避了數據丟失風險。許多消息隊列所採用的”插入-獲取-刪除”範式中,在把一個消息從隊列中刪除之前,需要你的處理系統明確的指出該消息已經被處理完畢,從而確保你的數據被安全的保存直到你使用完畢。

6.4 擴展性

因爲消息隊列解耦了你的處理過程,所以增大消息入隊和處理的頻率是很容易的,只要另外增加處理過程即可。不需要改變代碼、不需要調節參數。便於分佈式擴容。

6.5 過載保護

在訪問量劇增的情況下,應用仍然需要繼續發揮作用,但是這樣的突發流量無法提取預知;如果以爲了能處理這類瞬間峯值訪問爲標準來投入資源隨時待命無疑是巨大的浪費。使用消息隊列能夠使關鍵組件頂住突發的訪問壓力,而不會因爲突發的超負荷的請求而完全崩潰。

6.6 可恢復性

系統的一部分組件失效時,不會影響到整個系統。消息隊列降低了進程間的耦合度,所以即使一個處理消息的進程掛掉,加入隊列中的消息仍然可以在系統恢復後被處理。

6.7 順序保證

在大多使用場景下,數據處理的順序都很重要。大部分消息隊列本來就是排序的,並且能保證數據會按照特定的順序來處理。

6.8 緩衝

在任何重要的系統中,都會有需要不同的處理時間的元素。消息隊列通過一個緩衝層來幫助任務最高效率的執行,該緩衝有助於控制和優化數據流經過系統的速度。以調節系統響應時間。

6.9 數據流處理

分佈式系統產生的海量數據流,如:業務日誌、監控數據、用戶行爲等,針對這些數據流進行實時或批量採集彙總,然後進行大數據分析是當前互聯網的必備技術,通過消息隊列完成此類數據收集是最好的選擇。



7.消息中間件常用協議

7.1 AMQP協議 -- -- 高級消息隊列協議

AMQP即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件不同產品,不同開發語言等條件的限制。 
優點:可靠、通用

7.2 MQTT協議

MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸)是IBM開發的一個即時通訊協議,有可能成爲物聯網的重要組成部分。該協議支持所有平臺,幾乎可以把所有聯網物品和外部連接起來,被用來當做傳感器和致動器(比如通過Twitter讓房屋聯網)的通信協議。 
優點:格式簡潔、佔用帶寬小、移動端通信、PUSH、嵌入式系統

7.3 STOMP協議

STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息協議,是一種爲MOM(Message Oriented Middleware,面向消息的中間件)設計的簡單文本協議。STOMP提供一個可互操作的連接格式,允許客戶端與任意STOMP消息代理(Broker)進行交互。 
優點:命令模式(非topic\queue模式)

7.4 XMPP協議

XMPP(可擴展消息處理現場協議,Extensible Messaging and Presence Protocol)是基於可擴展標記語言(XML)的協議,多用於即時消息(IM)以及在線現場探測。適用於服務器之間的準即時操作。核心是基於XML流傳輸,這個協議可能最終允許因特網用戶向因特網上的其他任何人發送即時消息,即使其操作系統和瀏覽器不同。 
優點:通用公開、兼容性強、可擴展、安全性高,但XML編碼格式佔用帶寬大

7.5 其他基於TCP/IP自定義的協議

有些特殊框架(如:redis、kafka、zeroMq等)根據自身需要未嚴格遵循MQ規範,而是基於TCP\IP自行封裝了一套協議,通過網絡socket接口進行傳輸,實現了MQ的功能。



8.常見消息中間件MQ介紹

8.1 RocketMQ

阿里系下開源的一款分佈式、隊列模型的消息中間件,原名Metaq,3.0版本名稱改爲RocketMQ,是阿里參照kafka設計思想使用java實現的一套mq。同時將阿里系內部多款mq產品(Notify、metaq)進行整合,只維護核心功能,去除了所有其他運行時依賴,保證核心功能最簡化,在此基礎上配合阿里上述其他開源產品實現不同場景下mq的架構,目前主要多用於訂單交易系統。

具有以下特點:

  • 能夠保證嚴格的消息順序
  • 提供針對消息的過濾功能
  • 提供豐富的消息拉取模式
  • 高效的訂閱者水平擴展能力
  • 實時的消息訂閱機制
  • 億級消息堆積能力

官方提供了一些不同於kafka的對比差異: https://rocketmq.apache.org/docs/motivation/


8.2 RabbitMQ

RabbitMQ是使用Erlang編寫的一個開源的消息隊列,是AMQP(高級消息隊列協議)的標準實現,除此之外還支持很多的協議:XMPP, SMTP,STOMP,也正是如此,使它變的非常重量級,更適合於企業級的開發。同時實現了Broker架構,核心思想是生產者不會將消息直接發送給隊列,消息在發送給客戶端時先在中心隊列排隊。對路由(Routing),負載均衡(Load balance)、數據持久化都有很好的支持。多用於進行企業級的ESB整合以及它在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面也表現不俗。

AMQP協議模型
RabbitMQ消息是如何實現流轉的
RabbitMQ結構圖

上圖中有幾個重要概念:

  • Broker:簡單來說就是消息隊列服務器實體。
  • Exchange:消息交換機,它指定消息按什麼規則,路由到哪個隊列。
  • Queue:消息隊列載體,每個消息都會被投入到一個或多個隊列。
  • Binding:綁定,它的作用就是把Exchange和Queue按照路由規則綁定起來。
  • Routing Key:路由關鍵字,Exchange根據這個關鍵字進行消息投遞。
  • vhost:虛擬主機,一個broker裏可以開設多個vhost,用作不同用戶的權限分離。
  • producer:消息生產者,就是投遞消息的程序。
  • consumer:消息消費者,就是接受消息的程序。
  • channel:消息通道,在客戶端的每個連接裏,可建立多個channel,每個channel代表一個會話任務。

消息隊列的使用過程,如下:

  1. 客戶端連接到消息隊列服務器,打開一個channel。
  2. 客戶端聲明一個exchange,並設置相關屬性。
  3. 客戶端聲明一個queue,並設置相關屬性。
  4. 客戶端使用routing key,在exchange和queue之間建立好綁定關係。
  5. 客戶端投遞消息到exchange。

  exchange接收到消息後,就根據消息的key和已經設置的binding,進行消息路由,將消息投遞到一個或多個隊列裏。


8.3 ActiveMQ

ActiveMQ 是Apache下的一個子項目。使用Java完全支持JMS1.1和J2EE 1.4規範的 JMS Provider實現,少量代碼就可以高效地實現高級應用場景。可插拔的傳輸協議支持,比如:in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多種語言客戶端 C++、Java、.Net,、Python、 Php、 Ruby等。

儘管JMS規範出臺已經是很久的事情了,但是JMS在當今的J2EE應用中間仍然扮演着特殊的地位。
ActiveMQ特性如下:

  1. 多種語言和協議編寫客戶端。
    語言: Java,C,C++,C#,Ruby,Perl,Python,PHP。
    應用協議: OpenWire,Stomp REST,WS Notification,XMPP,AMQP
  2. 完全支持JMS1.1和J2EE 1.4規範 (持久化,XA消息,事務)
  3. 對Spring的支持。
    ActiveMQ可以很容易內嵌到使用Spring的系統裏面去,而且也支持Spring2.0的特性
  4. 通過了常見J2EE服務器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的測試,其中通過JCA 1.5 resource adaptors的配置,可以讓ActiveMQ可以自動的部署到任何兼容J2EE 1.4 商業服務器上
  5. 支持多種傳送協議:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA
  6. 支持通過JDBC和journal提供高速的消息持久化
  7. 從設計上保證了高性能的集羣,客戶端-服務器,點對點
  8. 支持Ajax
  9. 支持與Axis的整合
  10. 可以很容易得調用內嵌JMS provider,進行測試

8.4 Redis

使用C語言開發的一個Key-Value的NoSQL數據庫,開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它本身支持MQ功能,所以完全可以當做一個輕量級的隊列服務來使用。對於RabbitMQ和Redis的入隊和出隊操作,各執行100萬次,每10萬次記錄一次執行時間。測試數據分爲128Bytes、512Bytes、1K和10K四個不同大小的數據。實驗表明:入隊時,當數據比較小時Redis的性能要高於RabbitMQ,而如果數據大小超過了10K,Redis則慢的無法忍受;出隊時,無論數據大小,Redis都表現出非常好的性能,而RabbitMQ的出隊性能則遠低於Redis。


8.5 Kafka

Kafka是Apache下的一個子項目,使用scala實現的一個高性能分佈式Publish/Subscribe消息隊列系統,它可以處理消費者規模的網站中的所有動作流數據。 這種動作(網頁瀏覽,搜索和其他用戶的行動)是在現代網絡上的許多社會功能的一個關鍵因素。 這些數據通常是由於吞吐量的要求而通過處理日誌和日誌聚合來解決。 對於像Hadoop的一樣的日誌數據和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。Kafka的目的是通過Hadoop的並行加載機制來統一線上和離線的消息處理,也是爲了通過集羣機來提供實時的消費。
Kafka是一種高吞吐量的分佈式發佈訂閱消息系統,有如下特性:

  • 快速持久化:通過磁盤順序讀寫與零拷貝機制,可以在O(1)的磁盤數據結構提供消息的持久化,這種結構對於即使數以TB的消息存儲也能夠保持長時間的穩定性能。(文件追加的方式寫入數據,過期的數據定期刪除)
  • 支持Hadoop數據並行加載:對於像Hadoop的一樣的日誌數據和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。
  • 完全的分佈式系統:Broker、Producer、Consumer都原生自動支持分佈式,依賴zookeeper自動實現複雜均衡;
  • 高吞吐量:即使是非常普通的硬件(服務器)上Kafka也可以支持每秒數百萬的吞吐速率。
  • 高堆積:支持topic下消費者較長時間離線,消息堆積量大;
  • 支持通過Kafka服務器和消費機集羣來分區消息。

Kafka相關概念

  Broker:Kafka集羣包含一個或多個服務器,這種服務器被稱爲broker

  Topic:每條發佈到Kafka集羣的消息都有一個類別,這個類別被稱爲Topic。(物理上不同Topic的消息分開存儲,邏輯上一個Topic的消息雖然保存於一個或多個broker上但用戶只需指定消息的Topic即可生產或消費數據而不必關心數據存於何處)

  Partition:Parition是物理上的概念,每個Topic包含一個或多個Partition.

  Producer:負責發佈消息到Kafka Broker

  Consumer:消息消費者,向Kafka Broker讀取消息的客戶端。

  Consumer Group:每個Consumer屬於一個特定的Consumer Group(可爲每個Consumer指定group name,若不指定group name則屬於默認的group)。

Kafka一般應用在大數據日誌處理或對實時性(少量延遲),可靠性(少量丟數據)要求稍低的場景使用。


8.6 ZeroMQ

號稱最快的消息隊列系統,專門爲高吞吐量/低延遲的場景開發,在金融界的應用中經常使用,偏重於實時數據通信場景。ZMQ能夠實現RabbitMQ不擅長的高級/複雜的隊列,但是開發人員需要自己組合多種技術框架,開發成本高。因此ZeroMQ具有一個獨特的非中間件的模式,更像一個socket library,你不需要安裝和運行一個消息服務器或中間件,因爲你的應用程序本身就是使用ZeroMQ API完成邏輯服務的角色。但是ZeroMQ僅提供非持久性的隊列,如果down機,數據將會丟失。如:Twitter的Storm中使用ZeroMQ作爲數據流的傳輸。

ZeroMQ 它實際類似於Socket的一系列接口,他跟Socket的區別是:普通的socket是端到端的(1:1的關係),而ZMQ卻是可以N:M 的關係,人們對BSD套接字瞭解較多的是點對點的連接,點對點連接需要顯式地建立連接、銷燬連接、選擇協議(TCP/UDP)和處理錯誤等,而ZMQ屏蔽了這些細節,讓你的網絡編程更爲簡單。ZMQ用於node與node間的通信,node可以是主機或者是進程;ZeroMQ套接字是與傳輸層無關的:ZeroMQ套接字對所有傳輸層協議定義了統一的API接口。默認支持 進程內(inproc) ,進程間(IPC) ,多播,TCP協議,在不同的協議之間切換隻要簡單的改變連接字符串的前綴。可以在任何時候以最小的代價從進程間的本地通信切換到分佈式下的TCP通信。ZeroMQ在背後處理連接建立,斷開和重連邏輯。
  引用官方的說法: “ZMQ(以下ZeroMQ簡稱ZMQ)是一個簡單好用的傳輸層,像框架一樣的一個socket library,他使得Socket編程更加簡單、簡潔和性能更高。是一個消息處理隊列庫,可在多個線程、內核和主機盒之間彈性伸縮。ZMQ的明確目標是“成爲標準網絡協議棧的一部分,之後進入Linux內核”。現在還未看到它們的成功。但是,它無疑是極具前景的、並且是人們更加需要的“傳統”BSD套接字之上的一層封裝。ZMQ讓編寫高性能網絡應用程序極爲簡單和有趣。”

特點是:

  • 高性能,非持久化;
  • 跨平臺:支持Linux、Windows、OS X等。
  • 多語言支持; C、C++、Java、.NET、Python等30多種開發語言。
  • 可單獨部署或集成到應用中使用;
  • 可作爲Socket通信庫使用。

與RabbitMQ相比,ZMQ並不像是一個傳統意義上的消息隊列服務器,事實上,它也根本不是一個服務器,更像一個底層的網絡通訊庫,在Socket API之上做了一層封裝,將網絡通訊、進程通訊和線程通訊抽象爲統一的API接口。支持“Request-Reply “,”Publisher-Subscriber“,”Parallel Pipeline”三種基本模型和擴展模型。

ZeroMQ高性能設計要點:

  1. 無鎖的隊列模型
    對於跨線程間的交互(用戶端和session)之間的數據交換通道pipe,採用無鎖的隊列算法CAS;在pipe兩端註冊有異步事件,在讀或者寫消息到pipe的時,會自動觸發讀寫事件。
  2. 批量處理的算法
    對於傳統的消息處理,每個消息在發送和接收的時候,都需要系統的調用,這樣對於大量的消息,系統的開銷比較大,zeroMQ對於批量的消息,進行了適應性的優化,可以批量的接收和發送消息。
  3. 多核下的線程綁定,無須CPU切換
    區別於傳統的多線程併發模式,信號量或者臨界區, zeroMQ充分利用多核的優勢,每個核綁定運行一個工作者線程,避免多線程之間的CPU切換開銷。


常見主流消息中間件的比較

綜上所述:

一般的業務系統要引入 MQ,最早大家都用 ActiveMQ,但是現在確實大家用的不多了,沒經過大規模吞吐量場景的驗證,社區也不是很活躍,所以我個人不推薦用這個了;現在很多企業開始嘗試用 RabbitMQ,但是確實 erlang 語言阻止了大量的 Java 工程師去深入研究和掌控它,對公司而言,幾乎處於不可控的狀態,但是確實人家是開源的,比較穩定的支持,活躍度也高;不過現在也有很多的公司,會去用 RocketMQ,確實很不錯(阿里出品),但社區可能有突然黃掉的風險,對自己公司技術實力有絕對自信的,推薦用 RocketMQ,否則回去老老實實用 RabbitMQ 吧,人家有活躍的開源社區,絕對不會黃。

(1)中小型軟件公司,技術實力較爲一般,技術挑戰不是特別高,建議選RabbitMQ.一方面,erlang語言天生具備高併發的特性,而且他的管理界面用起來十分方便。正所謂,成也蕭何,敗也蕭何!他的弊端也在這裏,雖然RabbitMQ是開源的,然而國內有幾個能定製化開發erlang的程序員呢?所幸,RabbitMQ的社區十分活躍,可以解決開發過程中遇到的bug,這點對於中小型公司來說十分重要。不考慮rocketmq和kafka的原因是,一方面中小型軟件公司不如互聯網公司,數據量沒那麼大,選消息中間件,應首選功能比較完備的,所以kafka排除。不考慮rocketmq的原因是,rocketmq是阿里出品,如果阿里放棄維護rocketmq,中小型公司一般抽不出人來進行rocketmq的定製化開發,因此不推薦。
(2)大型軟件公司,根據具體使用在rocketMq和kafka之間二選一。一方面,大型軟件公司,具備足夠的資金搭建分佈式環境,也具備足夠大的數據量。針對rocketMQ,大型軟件公司也可以抽出人手對rocketMQ進行定製化開發,畢竟國內有能力改JAVA源碼的人,還是相當多的。至於kafka,根據業務場景選擇,如果有日誌採集功能,肯定是首選kafka了。具體該選哪個,看使用場景。

(3)如果是大數據領域的實時計算、日誌採集等場景,用 Kafka 是業內標準的,絕對沒問題,社區活躍度很高,絕對不會黃,何況幾乎是全世界這個領域的事實性規範。

  • 9.消息隊列相關技術要點

9.1如何保證消息隊列是高可用的?

回答:這問題,其實要對消息隊列的集羣模式要有深刻了解,纔好回答。


以rcoketMQ爲例,

RabbitMQ 是比較有代表性的,因爲是基於主從(非分佈式)做高可用性的。

RabbitMQ 有三種模式:單機模式、普通集羣模式、鏡像集羣模式。

(1)單機模式:就是 Demo 級別的,一般就是你本地啓動了玩玩兒的?,沒人生產用單機模式。

(2)普通集羣模式(無高可用性):意思就是在多臺機器上啓動多個 RabbitMQ 實例,每個機器啓動一個。你創建的 queue,只會放在一個 RabbitMQ 實例上,但是每個實例都同步 queue 的元數據(元數據可以認爲是 queue 的一些配置信息,通過元數據,可以找到 queue 所在實例)。你消費的時候,實際上如果連接到了另外一個實例,那麼那個實例會從 queue 所在實例上拉取數據過來。

rcoketMQ多master多slave模式部署架構圖
普通集羣模式

這種方式確實很麻煩,也不怎麼好,沒做到所謂的分佈式,就是個普通集羣。因爲這導致你要麼消費者每次隨機連接一個實例然後拉取數據,要麼固定連接那個 queue 所在實例消費數據,前者有數據拉取的開銷,後者導致單實例性能瓶頸;而且如果那個放 queue 的實例宕機了,就會導致其他實例無法再次從那個queue實例拉取數據,如果你開啓了消息持久化,讓 RabbitMQ 落地存儲消息的話,消息不一定會丟,但是這得等這個實例恢復了,然後纔可以繼續從這個 queue 拉取數據;所以這就沒有什麼所謂的高可用性,這方案主要是提高吞吐量的,就是說讓集羣中多個節點來服務某個 queue 的讀寫操作。

其實我們第一眼看到這個圖,可能會覺得它和kafka好像,只是NameServer集羣,在kafka中是用zookeeper代替,都是用來保存和發現master和slave用的。通信過程如下:

Producer 與 NameServer集羣中的其中一個節點(隨機選擇)建立長連接,定期從 NameServer 獲取 Topic 路由信息,並向提供 Topic 服務的 Broker Master 建立長連接,且定時向 Broker 發送心跳。Producer 只能將消息發送到 Broker master,但是 Consumer 則不一樣,它同時和提供 Topic 服務的 Master 和 Slave建立長連接,既可以從 Broker Master 訂閱消息,也可以從 Broker Slave 訂閱消息。

那麼kafka呢,爲了對比說明直接上kafka的拓補架構圖

kafka的拓補架構圖

如上圖所示,一個典型的Kafka集羣中包含若干Producer(可以是web前端產生的Page View,或者是服務器日誌,系統CPU、Memory等),若干broker(Kafka支持水平擴展,一般broker數量越多,集羣吞吐率越高),若干Consumer Group,以及一個Zookeeper集羣。Kafka通過Zookeeper管理集羣配置,選舉leader,以及在Consumer Group發生變化時進行rebalance。Producer使用push模式將消息發佈到broker,Consumer使用pull模式從broker訂閱並消費消息。

(3)鏡像集羣模式(高可用性):

鏡像集羣模式

這種模式,纔是所謂的 RabbitMQ 的高可用模式。跟普通集羣模式不一樣的是,在鏡像集羣模式下,你創建的 queue,無論是元數據還是 queue 裏的消息都會存在於多個實例上,就是說,每個 RabbitMQ 節點都有這個 queue 的一個完整鏡像,包含 queue 的全部數據的意思。然後每次你寫消息到 queue 的時候,都會自動把消息同步到多個實例的 queue 上。

這樣的話,好處在於,你任何一個機器宕機了,其它機器(節點)依然可以繼續使用,因爲其他機器也包含了這個 queue 的完整數據,別的 consumer 都可以到其它節點上去消費數據;壞處在於,第一,這個性能開銷太大了,消息需要同步到所有機器上,導致網絡帶寬壓力和消耗很重!第二,這不是分佈式的,就沒有擴展性可言了,如果某個 queue 負載很重,你加機器,新增的機器也包含了這個 queue 的所有數據,並沒有辦法線性擴展你的 queue。你想,如果這個 queue 的數據量很大,大到這個機器上的容量無法容納了,此時該怎麼辦呢?

開啓這個鏡像集羣模式:其實很簡單,RabbitMQ 有很好的管理控制檯,就是在後臺新增一個策略,這個策略是鏡像集羣模式的策略,指定的時候是可以要求數據同步到所有節點的,也可以要求同步到指定數量的節點,然後你再次創建 queue 的時候,應用這個策略,就會自動將數據同步到其他的節點上去了。

實際上rabbitmq之類的,並不是分佈式消息隊列,他就是傳統的消息隊列,只不過提供了一些集羣、HA的機制而已,因爲無論怎麼樣,rabbitmq一個queue的數據都是放在一個節點裏的,鏡像集羣下,也是每個節點都放這個queue的完整數據.


以Kafka爲例,

kafka一個最基本的架構認識:由多個broker組成,每個broker是一個節點。你創建一個topic,這個topic可以劃分爲多個partition,每個partition可以存在於不同的broker上,每個partition就放一部分數據,這就是天然的分佈式消息隊列。就是說一個topic的數據,是分散放在多個機器上的,每個機器就放一部分數據。

kafka的高可用性

kafka 0.8以前,是沒有HA機制的,就是任何一個broker宕機了,那個broker上的partition就廢了,沒法寫也沒法讀,沒有什麼高可用性可言。  

kafka 0.8以後,才提供了HA機制,就是replica副本機制。每個partition的數據都會同步到其他機器上,形成自己的多個replica副本。然後所有replica會選舉一個leader出來,那麼生產和消費都跟這個leader打交道,然後其他replica就是follower。寫的時候,leader會負責把數據同步到所有follower上去,讀的時候就直接讀leader上數據即可。只能讀寫leader?很簡單,要是你可以隨意讀寫每個follower,那麼就要care數據一致性的問題,系統複雜度太高,很容易出問題。kafka會均勻的將一個partition的所有replica分佈在不同的機器上,這樣纔可以提高容錯性。  

  這樣一來就有所謂的高可用性了,因爲如果某個broker宕機了,沒事兒,那個broker上面的partition在其他機器上都有副本的,如果這上面有某個partition的leader,那麼此時會重新選舉一個新的leader出來,大家繼續讀寫那個新的leader即可,這就有所謂的高可用性了。寫數據的時候,生產者就寫leader,然後leader將數據落地寫本地磁盤,接着其他follower自己主動從leader來pull數據。一旦所有follower同步好數據了,就會發送ack給leader,leader收到所有follower的ack之後,就會返回寫成功的消息給生產者。(當然,這只是其中一種模式,還可以適當調整這個行爲)消費的時候,只會從leader去讀,但是隻有一個消息已經被所有follower都同步成功返回ack的情況下,這個消息纔會被消費者讀到。


redis 實現高併發主要依靠主從架構,一主多從,一般來說,單主用來寫入數據,單機幾萬 QPS,多從用來查詢數據,多個從實例可以提供每秒 10w 的 QPS。如果想要在實現高併發的同時,容納大量的數據,那麼就需要 redis 集羣,使用 redis 集羣之後,可以提供每秒幾十萬的讀寫併發。redis 高可用,如果是做主從架構部署,那麼加上哨兵就可以了,就可以實現,任何一個實例宕機,可以進行主備切換。(redis具體知識,後面專門整理一篇文檔)


9.2如何保證消息不被重複消費?

這個問題其實換一種問法就是,如何保證消息隊列的冪等性?這個問題可以認爲是消息隊列領域的基本問題。

先來說一下爲什麼會造成重複消費?

其實無論是那種消息隊列,造成重複消費原因其實都是類似的。正常情況下,消費者在消費消息時候,消費完畢後,會發送一個確認信息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發送的確認信息形式不同,例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標誌,kafka實際上有個offset的概念,簡單說一下,就是每個消息寫進去,都有一個offset,代表他的序號,然後consumer消費了數據之後,每隔一段時間,會把自己消費過的消息的offset提交一下,代表我已經消費過了,下次我要是重啓啥的,你就讓我繼續從上次消費到的offset來繼續消費吧。

但是凡事總有意外,比如我們之前生產經常遇到的,就是你有時候重啓系統,看你怎麼重啓了,如果碰到點着急的,直接kill進程了,再重啓。這會導致consumer有些消息處理了,但是沒來得及提交offset,尷尬了。重啓之後,少數消息會再次消費一次。

其實重複消費不可怕,可怕的是你沒考慮到重複消費之後,怎麼保證冪等性。

給你舉個例子吧。假設你有個系統,消費一條就往數據庫裏插入一條,要是你一個消息重複兩次,你不就插入了兩條,這數據不就錯了?但是你要是消費到第二次的時候,自己判斷一下已經消費過了,直接扔了,不就保留了一條數據?

一條數據重複出現兩次,數據庫裏就只有一條數據,這就保證了系統的冪等性

冪等性,我通俗點說,就一個數據,或者一個請求,給你重複來多次,你得確保對應的數據是不會改變的,不能出錯。

kafka如何避免消息被重複消費

那造成重複消費的原因?就是因爲網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將該消息分發給其他的消費者。

如何解決?這個問題其實還是得結合業務來思考,針對業務場景來答分以下幾點

(1)比如你拿到這個消息是用來做數據庫的insert操作。

那就容易了,你先根據主鍵查一下,如果這數據都有了,你就別插入了,update一下好吧;如果沒有你就直接給這個消息做一個唯一主鍵,那麼就算出現重複消費的情況,就會導致主鍵衝突,避免數據庫出現髒數據。
(2)再比如,你拿到這個消息做redis的set的操作。

那就容易了,直接不用解決,因爲你無論set幾次結果都是一樣的,set操作本來就是天然冪等操作。
(3)如果上面兩種情況還不行,上大招。

那做的稍微複雜一點,準備一個第三方介質,來做消費記錄,你需要讓生產者發送每條數據的時候,裏面加一個全局唯一的id,類似訂單id之類的東西,然後你這裏消費到了之後,先根據這個id去比如redis裏查一下,之前消費過嗎?如果沒有消費過,你就處理;如果消費過了,那你就別處理了,保證別重複處理相同的消息即可。以redis爲例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis。那消費者開始消費前,先去redis中查詢有沒消費記錄即可。

還有比如基於數據庫的唯一鍵來保證重複數據不會重複插入多條,我們之前線上系統就有這個問題,就是拿到數據的時候,每次重啓可能會有重複,因爲kafka消費者還沒來得及提交offset,重複數據拿到了以後我們插入的時候,因爲有唯一鍵約束了,所以重複數據只會插入報錯,不會導致數據庫中出現髒數據

如何保證MQ的消費是冪等性的,需要結合具體的業務來看

 

9.3如何保證消費的可靠性傳輸?

其實回答這個問題,我們只需要從MQ的三個角度來分析:生產者弄丟數據、消息隊列弄丟數據、消費者弄丟數據

RabbitMQ

(1)生產者丟數據

生產者將數據發送到rabbitmq的時候,可能數據就在半路給搞丟了,因爲網絡啥的問題,都有可能。

此時可以選擇用rabbitmq提供的事務功能,就是生產者發送數據之前開啓rabbitmq事務(channel.txSelect),然後發送消息,如果消息沒有成功被rabbitmq接收到,那麼生產者會收到異常報錯,此時就可以回滾事務(channel.txRollback),然後重試發送消息;如果收到了消息,那麼可以提交事務(channel.txCommit)。但是問題是,rabbitmq事務機制一搞,基本的吞吐量會降下來,因爲太耗性能。

所以一般來說,如果你要確保說寫rabbitmq的消息別丟,可以開啓confirm模式,在生產者那裏設置開啓confirm模式之後,你每次寫的消息都會分配一個唯一的id,然後如果寫入了rabbitmq中,rabbitmq會給你回傳一個ack消息,告訴你說這個消息ok了。如果rabbitmq沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息id的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。

事務機制和cnofirm機制最大的不同在於,事務機制是同步的,你提交一個事務之後會阻塞在那兒,但是confirm機制是異步的,你發送個消息之後就可以發送下一個消息,然後那個消息rabbitmq接收了之後會異步回調你一個接口通知你這個消息接收到了。

所以一般在生產者這塊避免數據丟失,都是用confirm機制的。因此,按照大多數大佬的經驗,生產上用confirm模式的居多。一旦channel進入confirm模式,所有在該信道上面發佈的消息都將會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之後,rabbitMQ就會發送一個Ack給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了.如果rabiitMQ沒能處理該消息,則會發送一個Nack消息給你,你可以進行重試操作。處理Ack和Nack的代碼如下所示

channel.addConfirmListener(new ConfirmListener() {  
      @Override  
      public void handleNack(long deliveryTag, boolean multiple) throws IOException { 
          System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
      }  
       @Override  
       public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
          System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
      }  
  });  

(2)rabbitmq丟數據

rabbitmq自己弄丟了數據

就是rabbitmq自己弄丟了數據,這個你必須開啓rabbitmq的持久化磁盤的配置,這個持久化配置可以和confirm機制配合使用,你可以在消息持久化磁盤後,纔會給生產者發送一個Ack信號。這樣哪怕是rabbitmq自己掛了,數據丟了,生產者收不到ack,你也是可以自己重發的。這樣恢復之後會自動讀取之前存儲的數據,一般數據不會丟。除非極其罕見的是,rabbitmq還沒持久化,自己就掛了,可能導致少量數據會丟失的,但是這個概率較小。

設置持久化有兩個步驟,第一個是創建queue的時候將queue的持久化標識durable設置爲true,則代表是一個持久的隊列,這樣就可以保證rabbitmq持久化queue的元數據,但是不會持久化queue裏的數據;第二個是發送消息的時候將消息的deliveryMode設置爲2,就是將消息設置爲持久化的,此時rabbitmq就會將消息持久化到磁盤上去。必須要同時設置這兩個持久化才行,rabbitmq哪怕是掛了,再次重啓,也會從磁盤上重啓恢復queue,恢復這個queue裏的數據。

總的來說:rabbitmq如果丟失了數據,主要是因爲你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啓了,那麼就尷尬了,rabbitmq認爲你都消費了,這數據就丟了。

(3)消費者丟數據
消費者丟數據一般是因爲採用了自動確認消息模式。這種模式下,消費者會自動確認收到信息。這時rahbitMQ會立即將消息刪除,這種情況下如果消費者出現異常而沒能處理該消息,就會丟失該消息。
至於解決方案,採用手動確認消息即可。這個時候得用rabbitmq提供的ack機制,簡單來說,就是你關閉rabbitmq自動ack,可以通過一個api來調用就行,然後每次你自己代碼裏確保處理完的時候,再程序裏ack一把。這樣的話,如果你還沒處理完,不就沒有ack?那rabbitmq就認爲你還沒處理完,這個時候rabbitmq會把這個消費分配給別的consumer去處理,消息是不會丟的。


kafka

kafka Replication的數據流向圖

Producer在發佈消息到某個Partition時,先通過ZooKeeper找到該Partition的Leader,然後無論該Topic的Replication Factor爲多少(也即該Partition有多少個Replica),Producer只將該消息發送到該Partition的Leader。Leader會將該消息寫入其本地Log。每個Follower都從Leader中pull數據。
針對上述情況,得出如下分析
(1)生產者丟數據
在kafka生產中,基本都有一個leader和多個follwer。follwer會去同步leader的信息。因此,爲了避免生產者丟數據,做如下兩點配置

  1. 第一個配置要在producer端設置acks=all。這個配置保證了,follwer同步完成後,才認爲消息發送成功。
  2. 在producer端設置retries=MAX,一旦寫入失敗,這無限重試

(2)消息隊列丟數據
針對消息隊列丟數據的情況,這塊比較常見的一個場景,就是kafka某個broker宕機,然後重新選舉partiton的leader時,數據還沒同步,leader就掛了,這時zookpeer會將其他的follwer切換爲leader,那數據就丟失了。所以此時針對這種情況,一般是要求起碼設置如下4個參數:

①給這個topic設置replication.factor參數:這個值必須大於1,即要求每個partition必須有至少2個副本

②在kafka服務端設置min.insync.replicas參數:這個值必須大於1,這個是要求一個leader至少感知到有至少一個follower還跟自己保持聯繫,沒掉隊,這樣才能確保leader掛了還有一個follower吧

③在producer端設置acks=all:這個是要求每條數據,必須是寫入所有replica之後,才能認爲是寫成功了

④在producer端設置retries=MAX(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗,就無限重試,卡在這裏了

我們生產環境一般都按照上述要求配置的,這樣配置之後,至少在kafka broker端就可以保證在leader所在broker發生故障,進行leader切換時,數據不會丟失

(3)消費者丟數據

唯一可能導致消費者弄丟數據的情況,就是說,你那個消費到了這個消息,然後消費者那邊自動提交了offset,讓kafka以爲你已經消費好了這個消息,其實你剛準備處理這個消息,你還沒處理,你自己就掛了,此時這條消息就丟咯。

再強調一次offset是幹嘛的,offset:指的是kafka的topic中的每個消費組消費的下標。簡單的來說就是一條消息對應一個offset下標,每次消費數據的時候如果提交offset,那麼下次消費就會從提交的offset加一那裏開始消費。
比如一個topic中有100條數據,我消費了50條並且提交了,那麼此時的kafka服務端記錄提交的offset就是49(offset從0開始),那麼下次消費的時候offset就從50開始消費。
解決方案也很簡單,改成手動提交即可。大家都知道kafka會自動提交offset,那麼只要關閉自動提交offset,在處理完之後自己手動提交offset,就可以保證數據不會丟。但是此時確實還是會重複消費,比如你剛處理完,還沒提交offset,結果自己掛了,此時肯定會重複消費一次,自己保證冪等性就好了。然而生產環境還可能碰到的這樣的問題,就是說我們的kafka消費者消費到了數據之後是寫到一個內存的queue裏先緩衝一下,結果有的時候,你剛把消息寫入內存queue,然後消費者會自動提交offset。然後此時我們重啓了系統,就會導致內存queue裏還沒來得及處理的數據就丟失了。

kafka消費端弄丟了數據

9.4如何保證消息的順序性?

針對這個問題,我們通過某種算法,將需要保持先後順序的消息放到同一個消息隊列中,然後只用一個消費者去消費該隊列。

1)rabbitmq保證數據的順序性

如果存在多個消費者,那麼就讓每個消費者對應一個queue,然後把要發送 的數據全都放到一個queue,這樣就能保證所有的數據只到達一個消費者從而保證每個數據到達數據庫都是順序的。

rabbitmq:拆分多個queue,每個queue一個consumer,就是多一些queue而已,確實是麻煩點;或者就一個queue但是對應一個consumer,然後這個consumer內部用內存隊列做排隊,然後分發給底層不同的worker來處理

rabbitmq保證數據的順序性

2)kafka保證數據的順序性

 kafka 寫入partion時指定一個key,列如訂單id,那麼消費者從partion中取出數據的時候肯定是有序的,當開啓多個線程的時候可能導致數據不一致,這時候就需要內存隊列,將相同的hash過的數據放在一個內存隊列裏,這樣就能保證一條線程對應一個內存隊列的數據寫入數據庫的時候順序性的,從而可以開啓多條線程對應多個內存隊列

kafka:一個topic,一個partition,一個consumer,內部單線程消費,寫N個內存queue,然後N個線程分別消費一個內存queue即可kafka保證數據的順序性

kafka保證數據的順序性

https://blog.csdn.net/u012431703/article/details/95939288

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