最近在看kafka的代碼夏跷,就免不了想看看消息隊(duì)列的一些要點(diǎn):服務(wù)質(zhì)量(QOS)蒸苇、性能、擴(kuò)展性等等算撮,下面一一探索這些概念,并談?wù)勗谔囟ǖ南㈥?duì)列如kafka或者mosquito中是如何具體實(shí)現(xiàn)這些概念的县昂。
服務(wù)質(zhì)量
服務(wù)語義
服務(wù)質(zhì)量一般可以分為三個級別肮柜,下面說明它們不同語義。
At most once
至多一次倒彰,消息可能丟失审洞,但絕不會重復(fù)傳輸。
生產(chǎn)者:完全依賴底層TCP/IP的傳輸可靠性待讳,不做特殊處理芒澜,所謂“發(fā)送即忘”。kafka中設(shè)置acks=0
创淡。
消費(fèi)者:先保存消費(fèi)進(jìn)度痴晦,再處理消息。kafka中設(shè)置消費(fèi)者自動提交偏移量并設(shè)置較短的提交時間間隔琳彩。
At least once
至少一次誊酌,消息絕不會丟部凑,但是可能會重復(fù)。
生產(chǎn)者:要做消息防丟失的保證碧浊。kafka中設(shè)置acks=1 或 all
并設(shè)置retries>0
涂邀。
消費(fèi)者:先處理消息,再保存消費(fèi)進(jìn)度箱锐。kafka中設(shè)置消費(fèi)者自動提交偏移量并設(shè)置很長的提交時間間隔比勉,或者直接關(guān)閉自動提交偏移量,處理消息后手動調(diào)用同步模式的偏移量提交瑞躺。
Exactly once
精確一次,每條消息肯定會被傳輸一次且僅一次兴想。
這個級別光靠消息隊(duì)列本身并不好保證幢哨,有可能要依賴外部組件。
生產(chǎn)者:要做消息防丟失的保證嫂便。kafka中設(shè)置acks=1 或 all
并設(shè)置retries>0
捞镰。mosquito中通過四步握手與DUP、MessageID等標(biāo)識來實(shí)現(xiàn)單次語義毙替。
消費(fèi)者:要做消息防重復(fù)的保證岸售,有多種方案,如:在保存消費(fèi)進(jìn)度和處理消息這兩個操作中引入兩階段提交協(xié)議厂画;讓消息冪等凸丸;讓消費(fèi)處理與進(jìn)度保存處于一個事務(wù)中來保證原子性
。kafka中關(guān)閉自動提交偏移量袱院,并設(shè)置自定義的再平衡監(jiān)聽器屎慢,監(jiān)聽到分區(qū)發(fā)生變化時從外部組件讀取或者存儲偏移量,保證自己或者其他消費(fèi)者在更換分區(qū)時能讀到最新的偏移量從而避免重復(fù)忽洛∧寤荩總之就是結(jié)合ConsumerRebalanceListener
、seek
和一個外部系統(tǒng)
(如支持事務(wù)的數(shù)據(jù)庫)共同來實(shí)現(xiàn)單次語義欲虚。此外集灌,kafka還提供了GUID以便用戶自行實(shí)現(xiàn)去重。kafka 0.11版本通過3個大的改動支持EOS
:1.冪等的producer复哆;2. 支持事務(wù)欣喧;3. 支持EOS的流式處理(保證讀-處理-寫全鏈路的EOS)。
這三個級別可靠性依次增加梯找,但是延遲和帶寬占用也會增加续誉,所以實(shí)際情況中,要依據(jù)業(yè)務(wù)類型做出權(quán)衡初肉。
可靠性
上面的三個語義不僅需要生產(chǎn)者和消費(fèi)者的配合實(shí)現(xiàn)酷鸦,還要broker本身的可靠性來進(jìn)行保證。可靠性就是只要broker向producer發(fā)出確認(rèn),就一定要保證這個消息可以被consumer獲取臼隔。
kafka 中一個topic
有多個partition
嘹裂,每個partition
又有多個replica
,所有replica
中有一個leader
摔握,ISR
是一定要同步leader
后才能返回提交成功的replica集
寄狼,OSR
內(nèi)的replica
盡力的去同步leader
,可能數(shù)據(jù)版本會落后氨淌。在kafka
工作的過程中泊愧,如果某個replica
同步速度慢于replica.lag.time.max.ms
指定的閾值,則被踢出ISR
存入OSR
盛正,如果后續(xù)速度恢復(fù)可以回到ISR
中删咱。可以配置min.insync.replicas
指定ISR
中的replica
最小數(shù)量豪筝,默認(rèn)該值為1痰滋。LEO
是分區(qū)的最新數(shù)據(jù)的offset,當(dāng)數(shù)據(jù)寫入leader后续崖,LEO就立即執(zhí)行該最新數(shù)據(jù)敲街,相當(dāng)于最新數(shù)據(jù)標(biāo)識位。HW
是當(dāng)寫入的數(shù)據(jù)被同步到所有的ISR
中的副本后严望,數(shù)據(jù)才認(rèn)為已提交多艇,HW
更新到該位置,HW
之前的數(shù)據(jù)才可以被消費(fèi)者訪問像吻,保證沒有同步完成的數(shù)據(jù)不會被消費(fèi)者訪問到墩蔓,相當(dāng)于所有副本同步數(shù)據(jù)標(biāo)識位。
每個partition
的所有replica
需要進(jìn)行leader
選舉(依賴ZooKeeper)萧豆。在leader
宕機(jī)后奸披,只能從ISR
列表中選取新的leader
,無論ISR
中哪個副本被選為新的leader
涮雷,它都知道HW
之前的數(shù)據(jù)阵面,可以保證在切換了leader
后,消費(fèi)者可以繼續(xù)看到HW
之前已經(jīng)提交的數(shù)據(jù)洪鸭。當(dāng)ISR
中所有replica
都宕機(jī)該partition
就不可用了样刷,可以設(shè)置unclean.leader.election.enable=true
,該選項(xiàng)使得kafka
選擇任何一個活的replica成為leader然后繼續(xù)工作览爵,此replica
可能不在ISR
中置鼻,就可能導(dǎo)致數(shù)據(jù)丟失。所以實(shí)際使用中需要進(jìn)行可用性與可靠性的權(quán)衡蜓竹。
kafka建議數(shù)據(jù)可靠存儲不依賴于數(shù)據(jù)強(qiáng)制刷盤(會影響整體性能)箕母,而是依賴于replica
储藐。
順序消費(fèi)
順序消費(fèi)是指消費(fèi)者處理消息的順序與生產(chǎn)者投放消息的順序一致。
主要可能破壞順序的場景是生產(chǎn)者投放兩條消息AB嘶是,然后A失敗重投遞導(dǎo)致消費(fèi)者拿到的消息是BA钙勃。
kafka中能保證分區(qū)內(nèi)部消息的有序性,其做法是設(shè)置max.in.flight.requests.per.connection=1
聂喇,也就是說生產(chǎn)者在未得到broker對消息A的確認(rèn)情況下是不會發(fā)送消息B的辖源,這樣就能保證broker存儲的消息有序,自然消費(fèi)者請求到的消息也是有序的希太。
但是我們明顯能感覺到這會降低吞吐量克饶,因?yàn)橄⒉荒懿⑿型哆f了,而且會阻塞等待誊辉,也沒法發(fā)揮 batch 的威力矾湃。
如果想要整個topic有序,那就只能一個topic
一個partition
了芥映,一個consumer group
也就只有一個consumer
了洲尊。這樣就違背了kafka高吞吐的初衷远豺。
重復(fù)消費(fèi)
重復(fù)消費(fèi)是指一個消息被消費(fèi)者重復(fù)消費(fèi)了奈偏。 這個問題也是上面第三個語義需要解決的。
一般的消息系統(tǒng)如kafka或者類似的rocketmq都不能也不提倡在系統(tǒng)內(nèi)部解決躯护,而是配合第三方組件惊来,讓用戶自己去解決。究其原因還是解決問題的成本與解決問題后獲得的價值不匹配棺滞,所以干脆不解決裁蚁,就像操作系統(tǒng)對待死鎖一樣,采取“鴕鳥政策”继准。
但是kafka 0.11
還是處理了這個問題枉证,見發(fā)行說明,維護(hù)者是想讓用戶無可挑剔嘛 [笑cry]移必。
性能
衡量一個消息系統(tǒng)的性能有許多方面室谚,最常見的就是下面幾個指標(biāo)。
連接數(shù)
是指系統(tǒng)在同一時刻能支持多少個生產(chǎn)者或者消費(fèi)者的連接總數(shù)崔泵。連接數(shù)和broker采用的網(wǎng)絡(luò)IO模型直接相關(guān)秒赤,常見模型有:單線程、連接每線程憎瘸、Reactor入篮、Proactor等。
單線程一時刻只能處理一個連接幌甘,連接每線程受制于server的線程數(shù)量潮售,Reactor是目前主流的高性能網(wǎng)絡(luò)IO模型痊项,Proactor由于操作系統(tǒng)對真異步的支持不太行所以尚未流行。
kafka的broker
采用了類似于Netty
的Reactor
模型:1(1個Acceptor
線程)+N(N個Processor
線程)+M(M個Work
線程)饲做。
其中Acceptor
負(fù)責(zé)監(jiān)聽新的連接請求线婚,同時注冊OPACCEPT
事件,將新的連接按照RoundRobin
的方式交給某個Processor
線程處理盆均。
每個Processor
都有一個NIO selector
塞弊,向 Acceptor
分配的 SocketChannel
注冊 OPREAD、OPWRITE
事件泪姨,對socket進(jìn)行讀寫游沿。N由num.networker.threads
決定。
Worker
負(fù)責(zé)具體的業(yè)務(wù)邏輯如:從requestQueue
中讀取請求肮砾、數(shù)據(jù)存儲到磁盤诀黍、把響應(yīng)放進(jìn)responseQueue
中等等。M的大小由num.io.threads
決定仗处。
Reactor模型一般基于IO多路復(fù)用(如select
眯勾,epoll
),是非阻塞的婆誓,所以少量的線程能處理大量的連接吃环。
如果大量的連接都是idle
的,那么Reactor使用epoll
的效率是杠杠的洋幻,如果大量的連接都是活躍的郁轻,此時如果沒有Proactor
的支持就最好把epoll
換成select
或者poll
。
具體做法是-Djava.nio.channels.spi.SelectorProvider
把sun.nio.ch
包下面的EPollSelectorProvider
換成PollSelectorProvider
文留。
QPS
是指系統(tǒng)每秒能處理的請求數(shù)量好唯。QPS通常可以體現(xiàn)吞吐量(該術(shù)語很廣燥翅,可以用TPS/QPS骑篙、PV、UV森书、業(yè)務(wù)數(shù)/小時等單位體現(xiàn))的大小靶端。
kafka中由于可以采用 batch 的方式(還可以壓縮),所以每秒鐘可以處理的請求很多(因?yàn)闇p少了解析量拄氯、網(wǎng)絡(luò)往復(fù)次數(shù)躲查、磁盤IO次數(shù)等)。另一方面译柏,kafka每一個topic都有多個partition镣煮,所以同一個topic下可以并行(注意不是并發(fā)喲)服務(wù)多個生產(chǎn)者和消費(fèi)者,這也提高了吞吐量鄙麦。
平均響應(yīng)時間
平均響應(yīng)時間是指每個請求獲得響應(yīng)需要的等待時間典唇。
kafka中處理請求的瓶頸(也就是最影響響應(yīng)時間的因素)最有可能出現(xiàn)在哪些地方呢镊折?
網(wǎng)絡(luò)? 有可能介衔,但是這個因素總體而言不是kafka能控制的恨胚,kafka可以對消息進(jìn)行編碼壓縮并批量提交,減少帶寬占用炎咖;
磁盤赃泡? 很有可能,所以kafka從分利用OS的pagecache乘盼,并且對磁盤采用順序?qū)?/strong>升熊,這樣能大大提升磁盤的寫入速度。同時kafka還使用了零拷貝技術(shù)绸栅,把普通的拷貝過程:disk->read buffer->app buffer->socket buffer->NIC buffer 中级野,內(nèi)核buffer到用戶buffer的拷貝過程省略了,加快了處理速度粹胯。此外還有文件分段技術(shù)蓖柔,每個partition
都分為多個segment
,避免了大文件操作的同時提高了并行度风纠。
CPU况鸣? 不大可能,因?yàn)橄㈥?duì)列的使用并不涉及大量的計(jì)算议忽,常見消耗有線程切換懒闷、編解碼十减、壓縮解壓栈幸、內(nèi)存拷貝等,這些在大數(shù)據(jù)處理中一般不是瓶頸帮辟。
并發(fā)數(shù)
是指系統(tǒng)同時能處理的請求數(shù)量數(shù)速址。一般而言,QPS = 并發(fā)數(shù)/平均響應(yīng)時間
或者說 并發(fā)數(shù) = QPS*平均響應(yīng)時間
由驹。
這個參數(shù)一般只能估計(jì)或者計(jì)算芍锚,沒法直接測。顧名思義蔓榄,機(jī)器性能越好當(dāng)然并發(fā)數(shù)越高咯并炮。此外注意用上多線程技術(shù)并且提高代碼的并行度、優(yōu)化IO模型甥郑、減少減少內(nèi)存分配和釋放等手段都是可以提高并發(fā)數(shù)的逃魄。
擴(kuò)展性
消息系統(tǒng)的可擴(kuò)展性是指要為系統(tǒng)組件添加的新的成員的時候比較容易。
kafka
中擴(kuò)展性的基石就是topic
采用的partition
機(jī)制澜搅。第一伍俘,Kafka
允許Partition
在cluster
中的Broker
之間移動邪锌,以此來解決數(shù)據(jù)傾斜問題。第二癌瘾,支持自定義的Partition
算法觅丰,比如你可以將同一個Key
的所有消息都路由到同一個Partition
上去(來獲得順序)。第三妨退,partition
的所有replica
通過ZooKeeper
來進(jìn)行集群管理妇萄,可以動態(tài)增減副本。第四咬荷,partition也支持動態(tài)增減嚣伐。
對于producer
,不存在擴(kuò)展問題萍丐,只要broker
還夠你連接就行轩端。
對于consumer
,一個consumer group
中的consumer
可以增減逝变,但是最好不要超過一個topic
的partition
數(shù)量基茵,因?yàn)槎嘤嗟?code>consumer并不能提升處理速度,一個partition
在同一時刻只能被一個consumer group
中的一個consumer
消費(fèi)
代碼上的可擴(kuò)展性就屬于設(shè)計(jì)模式的領(lǐng)域了壳影,這里不談拱层。
參考
《kafka技術(shù)內(nèi)幕》
Kafka的存儲機(jī)制以及可靠性
Kafka 0.11.0.0 是如何實(shí)現(xiàn) Exactly-once 語義的
查看原文,來自mageekchiu宴咧「疲總結(jié)不到位的地方請不吝賜教。