-Kafka關(guān)鍵概念介紹
-消息隊列的各種策略與語義
作為一個消息隊列不恭,Kafka在業(yè)界已經(jīng)相當(dāng)有名价淌。相對傳統(tǒng)的RabbitMq/ActiveMq杆故,Kafka天生就是分布式的霜威,支持?jǐn)?shù)據(jù)的分片、復(fù)制以及集群的方便擴(kuò)展欠拾。
與此同時梭域,Kafka是高可靠的石抡、持久化的消息隊列轻黑,并且這種可靠性沒有以犧牲性能為前提糊肤。
同時,在允許丟消息的業(yè)務(wù)場景下氓鄙,Kafka可以以非ACK馆揉、異步的方式來運行,從而最大程度的提高性能抖拦。
從本篇開始升酣,本序列將會由淺入深舷暮、從使用方式到原理再到源碼,全面的剖析Kafka這個消息中間件的方方面面噩茄。(所用Kafka源碼為0.9.0)
關(guān)鍵概念介紹
topic
以下是kafka的邏輯結(jié)構(gòu)圖: 每個topic也就是自定義的一個隊列下面,producer往隊列中放消息,consumer從隊列中取消息巢墅,topic之間相互獨立诸狭。
broker
與上圖對應(yīng)的是kafka的物理結(jié)構(gòu)圖:每個broker通常就是一臺物理機(jī)器券膀,在上面運行kafka server的一個實例君纫,所有這些broker實例組成kafka的服務(wù)器集群。
每個broker會給自己分配一個唯一的broker id芹彬。broker集群是通過zookeeper集群來管理的蓄髓。每個broker都會注冊到zookeeper上,有某個機(jī)器掛了舒帮,有新的機(jī)器加入会喝,zookeeper都會收到通知。
在0.9.0中玩郊,producer/consumer已經(jīng)不會依賴Zookeeper來獲取集群的配置信息肢执,而是通過任意一個broker來獲取整個集群的配置信息。如下圖所示:只有服務(wù)端依賴zk译红,客戶端不依賴zk预茄。
partition
kafka的topic,在每個機(jī)器上侦厚,是用文件存儲的耻陕。而這些文件呢,會分目錄刨沦。partition就是文件的目錄诗宣。比如一個topic叫abc,分了10個partion想诅,則在機(jī)器的目錄上召庞,就是:
abc_0
abc_1
abc_2
abc_3
...
abc_9
然后每個目錄里面,存放了一堆消息文件来破,消息是順序append log方式存儲的裁眯。關(guān)于這個,后面會詳細(xì)闡述讳癌。
replica/leader/follower
每個topic的partion的所有消息穿稳,都不是只存1份,而是在多個broker上冗余存儲晌坤,從而提高系統(tǒng)的可靠性逢艘。這多臺機(jī)器就叫一個replica集合旦袋。
在這個replica集合中,需要選出1個leader它改,剩下的是follower疤孕。也就是master/slave。
發(fā)送消息的時候央拖,只會發(fā)送給leader祭阀,然后leader再發(fā)給follower。
那這里面就有一個問題:leader收到消息之后鲜戒,是直接返回給producer呢专控,還是等所有follower都寫完消息之后,再返回遏餐? 關(guān)于這個伦腐,后面會相信闡述。
關(guān)鍵點:這里replica/leader/follower都是邏輯概念失都,并且是相對”partion”來講的柏蘑,而不是”topic”。也就說粹庞,同一個topic的不同partion咳焚,對于的replica集合可以是不一樣的。
比如
“abc-0” <1,3,5> //abc_0的replica集合是borker 1, 3, 5庞溜, leader是1革半, follower是3, 5
“abc-1” <1,3,7> //abc_1的replica集合是broker 1, 3, 7,leader是1, follower是3, 7
“abc_2” <3,7,9>
“abc_3” <1,7,9>
“abc_4” <1,3,5>
消息隊列的各種策略和語義
對于消息隊列的使用强缘,表面上看起來很簡單督惰,一端往里面放,一端從里面取旅掂。但就在這一放一取中赏胚,存在著諸多策略。
Producer的策略
是否ACK
所謂ACK商虐,是指服務(wù)器收到消息之后觉阅,是存下來之后,再給客戶端返回秘车,還是直接返回典勇。很顯然,是否ACK叮趴,是影響性能的一個重要指標(biāo)割笙。在kafka中,request.required.acks有3個取值,分別對應(yīng)3種策略:
request.required.acks
//0: 不等服務(wù)器ack就返回了伤溉,性能最高般码,可能丟數(shù)據(jù)
//1. leader確認(rèn)消息存下來了,再返回
//all: leader和所有的follower都確認(rèn)消息存下來了乱顾,再返回板祝。最可靠
備注:在0.9.0以前的版本,是用-1表示all
同步發(fā)送 vs 異步發(fā)送
所謂異步發(fā)送走净,就是指客戶端有個本地緩沖區(qū)券时,消息先存放到本地緩沖區(qū),然后有后臺線程來發(fā)送伏伯。
在0.8.2和0.8.2之前的版本中橘洞,同步發(fā)送和異步發(fā)送是分開實現(xiàn)的,用的Scala語言舵鳞。從0.8.2開始震檩,引入了1套新的Java版的client api琢蛤。在這套api中蜓堕,同步實際上是用異步間接實現(xiàn)的:
在異步發(fā)送下,有以下4個參數(shù)需要配置:
(1)隊列的最大長度
buffer.memory //缺省為33554432, 即32M
(2)隊列滿了博其,客戶端是阻塞套才,還是拋異常出來(缺省是true)
block.on.buffer.full
//true: 阻塞消息
//false:拋異常
(3)發(fā)送的時候,可以批量發(fā)送的數(shù)據(jù)量
batch.size //缺省16384字節(jié)慕淡,即16K
(4)最長等多長時間背伴,批量發(fā)送
linger.ms //缺省是0
//類似TCP/IP協(xié)議中的linger algorithm,> 0 表示發(fā)送的請求峰髓,會在隊列中積攥傻寂,然后批量發(fā)送。
很顯然携兵,異步發(fā)送可以提高發(fā)送的性能疾掰,但一旦客戶端掛了,就可能丟數(shù)據(jù)徐紧。
對于RabbitMQ, ActiveMQ静檬,他們都強(qiáng)調(diào)可靠性,因此不允許非ACK的發(fā)送并级,也沒有異步發(fā)送模式拂檩。Kafka提供了這個靈活性,允許使用者在性能與可靠性之間做權(quán)衡嘲碧。
(5)消息的最大長度
max.request.size //缺省是1048576稻励,即1M
這個參數(shù)會影響batch的大小,如果單個消息的大小 > batch的最大值(16k)愈涩,那么batch會相應(yīng)的增大
Consumer的策略
Push vs Pull
所有的消息隊列都要面對一個問題望抽,是broker把消息Push給消費者呢至非,還是消費者主動去broker Pull消息?
kafka選擇了pull的方式糠聪,為什么呢荒椭? 因為pull的方式更靈活:消息發(fā)送頻率應(yīng)該如何,消息是否可以延遲然后batch發(fā)送舰蟆,這些信息只有消費者自己最清楚趣惠!
因此把控制權(quán)交給消費者,消費者自己控制消費的速率身害,當(dāng)消費者處理消息很慢時味悄,它可以選擇減緩消費速率;當(dāng)處理消息很快時塌鸯,它可以選擇加快消費速率侍瑟。而在push的方式下,要實現(xiàn)這種靈活的控制策略丙猬,就需要額外的協(xié)議涨颜,讓消費者告訴broker,要減緩還是加快消費速率茧球,這增加了實現(xiàn)的復(fù)雜性庭瑰。
另外pull的方式下,消費者可以很容易的自適應(yīng)控制消息是batch的發(fā)送抢埋,還是最低限度的減少延遲弹灭,每來1個就發(fā)送1個。
消費的confirm
在消費端揪垄,所有消息隊列都要解決的一個問題就是“消費確認(rèn)問題”:消費者拿到一個消息穷吮,然后處理這個消息的時候掛了,如果這個時候broker認(rèn)為這個消息已經(jīng)消費了饥努,那這條消息就丟失了捡鱼。
一個解決辦法就是,消費者在消費完之后肪凛,再往broker發(fā)個confirm消息堰汉。broker收到confirm消息之后,再把消息刪除伟墙。
要實現(xiàn)這個翘鸭,broker就要維護(hù)每個消息的狀態(tài),已發(fā)送/已消費戳葵,很顯然就乓,這會增大broker的實現(xiàn)難度。同時,這還有另外一個問題生蚁,就是消費者消費完消息噩翠,發(fā)送confirm的時候,掛了邦投。這個時候會出現(xiàn)重復(fù)消費的問題伤锚。
kafka沒有直接解決這個問題,而是引入offset回退機(jī)制志衣,變相解決了這個問題屯援。在kafka里面,消息會存放一個星期念脯,才會被刪除狞洋。并且在一個partion里面,消息是按序號遞增的順序存放的绿店,因此消費者可以回退到某一個歷史的offset吉懊,進(jìn)行重新消費。
當(dāng)然假勿,對于重復(fù)消費的問題借嗽,需要消費者去解決。
broker的策略
消息的順序問題
在某些業(yè)務(wù)場景下废登,需要消息的順序不能亂:發(fā)送順序和消費順序要嚴(yán)格一致淹魄。而在kafka中郁惜,同一個topic堡距,被分成了多個partition,這多個partition之間是互相獨立的兆蕉。
之所以要分成多個partition羽戒,是為了提高并發(fā)度,多個partition并行的進(jìn)行發(fā)送/消費虎韵,但這卻沒有辦法保證消息的順序問題易稠。
一個解決辦法是,一個topic只用一個partition包蓝,但這樣很顯然限制了靈活性驶社。
還有一個辦法就是,所有發(fā)送的消息测萎,用同一個key亡电,這樣同樣的key會落在一個partition里面。
消息的刷盤機(jī)制
我們都知道硅瞧,操作系統(tǒng)本身是有page cache的份乒。即使我們用無緩沖的io,消息也不會立即落到磁盤上,而是在操作系統(tǒng)的page cache里面或辖。操作系統(tǒng)會控制page cache里面的內(nèi)容瘾英,什么時候?qū)懟氐酱疟P。在應(yīng)用層颂暇,對應(yīng)的就是fsync函數(shù)缺谴。
我們可以指定每條消息都調(diào)用一次fsync存盤,但這會較低性能耳鸯,也增大了磁盤IO瓣赂。也可以讓操作系統(tǒng)去控制存盤。
消息的不重不漏 – Exactly Once
一個完美的消息隊列片拍,應(yīng)該做到消息的“不重不漏”煌集,這里面包含了4重語義:
消息不會重復(fù)存儲;
消息不會重復(fù)消費捌省;
消息不會丟失存儲苫纤;
消息不會丟失消費。
先說第1個:重復(fù)存儲纲缓。發(fā)送者發(fā)送一個消息之后卷拘,服務(wù)器返回超時了。那請問祝高,這條消息是存儲成功了栗弟,還是沒有呢?
要解決這個問題:發(fā)送者需要給每條消息增加一個primary key工闺,同時服務(wù)器要記錄所有發(fā)送過的消息乍赫,用于判重。很顯然陆蟆,要實現(xiàn)這個雷厂,代價很大
重復(fù)消費:上面說過了,要避免這個叠殷,消費者需要消息confirm改鲫。但同樣,會引入其他一些問題林束,比如消費完了像棘,發(fā)送confirm的時候,掛了怎么辦壶冒? 一個消息一直處于已發(fā)送缕题,但沒有confirm狀態(tài)怎么辦?
丟失存儲:這個已經(jīng)解決
丟失消費:同丟失存儲一樣依痊,需要confirm避除。
總結(jié)一下:真正做到不重不漏怎披,exactly once,是很難的瓶摆。這個需要broker凉逛、producer、consumer和業(yè)務(wù)方的協(xié)調(diào)配合群井。
在kafka里面状飞,是保證消息不漏,也就是at least once书斜。至于重復(fù)消費問題诬辈,需要業(yè)務(wù)自己去保證,比如業(yè)務(wù)加判重表荐吉。
歡迎加入QQ群:104286694