主要是想學(xué)習(xí)一個高性能的,高可用的分布式消息系統(tǒng)是如何設(shè)計(jì)和考慮的掏愁?
先寫提綱枢劝,后面自問自答
生產(chǎn)者的高性能如何保障净捅?
- 如何把海量的數(shù)據(jù)快速發(fā)送完畢
- 如何確保數(shù)據(jù)被接收
- 發(fā)送過程中狸涌,網(wǎng)絡(luò)異常和抖動怎么辦
- 如果重復(fù)發(fā)送了數(shù)據(jù)怎么處理切省?
設(shè)計(jì)一個client 要考慮的問題:
異常處理的能力。
由于服務(wù)端是一個和黑子帕胆,很可能存在諸多的問題朝捆,比如網(wǎng)絡(luò)性能,網(wǎng)絡(luò)異常懒豹,服務(wù)器宕機(jī)等芙盘,
需要在諸多的不確定下,實(shí)現(xiàn)良好的異常處理機(jī)制脸秽。
1)如何確保儒老,消息一定被接收到。應(yīng)答ack是一個常用的方法豹储。對于發(fā)送出去的消息贷盲,kaf 放在一個inflightRequest隊(duì)列里面淘这。
如果收到客戶端的ack剥扣,就視為真正的發(fā)送成功巩剖,否則則啟動重試策略,比如一段時間沒有收到ack就可以發(fā)起重試钠怯。
當(dāng)然是否需要ack佳魔,也是可以配置的。根據(jù)具體的需求晦炊,kaf可以設(shè)置幾種ack的強(qiáng)度鞠鲜。0,不需要断国。1贤姆,需要leader回復(fù)。-1稳衬,需要 leader +ISR 回復(fù)霞捡。
這在存儲的高可用性的時候會有詳細(xì)的涉及。高效的生產(chǎn)能力薄疚。
比如對接用戶行為碧信,可能行為流數(shù)據(jù)源源不斷的需要寫入到kafka,這個時候?qū)lient的性能上是有要求的街夭。
從設(shè)計(jì)高性能的client端來看砰碴, client 的性能消耗可能存在的地方:
1) 消息預(yù)處理序列化成字節(jié)碼
2) 構(gòu)建網(wǎng)絡(luò)連接,發(fā)送字節(jié)碼板丽。
涉及到網(wǎng)絡(luò)發(fā)送的性能優(yōu)化呈枉, 一個是建立長連接,避免不斷建立連接消耗時間和系統(tǒng)資源埃碱;
使用batch的模式碴卧,一次多發(fā);使用零拷貝的技術(shù)乃正,盡量不要多次從 用戶態(tài)到內(nèi)核太的拷貝數(shù)據(jù)住册。
使用零拷貝,可以收到數(shù)據(jù)socket-發(fā)送數(shù)據(jù)socket直接拷貝瓮具。
kafka client荧飞,基本上用到了上面提到的tip。
kafka是一個異步發(fā)送的模型名党, 需要發(fā)送的數(shù)據(jù)叹阔,先存在本地的一個buffPool里面,
然后等待一段時間传睹,或者達(dá)到一定數(shù)量耳幢,就一起發(fā)送,類似于大巴車待客出發(fā)的模式。
調(diào)用client.close()會強(qiáng)制刷新
自動處理服務(wù)端變化的能力睛藻。
服務(wù)端1開始10個服務(wù)器启上,后來擴(kuò)展到20個服務(wù)器,client需要有自動rebalance的能力店印。
kafka 提供了幾種模式冈在,可以指定分區(qū),也可以不指定按摘, 不指定的時候包券,需要獲取集群的metadata,
然后根據(jù)meta的信息來決定待發(fā)送的 leader是否 alive等炫贤。
上面說的是擴(kuò)容的情況溅固,其實(shí)服務(wù)端的變化,還包含宕機(jī)和恢復(fù)兰珍。 kafka都可以通過metadata接口來感知发魄,
并作出相應(yīng)的變化。如果product 自己宕機(jī)了怎么辦俩垃?
現(xiàn)有文檔對這個問題涉及的比較少励幼,只是在 exactly once 有相關(guān)的論述。
如果product 發(fā)送A 的過程中口柳,自己宕機(jī)了苹粟,那么恢復(fù)之后如何知道,這條A 是否需要從新發(fā)送呢跃闹?
可以記錄A消息是否收到ACK嵌削,如果收到不重發(fā),如果不收到重發(fā)望艺。
但是有可能ACK是中途丟失了了苛秕,重復(fù)發(fā)送,kafka 就可能收到兩份A重復(fù)的找默。 這就比較麻煩了艇劫,
這個時候需要給消息一個全局的ID,kafka判斷A是否已經(jīng)重復(fù)惩激。
所以說 client 無法做 exactly once的店煞,只能靠kafka 去判斷,兩者還需配合风钻。
接收者高性能+ 高可用如何保障
- 是如何存儲數(shù)據(jù)的
- 是如何做擴(kuò)展的
- 數(shù)據(jù)一致性是如何保證的
- 分片和副本是如何做到高可用的
- 讀/寫是如何分別做到高性能的
- 歷史數(shù)據(jù)會永遠(yuǎn)保存嗎顷蟀?
- 先看存儲的具體騷操作
從寫入1臺,到寫入 3臺server骡技。
分布式系統(tǒng)的三寶鸣个,分片,副本,一致性囤萤。
比如有個topA昼窗, 那么他會存在 3臺服務(wù)器上,都是以文件的方式存儲阁将。
A-1膏秫, 2: A-2右遭, 3:A-3做盅;
分片的方法就是大規(guī)模橫向擴(kuò)展, 這樣就能更具需求加機(jī)器窘哈,每個機(jī)器都分擔(dān)一點(diǎn)壓力吹榴。
我們知道,在只有一臺存儲服務(wù)器的時候滚婉,我們需要給他做備份图筹,在分布式里面也一樣。
每一個分片可以類比與一臺電腦让腹,我們需要給他做一個備份远剩,專業(yè)術(shù)語就是副本。
副本高效的同步一直是分布式系統(tǒng)的一大難題骇窍,而 leader-follower 模型是相對簡單的瓜晤,在leader 宕機(jī)的時候采用一半選舉機(jī)制來選出新的leader。
首先腹纳,我們要明確leader的作用痢掠,就是一切read/write的接口,follow只負(fù)責(zé)做備份嘲恍,不對外提供服務(wù)足画。
kaf的存儲方式非常值得借鑒,如何實(shí)現(xiàn)高效的文件讀寫系統(tǒng)佃牛。
比如淹辞,要寫入a,b,c,d,e,f,g 7條數(shù)據(jù)到1個分區(qū)我們可以看看是如何實(shí)現(xiàn)的。
原理:要想讀的快俘侠,必須要靠索引桑涎。
0.log, 文件存放 a,b
3.log, 存放 c,d
5.log, 存放 e,f
7.log,存放 g兼贡。
現(xiàn)在需要消費(fèi) f攻冷,offset = 6 的數(shù)據(jù)。
總不可能對所有文件進(jìn)行掃描遍希,這個時候就需要索引文件了等曼。
0.index,3. index. 5.index, 7.index
index 的文件名為 offset 的偏移量。
比如offset = 6禁谦,的數(shù)據(jù)一定是在 5.index 里面胁黑,這里存了5-7的數(shù)據(jù)。
鎖定index文件州泊,是一個二分查找丧蘸。
那么 5.index 的內(nèi)容又是什么呢?
5-> 第一行
6-> 第二行
index 文件肯定是倒排索引了遥皂,這個索引是 log 文件的行數(shù)力喷。
通常 一個 log文件大概為1G, 那么對應(yīng)的 index其實(shí)也比較多演训,為了節(jié)省空間以使得index
可以放入內(nèi)存弟孟。 kaf使用了稀疏索引。 并不是為每一行都做倒排样悟,讀取的時候可能需要小范圍的一個遍歷拂募。
寫的時候,由于一個分區(qū)都是有效的窟她,所以會往最后一個文件進(jìn)行順序追加陈症,速度是非常快的震糖。
2)副本同步機(jī)制ISR
在前面提到录肯,kaf在收到生產(chǎn)者的消息之后,有三種處理方式
1)leader 寫入成功之后试伙,就發(fā)送ack嘁信,這種情況下吞吐最大
2)leader+follower全部寫入成功再發(fā)送ack,影響吞吐量疏叨,并且有些follow的宕機(jī)和阻塞可能造成大面積的等待潘靖。
3)kaf的 ISR機(jī)制,維護(hù)一個ISR列表蚤蔓,整個列表中的節(jié)點(diǎn)寫入成功就返回ack卦溢。
ISR機(jī)制是kaf的一個特色。kaf的 leader+follower模式秀又,并沒有使用 一半投票選舉法单寂。 選舉方法的劣勢是,如果要容忍n臺機(jī)器的失敗吐辙,那么必須有2n+1臺機(jī)器做副本宣决,這個開銷是比較大的。
ISR的思想是什么昏苏,就是把整個副本中大家比較一致的維護(hù)起來尊沸,這種一致性代表他們的數(shù)據(jù)層面比較接近威沫,當(dāng)有l(wèi)eader掛了之后,就從這個列表里面拿一個作為新的leader洼专,而無需選舉棒掠。
可以看到,這種情況下屁商,容忍n個節(jié)點(diǎn)壞掉烟很,只需要n+1個副本。
那么ISR隊(duì)列是怎么維護(hù)的呢蜡镶,以三個副本為例進(jìn)行解釋雾袱。
一開始晃琳,初始狀態(tài)(a,b,c) 大家都是0醒第,
來了一條消息,寫入到 a,這個時候妻枕,b,c 作為follower需要一種機(jī)制去pull數(shù)據(jù)粘驰。
kaf里面是一個連接去pull leader的數(shù)據(jù)屡谐,為了性能考慮,做一個半長連接蝌数,leader有數(shù)據(jù)會推送過去愕掏。
正常情況下,follower都是能同步到數(shù)據(jù)顶伞。如果例外來了饵撑,比如宕機(jī)重啟,網(wǎng)絡(luò)繁忙唆貌,可能拉取不及時滑潘,造成了一定時間沒有與leader同步。 這個時間是可以設(shè)置的锨咙,最新的版本kaf就是根據(jù)多久沒拉數(shù)據(jù)來決定是否刪除 ISR语卤。
最開始的版本,還有一個變量控制酪刀,follower與 leader相差多少個消息粹舵,后來這個變量取消了。因?yàn)樯a(chǎn)者可以發(fā)送batch骂倘,用這個變量不太好度量眼滤。
ISR總的來說是處理follower中的例外的,有例外的就踢出历涝,以保證大家基本在一個頻道上诅需,這么做的好處情妖,當(dāng)有l(wèi)eader廢掉之后,可以直接從 ISR拿一個作為leader诱担。
假設(shè)所有的節(jié)點(diǎn)都是健康的毡证,那么寫入的時候,實(shí)際上是所有follower都需要寫入之后才會返回ack蔫仙。
那么在leaer 與follower同步的這段時間內(nèi)料睛,有消費(fèi)者來消費(fèi)數(shù)據(jù),會是個怎么處理的呢摇邦?
例如恤煞,a=1,2,3 條數(shù),b=1,2施籍,c=1居扒, 各個節(jié)點(diǎn) 維護(hù)一個坐標(biāo)叫 highwater,這個值就是提交過的消息的下一個值丑慎。
也就是hw =2喜喂; 盡管 a的long end =4(下一個值); 消費(fèi)的數(shù)據(jù)要保證是大家都有的竿裂。 在這個例子里面玉吁,如果能消費(fèi)2,這個2 還是沒有提交的腻异。假設(shè) a进副,b掛了,生產(chǎn)者可會重發(fā)悔常,等一切正常之后影斑,消費(fèi)者可能還是會重復(fù)消費(fèi)2 。
個人認(rèn)為hw和 long end 是處于這個考慮机打。
(https://www.cnblogs.com/huxi2b/p/7453543.html)
如何做擴(kuò)展的矫户?
broker節(jié)點(diǎn)可以隨時加入和退出,那么必須有一個地方存儲這些信息姐帚,以便集群知道整個情況吏垮。
中心化和p2p的模式都可以實(shí)現(xiàn),kaf使用了中心化的模式罐旗,大量的配置信息存在了zookeeper這樣一個
可以做分布式容錯的配置中心膳汪。
比如,新增加了一個節(jié)點(diǎn)九秀, 他會向zookeeper注冊遗嗽,加入到brokerList里面。
比如鼓蜒,丟失了一個broker痹换, zookeeper會發(fā)現(xiàn)這個問題征字,涉及的后續(xù)操作包括是否需要選擇新的
controller。controller 決定是否需要為一些丟失的分區(qū)選擇新的leader娇豫。
如果是一些follower丟失宕機(jī)了匙姜,zookeeper還維護(hù) ISR的列表,會決定ISR的增刪改查冯痢。
另外kaf可以增加分區(qū)和副本數(shù)氮昧,這些配置信息都是需要zookeeper協(xié)調(diào)處理的。歷史數(shù)據(jù)會留多久浦楣。
kaf的數(shù)據(jù)都是存在磁盤上的袖肥,對于一個topic,可以被不同的消費(fèi)者組消費(fèi)振劳,
比如新建一個消費(fèi)者組椎组,他實(shí)際上是可以消費(fèi)所有保留的歷史數(shù)據(jù)的。
這個歷史數(shù)據(jù)默認(rèn)是保留7天历恐,可通過設(shè)置參數(shù)改變默認(rèn)行為寸癌。
消費(fèi)者高性能+高可用
- 消息太多,如何做到快速消費(fèi)的
- 增加和減少機(jī)器夹供,整個集群會發(fā)生什么變化
- 消費(fèi)者會多次消費(fèi)數(shù)據(jù)嗎灵份, 斷開之后仁堪,再續(xù)上哮洽,消費(fèi)數(shù)據(jù)會有什么變化。
- 消費(fèi)過程中斷網(wǎng)會怎么樣
- 如何保證數(shù)據(jù)只消費(fèi)一次
消費(fèi)端要解決的問題:
1)單臺機(jī)器消費(fèi)要快弦聂,支持彈性的擴(kuò)容鸟辅,可以根據(jù)消息量動態(tài)的增加多臺機(jī)器。
單臺機(jī)器消費(fèi)能力的提升其實(shí)和product差不多莺葫。
- 采用pull的方式拉取數(shù)據(jù)匪凉,可以做一個 long polling, 一定時間之后再timeout捺檬。
- 拉取的時候再层,盡量多拉取數(shù)據(jù),比如達(dá)到一個pool的size堡纬。
- 另外存儲端聂受,我們前面說過他的存儲,給定一個offset烤镐,他是通過索引快速定位到文件行蛋济,并通過零拷貝的方式發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)。
單機(jī)的提升總是有限的炮叶,那么比如通過擴(kuò)容機(jī)制來處理碗旅。比如1個topic 有 5個 分區(qū)渡处。 如果只有2個消費(fèi)者,那么根據(jù)kaf的分配風(fēng)格祟辟,兩臺機(jī)器(1医瘫,2,3)(4,5)旧困,一個分區(qū)的數(shù)據(jù)只能被一個機(jī)器消費(fèi)登下。所以我們最多可以使用5臺機(jī)器進(jìn)行擴(kuò)容消費(fèi)。 添加機(jī)器的時候叮喳,會觸發(fā)一個rebalance的事件被芳。這個可以通過注冊節(jié)點(diǎn)到zookeeper由它來協(xié)調(diào)。比如新加一臺機(jī)器馍悟,消費(fèi)對應(yīng)關(guān)系(1畔濒,2)(3,4)(5)÷嘀洌可以看到這個在平衡的過程后侵状,第二臺和新加入的機(jī)器是需要把一些信息拿過來的。 比如毅整,第二臺機(jī)器需要 獲取趣兄,原來分區(qū)-3 最后一個消費(fèi)的offset。
同理可以推導(dǎo)移除消費(fèi)者的整個過程悼嫉。
2)如何做容錯的
第一種問題就是消費(fèi)機(jī)器艇潭,變慢了或者說是宕機(jī)了。 通诚访铮可以通過心跳機(jī)制來維護(hù)消費(fèi)者的狀態(tài)蹋凝。kaf里面可以通過polling信號來作為心跳的一種,比如一定時間沒有收到polling請求总棵,就認(rèn)為該機(jī)器不可達(dá)鳍寂,就有做相應(yīng)的列表移除和rebalance的操作。重連之后情龄,尚未提交的ack 的消息還是可以再次拉取迄汛。
這里引出一個問題,就是怎么樣的數(shù)據(jù)才算真的消費(fèi)骤视,依舊是ack機(jī)制鞍爱,和product一樣的概念。
比如來取到數(shù)據(jù)到客戶端之后尚胞,就可以自動提交ack硬霍;如果這個時候宕機(jī)了,該消息未處理就丟失了笼裳。
也可以是程序手動提交ack唯卖,也就是真正在處理完業(yè)務(wù)邏輯之后粱玲,再發(fā)送給服務(wù)器端,告訴它某個 offset的消息已經(jīng)處理完畢拜轨。 這個時候如果發(fā)送出去的 ack 丟了抽减,那么下次還是可能會拉取到重復(fù)數(shù)據(jù),如果是對 exactly once要求特別嚴(yán)格的程序橄碾,是需要自己做一些額外的工作保證冪等性的卵沉。
kaf里面有兩種提交方式,一種是auto法牲,大概5秒自動提交一次史汗;一種是手動,一般選擇手動提交的比較多拒垃,手動里面分布同步和異步停撞,同步可以比較好的處理異常情況,而異步提交悼瓮,很可能戈毒,一個條offset=3,成功横堡,后續(xù)一個提交offset=2成功埋市。 異常情況下,順序性等比較難保證命贴,可能會造成重復(fù)消費(fèi)數(shù)據(jù)道宅。
3) 其他一些定制化的tips
提交offset的時候,默認(rèn)是提交polling 拿到的最新的一個消息偏移套么,當(dāng)整個batch比較大的時候培己,我們可能希望消費(fèi)一部分就盡可能的提交一部分,避免一個宕機(jī)前功盡棄胚泌。 consumer的api 提供了指定offset的提交方式。
polling的時候肃弟,也可以指定具體的offset的位置玷室,可以決定從頭開始,還是從具體的某個特定位置開始笤受。
4)精確的一次消費(fèi)
冪等性在kaf里面是一個很大的話題穷缤,后面單獨(dú)討論。
1.Producer如何來實(shí)現(xiàn)箩兽。
構(gòu)建一個 唯一 id 津肛。(pid,topic汗贫,partion身坐, seq)
服務(wù)端秸脱,把這個信息存起來。
解決的問題: 單個分區(qū)發(fā)送的信息部蛇,有序無重復(fù)摊唇。
失敗的例子-1)
如果這個分區(qū)全部掛了,pid寫入到其他分區(qū)涯鲁。
然后巷查,這個分區(qū)又好了。 里面存在了兩份數(shù)據(jù)抹腿。
造成了重復(fù)岛请。
失敗的例子-2)
producer掛了, 假設(shè)業(yè)務(wù)上保存了最后一數(shù)據(jù)(msg+ seq)
那么如果 知道自己不變的 pid警绩, 也是可以恢復(fù)的髓需。
但是應(yīng)為是分布式,要如何保存這個pid 唯一不變呢房蝉?
如果機(jī)器掛了僚匆,換一條,必須要取個不一樣的名字搭幻,如果一樣咧擂,等恢復(fù)了怎么辦?
所以這里行不通檀蹋。
目前kafka松申,的單個分區(qū),單個session 模式能保證 exactly once俯逾。
如果要多份區(qū)呢贸桶?
- 事務(wù)ID
通過上面的推理,我們必須要保障一個 全局的ID 來決定一個全局的 唯一ID
要跨分區(qū)桌肴,必須要有一個地方存儲中心化的信息吧皇筛?
不然如果解決 失敗的例子-1) 這種情況呢?
把(transId坠七,prid水醋,topic,seq) 作為一個id彪置。就不用考慮分區(qū)了拄踪。
存在kafka的一個公共隊(duì)列里面。
貌似加了 生成這邊就沒什么問題了拳魁。惶桐。。
但是更難的問題是, 如果保障 多個寫姚糊,讀贿衍,混合的操作,滿足事務(wù)的語義
要么全部提交叛拷,要么全部abort舌厨。
這個就涉及到 commitOffset 和 readOffset的處理和協(xié)調(diào)問題。
看不太懂= TODO
(http://www.jasongj.com/kafka/transaction/)
設(shè)計(jì)上為什么是優(yōu)秀的忿薇,有哪些亮點(diǎn)可以借鑒與分布式系統(tǒng)中裙椭。
- 分布式核心,分區(qū)署浩,副本揉燃,一致性。
- 文件存儲的高性能筋栋,順序讀寫炊汤,分塊索引
- master-slave 簡單的容錯機(jī)制
擴(kuò)展的基礎(chǔ)知識
- RPC 框架與 異步RPC實(shí)現(xiàn)原理
- push和poll模式區(qū)別,pull 模式 半長連接的效率提升
http://www.reibang.com/p/6e90c2f2e463
核心弊攘,使用poll 模式抢腐,hold住一個長連接,有數(shù)據(jù)之后就返回襟交÷醣叮或者是等待超時就返回,盡可能的讓每一次pull的消耗都是有價值的捣域。 - 分布式的難題啼染, exactly once。