週末無聊刷着手機,某寶網APP忽然蹦出來一條消息「爲了回饋老客戶,女友買一送一,活動僅限今天!」。買一送一還有這種好事,那我可不能錯過!忍不住立馬點了去。因而選了兩個最新款,下單、支付一鼓作氣!知足的躺在牀上,想着立刻有女友了,居然幸福的失眠了…… 次日正常上着班,忽然接到快遞小哥的電話: 小哥:「你是xx嗎?你的女友到了,我如今在你樓下,你來拿一下吧!」。 我:「這……我在上班呢,能夠晚上送過來嗎?「。 小哥:「晚上可不行哦,晚上我也下班了呢!」。 因而兩我的僵持了好久…… 最後小哥說,要不我幫你放到樓下小芳便利店吧,你晚上下班了過來拿,尷尬的局面這才得以緩解!安全
回到正題,若是沒有小芳便利店,那快遞小哥和個人交互圖就應該以下:服務器
會出現什麼狀況呢? 一、爲了這個女友,我請假回去拿(老闆不批)。 二、小哥一直在你樓下等(小哥還有其餘的快遞要送)。 三、週末再送(顯然等不及)。 四、這個女友我不要了(絕對不可能)!微信
小芳便利店出現後,交互圖就應以下:架構
在上面例子中,「快遞小哥」和「買女友的我」就是須要交互的兩個系統,小芳便利店就是咱們本文要講的-「消息中間件」。總結下來小芳便利店(消息中間件)出現後有以下好處:併發
一、 解耦 快遞小哥手上有不少快遞須要送,他每次都須要先電話一一確認收貨人是否有空、哪一個時間段有空,而後再肯定好送貨的方案。這樣徹底依賴收貨人了!若是快遞一多,快遞小哥估計的忙瘋了……若是有了便利店,快遞小哥只須要將同一個小區的快遞放在同一個便利店,而後通知收貨人來取貨就能夠了,這時候快遞小哥和收貨人就實現瞭解耦!負載均衡
二、 異步 快遞小哥打電話給我後須要一直在你樓下等着,直到我拿走你的快遞他才能去送其餘人的。快遞小哥將快遞放在小芳便利店後,又能夠幹其餘的活兒去了,不須要等待你到來而一直處於等待狀態。提升了工做的效率。異步
三、 削峯 假設雙十一我買了不一樣店裏的各類商品,而恰巧這些店發貨的快遞都不同,有中通、圓通、申通、各類通等……更巧的是他們都同時到貨了!中通的小哥打來電話叫我去北門取快遞、圓通小哥叫我去南門、申通小哥叫我去東門。我一時手忙腳亂……分佈式
咱們能看到在系統須要交互的場景中,使用消息隊列中間件真的是好處多多,基於這種思路,就有了豐巢、菜鳥驛站等比小芳便利店更專業的「中間件」了。 最後,上面的故事純屬虛構……高併發
經過上面的例子咱們引出了消息中間件,而且介紹了消息隊列出現後的好處,這裏就須要介紹消息隊列通訊的兩種模式了:性能
如上圖所示,點對點模式一般是基於拉取或者輪詢的消息傳送模型,這個模型的特色是發送到隊列的消息被一個且只有一個消費者進行處理。生產者將消息放入消息隊列後,由消費者主動的去拉取消息進行消費。點對點模型的的優勢是消費者拉取消息的頻率能夠由本身控制。可是消息隊列是否有消息須要消費,在消費者端沒法感知,因此在消費者端須要額外的線程去監控。
如上圖所示,發佈訂閱模式是一個基於消息送的消息傳送模型,改模型能夠有多種不一樣的訂閱者。生產者將消息放入消息隊列後,隊列會將消息推送給訂閱過該類消息的消費者(相似微信公衆號)。因爲是消費者被動接收推送,因此無需感知消息隊列是否有待消費的消息!可是consumer一、consumer二、consumer3因爲機器性能不同,因此處理消息的能力也會不同,但消息隊列卻沒法感知消費者消費的速度!因此推送的速度成了發佈訂閱模模式的一個問題!假設三個消費者處理速度分別是8M/s、5M/s、2M/s,若是隊列推送的速度爲5M/s,則consumer3沒法承受!若是隊列推送的速度爲2M/s,則consumer一、consumer2會出現資源的極大浪費!
上面簡單的介紹了爲何須要消息隊列以及消息隊列通訊的兩種模式,接下來就到了咱們本文的主角——kafka閃亮登場的時候了!Kafka是一種高吞吐量的分佈式發佈訂閱消息系統,它能夠處理消費者規模的網站中的全部動做流數據,具備高性能、持久化、多副本備份、橫向擴展能力……… 一些基本的介紹這裏就不展開了,網上有太多關於這些的介紹了,讀者能夠自行百度一下!
話很少說,先看圖,經過這張圖咱們來捋一捋相關的概念及之間的關係:
若是看到這張圖你很懵逼,木有關係!咱們先來分析相關概念 Producer:Producer即生產者,消息的產生者,是消息的入口。
kafka cluster:
Broker:Broker是kafka實例,每一個服務器上有一個或多個kafka的實例,咱們姑且認爲每一個broker對應一臺服務器。每一個kafka集羣內的broker都有一個不重複的編號,如圖中的broker-0、broker-1等…… Topic:消息的主題,能夠理解爲消息的分類,kafka的數據就保存在topic。在每一個broker上均可以建立多個topic。 Partition:Topic的分區,每一個topic能夠有多個分區,分區的做用是作負載,提升kafka的吞吐量。同一個topic在不一樣的分區的數據是不重複的,partition的表現形式就是一個一個的文件夾! Replication:每個分區都有多個副本,副本的做用是作備胎。當主分區(Leader)故障的時候會選擇一個備胎(Follower)上位,成爲Leader。在kafka中默認副本的最大數量是10個,且副本的數量不能大於Broker的數量,follower和leader絕對是在不一樣的機器,同一機器對同一個分區也只可能存放一個副本(包括本身)。 Message:每一條發送的消息主體。 Consumer:消費者,即消息的消費方,是消息的出口。 Consumer Group:咱們能夠將多個消費組組成一個消費者組,在kafka的設計中同一個分區的數據只能被消費者組中的某一個消費者消費。同一個消費者組的消費者能夠消費同一個topic的不一樣分區的數據,這也是爲了提升kafka的吞吐量! Zookeeper:kafka集羣依賴zookeeper來保存集羣的的元信息,來保證系統的可用性。
上面介紹了kafka的基礎架構及基本概念,不知道你們看完有沒有對kafka有個大體印象,若是對還比較懵也不要緊!咱們接下來再結合上面的結構圖分析kafka的工做流程,最後再回來整個梳理一遍我相信你會更有收穫!
咱們看上面的架構圖中,producer就是生產者,是數據的入口。注意看圖中的紅色箭頭,Producer在寫入數據的時候永遠的找leader,不會直接將數據寫入follower!那leader怎麼找呢?寫入的流程又是什麼樣的呢?咱們看下圖:
發送的流程就在圖中已經說明了,就不單獨在文字列出來了!須要注意的一點是,消息寫入leader後,follower是主動的去leader進行同步的!producer採用push模式將數據發佈到broker,每條消息追加到分區中,順序寫入磁盤,因此保證同一分區內的數據是有序的!寫入示意圖以下:
上面說到數據會寫入到不一樣的分區,那kafka爲何要作分區呢?相信你們應該也能猜到,分區的主要目的是: 一、 方便擴展。由於一個topic能夠有多個partition,因此咱們能夠經過擴展機器去輕鬆的應對日益增加的數據量。 二、 提升併發。以partition爲讀寫單位,能夠多個消費者同時消費數據,提升了消息的處理效率。
熟悉負載均衡的朋友應該知道,當咱們向某個服務器發送請求的時候,服務端可能會對請求作一個負載,將流量分發到不一樣的服務器,那在kafka中,若是某個topic有多個partition,producer又怎麼知道該將數據發往哪一個partition呢?kafka中有幾個原則: 一、 partition在寫入的時候能夠指定須要寫入的partition,若是有指定,則寫入對應的partition。 二、 若是沒有指定partition,可是設置了數據的key,則會根據key的值hash出一個partition。 三、 若是既沒指定partition,又沒有設置key,則會輪詢選出一個partition。
保證消息不丟失是一個消息隊列中間件的基本保證,那producer在向kafka寫入消息的時候,怎麼保證消息不丟失呢?其實上面的寫入流程圖中有描述出來,那就是經過ACK應答機制!在生產者向隊列寫入數據的時候能夠設置參數來肯定是否確認kafka接收到數據,這個參數可設置的值爲0、1、all。 0表明producer往集羣發送數據不須要等到集羣的返回,不確保消息發送成功。安全性最低可是效率最高。 1表明producer往集羣發送數據只要leader應答就能夠發送下一條,只確保leader發送成功。 all表明producer往集羣發送數據須要全部的follower都完成從leader的同步纔會發送下一條,確保leader發送成功和全部的副本都完成備份。安全性最高,可是效率最低。
最後要注意的是,若是往不存在的topic寫數據,能不能寫入成功呢?kafka會自動建立topic,分區和副本的數量根據默認配置都是1。
Producer將數據寫入kafka後,集羣就須要對數據進行保存了!kafka將數據保存在磁盤,可能在咱們的通常的認知裏,寫入磁盤是比較耗時的操做,不適合這種高併發的組件。Kafka初始會單獨開闢一塊磁盤空間,順序寫入數據(效率比隨機寫入高)。
Partition 結構 前面說過了每一個topic均可以分爲一個或多個partition,若是你以爲topic比較抽象,那partition就是比較具體的東西了!Partition在服務器上的表現形式就是一個一個的文件夾,每一個partition的文件夾下面會有多組segment文件,每組segment文件又包含.index文件、.log文件、.timeindex文件(早期版本中沒有)三個文件, log文件就實際是存儲message的地方,而index和timeindex文件爲索引文件,用於檢索消息。
如上圖,這個partition有三組segment文件,每一個log文件的大小是同樣的,可是存儲的message數量是不必定相等的(每條的message大小不一致)。文件的命名是以該segment最小offset來命名的,如000.index存儲offset爲0~368795的消息,kafka就是利用分段+索引的方式來解決查找效率的問題。
Message結構 上面說到log文件就實際是存儲message的地方,咱們在producer往kafka寫入的也是一條一條的message,那存儲在log中的message是什麼樣子的呢?消息主要包含消息體、消息大小、offset、壓縮類型……等等!咱們重點須要知道的是下面三個: 一、 offset:offset是一個佔8byte的有序id號,它能夠惟一肯定每條消息在parition內的位置! 二、 消息大小:消息大小佔用4byte,用於描述消息的大小。 三、 消息體:消息體存放的是實際的消息數據(被壓縮過),佔用的空間根據具體的消息而不同。
存儲策略 不管消息是否被消費,kafka都會保存全部的消息。那對於舊數據有什麼刪除策略呢? 一、 基於時間,默認配置是168小時(7天)。 二、 基於大小,默認配置是1073741824。 須要注意的是,kafka讀取特定消息的時間複雜度是O(1),因此這裏刪除過時的文件並不會提升kafka的性能!
消息存儲在log文件後,消費者就能夠進行消費了。在講消息隊列通訊的兩種模式的時候講到過點對點模式和發佈訂閱模式。Kafka採用的是點對點的模式,消費者主動的去kafka集羣拉取消息,與producer相同的是,消費者在拉取消息的時候也是找leader去拉取。
多個消費者能夠組成一個消費者組(consumer group),每一個消費者組都有一個組id!同一個消費組者的消費者能夠消費同一topic下不一樣分區的數據,可是不會組內多個消費者消費同一分區的數據!!!是否是有點繞。咱們看下圖:
圖示是消費者組內的消費者小於partition數量的狀況,因此會出現某個消費者消費多個partition數據的狀況,消費的速度也就不及只處理一個partition的消費者的處理速度!若是是消費者組的消費者多於partition的數量,那會不會出現多個消費者消費同一個partition的數據呢?上面已經提到過不會出現這種狀況!多出來的消費者不消費任何partition的數據。因此在實際的應用中,建議消費者組的consumer的數量與partition的數量一致! 在保存數據的小節裏面,咱們聊到了partition劃分爲多組segment,每一個segment又包含.log、.index、.timeindex文件,存放的每條message包含offset、消息大小、消息體……咱們屢次提到segment和offset,查找消息的時候是怎麼利用segment+offset配合查找的呢?假如如今須要查找一個offset爲368801的message是什麼樣的過程呢?咱們先看看下面的圖:
一、 先找到offset的368801message所在的segment文件(利用二分法查找),這裏找到的就是在第二個segment文件。 二、 打開找到的segment中的.index文件(也就是368796.index文件,該文件起始偏移量爲368796+1,咱們要查找的offset爲368801的message在該index內的偏移量爲368796+5=368801,因此這裏要查找的相對offset爲5)。因爲該文件採用的是稀疏索引的方式存儲着相對offset及對應message物理偏移量的關係,因此直接找相對offset爲5的索引找不到,這裏一樣利用二分法查找相對offset小於或者等於指定的相對offset的索引條目中最大的那個相對offset,因此找到的是相對offset爲4的這個索引。 三、 根據找到的相對offset爲4的索引肯定message存儲的物理偏移位置爲256。打開數據文件,從位置爲256的那個地方開始順序掃描直到找到offset爲368801的那條Message。
這套機制是創建在offset爲有序的基礎上,利用segment+有序offset+稀疏索引+二分查找+順序查找等多種手段來高效的查找數據!至此,消費者就能拿到須要處理的數據進行處理了。那每一個消費者又是怎麼記錄本身消費的位置呢?在早期的版本中,消費者將消費到的offset維護zookeeper中,consumer每間隔一段時間上報一次,這裏容易致使重複消費,且性能很差!在新的版本中消費者消費到的offset已經直接維護在kafk集羣的__consumer_offsets這個topic中!