本文已同步至我的博客 liaosi'blog-kafka(一)—基本概念
Kafka是用scala語言編寫,最初由Linkedin公司開發,後貢獻給了Apache基金會併成爲頂級開源項目。是一個分佈式、支持分區的(partition)、多副本的(replication),基於zookeeper協調的分佈式消息系統,它的最大的特性就是能夠實時的處理大量數據以知足各類需求場景:好比基於hadoop的批處理系統、低延遲的實時系統、storm/Spark流式處理引擎,web/nginx日誌、訪問日誌,消息服務等等。mysql
Kafka是一個類JMS消息隊列,結合了JMS中的兩種模式,能夠有多個消費者主動拉取數據。雖然它提供了相似於JMS的特性,可是在設計實現上徹底不一樣,此外它並非JMS規範的實現,在JMS中只有點對點模式纔有消費者主動拉取數據。nginx
官方文檔:Kafka官網文檔
Kafka的運行架構以下圖,各組件之間經過TCP協議通訊:web
kafka 集羣由多個 kafka 實例組成,每一個實例 (server) 稱爲 broker ,在集羣中每一個broker都有一個惟一brokerid,不得重複。 不管是 kafka 集羣,仍是 producer 和 consumer 都依賴於 zookeeper 來保證系統可用性,爲集羣保存一些 meta (元數據)信息。算法
主題(topic)是一種分類或發佈的一系列記錄的名義上的名字。Kafka的主題始終是支持多用戶訂閱的;也就是說,一個主題能夠有零個,一個或多個消費者訂閱寫入的數據。
對於每個主題,Kafka集羣保持一個分區日誌文件,看下圖:sql
每一個分區都由一系列有序的、不可變的消息組成,這些消息被連續的追加到分區中。分區中的每一個消息都有一個連續的序列號叫作offset,用來在分區中惟一的標識這個消息。apache
在一個可配置的時間段內,Kafka集羣保留全部發布的消息,無論這些消息有沒有被消費。好比,若是消息的保存策略被設置爲2天,那麼在一個消息被髮布的兩天時間內,它都是能夠被消費的,以後它將被丟棄以釋放空間。Kafka的性能是和數據量無關的常量級的,因此保留太多的數據並非問題。緩存
實際上每一個consumer惟一須要維護的數據是消息在日誌中的位置,也就是offset
。這個offset有consumer來維護:通常狀況下隨着consumer不斷的讀取消息,這offset的值不斷線性增長,但其實consumer能夠在任何它想要的位置讀取記錄,好比它能夠將offset設置成爲一箇舊的值來重讀以前的消息。服務器
正由於以上的特色,使Kafka consumers很是的輕量級:它們能夠在不對集羣和其餘consumer形成影響的狀況下讀取消息。例如,你可使用命令行工具去"tail"任何主題的內容,而不會對其餘正在消費該消息的consumer形成影響。session
Producer將消息發佈到它指定的topic中,並負責決定發佈到哪一個分區。一般簡單的由負載均衡機制隨機選擇分區,也能夠根據一些更復雜的語義分區算法(好比基於記錄一些鍵值)來完成。使用的更多的是第二種。默認是defaultPartition Utils.abs(key.hashCode) % numPartitions架構
每一個 Consumer 進程都會劃歸到一個邏輯的Consumer Group中,邏輯的訂閱者是Consumer Group,同一個 Consumer Group 中的 Consumer 能夠在不一樣的程序中,也能夠在不一樣的機器上。因此一條message能夠被多個訂閱該 message 所在的topic的每個Consumer Group 所消費,也就好像是這條message被廣播到每一個Consumer Group同樣。而每一個Consumer Group中,相似於一個Queue(JMS中的Queue)的概念差很少,即topic中的一條message只會被Consumer Group中的一個Consumer消費。
上圖顯示,一個kafka cluster中的某個topic有4個分區(P0-P3)和2個consumer組。A組有2個consumer,B組有4個consumer。
其實上面所說的訂閱關係還不夠明確,其實topic中的partition被分配到某個consumer上,也就是某個consumer訂閱了某個partition。Consumer Group 訂閱的是topic,可是consumer訂閱的是partition,而不是message。因此在同一時間點上,訂閱到同一個partition的consumer必然屬於不一樣的Consumer Group。另外,partition分配的工做是在consumer leader中完成的。
當一個Consumer 進程掛掉 或者是卡住時,該Consumer所訂閱的partition會被從新分配到該group內的其它的Consumer上。當一個consumer加入到一個Consumer Group中時,一樣會從其它的Consumer中分配出一個或者多個partition 到這個新加入的Consumer。
當啓動一個Consumer時,會指定它要加入的group,使用的是配置項:group.id。
爲了維持Consumer 與 Consumer Group的關係,須要Consumer週期性的發送heartbeat到coordinator(協調者,在早期版本,以zookeeper做爲協調者。後期版本則以某個broker做爲協調者)。當Consumer因爲某種緣由不能發Heartbeat到coordinator時,而且時間超過session.timeout.ms時,就會認爲該consumer已退出,它所訂閱的partition會分配到同一group 內的其它的consumer上。而這個過程,被稱爲rebalance。
若是一個consumer 進程一直在週期性的發送heartbeat,可是它就是不消費消息,這種狀態稱爲livelock狀態。
Coordinator 協調者,協調consumer、broker。早期版本中Coordinator,使用zookeeper實現,可是這樣作,rebalance的負擔過重。爲了解決scalable的問題,再也不使用zookeeper,而是讓每一個broker來負責一些group的管理,這樣consumer就徹底再也不依賴zookeeper了。
從Consumer的實現來看,在執行poll或者是join group以前,都要保證已鏈接到Coordinator。鏈接到coordinator的過程是:
1)鏈接到最後一次鏈接的broker(若是是剛啓動的consumer,則要根據配置中的borker)。它會響應一個包含coordinator信息(host, port等)的response。
2)鏈接到coordinator。
Consumer Group 管理中,也是須要coordinator的參與。一個Consumer要join到一個group中,或者一個consumer退出時,都要進行rebalance。進行rebalance的流程是:
1)會給一個coordinator發起Join請求(請求中要包括本身的一些元數據,例如本身感興趣的topics)
2)Coordinator 根據這些consumer的join請求,選擇出一個leader,並通知給各個consumer。這裏的leader是consumer group 內的leader,是由某個consumer擔任,不要與partition的leader混淆。
3)Consumer leader 根據這些consumer的metadata,從新爲每一個consumer member從新分配partition。分配完畢經過coordinator把最新分配狀況同步給每一個consumer。
4)Consumer拿到最新的分配後,繼續工做。
傳統的隊列在服務器上保存有序的消息,若是多個consumers同時從這個服務器消費消息,服務器就會以消息存儲的順序向consumer分發消息。雖然服務器按順序發佈消息,可是消息是被異步的分發到各consumer上,因此當消息到達時可能已經失去了原來的順序,這意味着併發消費將致使順序錯亂。爲了不故障,這樣的消息系統一般使用「專用consumer」的概念,其實就是隻容許一個消費者消費消息,固然這就意味着失去了併發性。
在這方面Kafka作的更好,經過分區的概念,Kafka能夠在多個consumer組併發的狀況下提供較好的有序性和負載均衡。將每一個分區分只分發給一個consumer組,這樣一個分區就只被這個組的一個consumer消費,就能夠順序的消費這個分區的消息。由於有多個分區,依然能夠在多個consumer組之間進行負載均衡。注意consumer組的數量不能多於分區的數量,也就是有多少分區就容許多少併發消費。
Kafka只能保證一個分區以內消息的有序性,在不一樣的分區之間是不能夠的,這已經能夠知足大部分應用的需求。若是須要topic中全部消息的有序性,那就只能讓這個topic只有一個分區,固然也就只有一個consumer組消費它。
Producer將消息發佈到它指定的topic中,並負責決定發佈到哪一個分區。一般簡單的由負載均衡機制隨機選擇分區,但也能夠經過特定的分區函數選擇分區。使用的更多的是第二種。默認是defaultPartition Utils.abs(key.hashCode) % numPartitions
每一個分區(partition)在 Kafka 集羣的若干服務中都有副本,這樣這些持有副本的服務能夠共同處理數據和請求,副本數量是能夠配置的(replication-factor 1),副本使Kafka具有了容錯能力。
每個分區都由一個服務器做爲「leader」,零或若干服務器做爲「followers」,leader負責處理消息的讀和寫,與此同時,follower會被動的去複製leader上的數據。
若是leader發生故障,followers中的一臺則會自動成爲leader。每臺服務器能夠做爲一些分區的leader,同時也能夠做爲其餘分區的follower,這樣集羣就會據有較好的負載均衡。特別強調,和mysql中主從有區別,mysql作主從是爲了讀寫分離,在kafka中讀寫操做都是leader。
在Kafka partition中,每一個消息有一個惟一標識,即partition內的offset。每一個consumer group中的訂閱到某個partition的consumer在從partition中讀取數據時,是依次讀取的。
上圖中,Consumer A、B分屬於不用的Consumer Group。Consumer B讀取到offset =11,Consumer A讀取到offset=9 。這個值表示Consumer Group中的某個Consumer 在下次讀取該partition時會從哪一個offset的 message開始讀取,即 Consumer Group A 中的Consumer下次會從offset = 9 的message 讀取, Consumer Group B 中的Consumer下次會從offset = 11 的message 讀取。
這裏並無說是Consumer A 下次會從offset = 9 的message讀取,緣由是Consumer A可能會退出Group ,而後Group A 進行rebalance,即從新分配分區。
如何將Kafka的流的概念和傳統的企業信息系統做比較?
消息處理模型從來有兩種:隊列和發佈-訂閱。在隊列模型中,一組消費者能夠從服務器讀取記錄,每一個記錄都會被其中一個消費者處理; 在發佈-訂閱模式裏,記錄被廣播到全部的消費者。這兩種模式都具備必定的優勢和弱點。隊列的優勢是它可讓你把數據分配到多個消費者去處理,它可讓您擴展你的處理能力。不幸的是,隊列不支持多個訂閱者,一旦一個進程讀取了數據,這個數據就會消失。發佈-訂閱模式可讓你廣播數據到多個進程,可是由於每個消息發送到每一個訂閱者,沒辦法對訂閱者處理能力進行擴展。
Kafka的消費羣的推廣了這兩個概念。消費羣能夠像隊列同樣讓消息被一組進程處理(消費羣的成員),與發佈 – 訂閱模式同樣,Kafka可讓你發送廣播消息到多個消費羣。
Kafka的模型的優勢是,每一個主題都具備這兩個屬性,它能夠擴展處理能力,也能夠實現多個訂閱者,沒有必要二選一。
Kafka比傳統的消息系統具備更強的消息順序保證的能力。
傳統的消息隊列的消息在隊列中是有序的,多個消費者從隊列中消費消息,服務器按照存儲的順序派發消息。然而,儘管服務器是按照順序派發消息,可是這些消息記錄被異步傳遞給消費者,消費者接收到的消息也許已是亂序的了。這實際上意味着消息的排序在並行消費中都將丟失。消息系統一般靠 「排他性消費」( exclusive consumer)來解決這個問題,只容許一個進程從隊列中消費,固然,這意味着沒有並行處理的能力。
Kafka作的更好。經過一個概念:並行性-分區-主題實現主題內的並行處理,Kafka是可以經過一組消費者的進程同時提供排序保證和負載均衡。每一個主題的分區指定給每一個消費羣中的一個消費者,使每一個分區只由該組中的一個消費者所消費。經過這樣作,咱們確保消費者是一個分區惟一的讀者,從而順序的消費數據。由於有許多的分區,因此負載還可以均衡的分配到不少的消費者實例上去。可是請注意,一個消費羣的消費者實例不能比分區數量多。
任何消息隊列都可以解耦消息的生產和消費,還可以有效地存儲正在傳送的消息。Kafka不同凡響的是,它是一個很是好的存儲系統。
Kafka把消息數據寫到磁盤和備份分區。Kafka容許生產者等待返回確認,直到副本複製和持久化所有完成才認爲成功,不然則認爲寫入服務器失敗。
Kafka使用的磁盤結構很好擴展,Kafka將執行相同的策略無論你是有50 KB或50TB的持久化數據。
因爲存儲的重要性,並容許客戶控制本身的讀取位置,你能夠把Kafka認爲是一種特殊用途的分佈式文件系統,致力於高性能,低延遲的有保障的日誌存儲,可以備份和自我複製。