kafka設(shè)計(下)

6.消息投遞

我們已經(jīng)了解了一些生產(chǎn)者和消費者是如何工作的么库,現(xiàn)在讓我們討論在生產(chǎn)者和消費者之間征绸,kafka提供的語義上的保證久橙,顯然kafka提供了多種可能的消息投遞保證:

  • At most once:最多一次,消息可能丟失管怠,而且也不會被再次投遞淆衷;
  • At least once:最少一次,消息不會丟失渤弛,但是有可能會被再次投遞祝拯;
  • Exactly once:恰好一次,這是用戶實際需要的她肯,每條消息會且僅會被投遞一次鹿驼。

這會被分解為兩個問題:發(fā)送消息時的持久化保證和消費消息時的保證欲低。

許多系統(tǒng)申明提供了"恰好一次"投遞語義,但是注意閱讀細(xì)節(jié)畜晰,許多的這些申明是誤導(dǎo)(他們沒有考慮消費者或者生產(chǎn)者出錯的情況砾莱,也沒有考慮有多個消費者的情況,也沒有考慮數(shù)據(jù)寫到磁盤可能丟失的情況)凄鼻。

kafka的語義非常直接腊瑟,當(dāng)發(fā)送消息的時候,我們有一個消息被"提交"到日志中的概念块蚌,一旦一個發(fā)送的消息被提交闰非,只要消息寫入的分區(qū)有一個broker存活,消息就不會丟失峭范。

現(xiàn)在讓我們假設(shè)一個完美的财松,沒有任何損壞的broker,然后試圖理解對生產(chǎn)者和消費者的保證纱控。如果一個生產(chǎn)者試圖發(fā)送一個消息并且碰到網(wǎng)絡(luò)錯誤辆毡,那么它不能確認(rèn)錯誤發(fā)生在消息提交前還是后。

在0.11.0.0版本之前甜害,如果生產(chǎn)者沒有收到消息已提交的響應(yīng)舶掖,它只能選擇重新發(fā)送消息。這就提供了"至少一次"投遞語義尔店。因為原來請求如果實際成功了眨攘,那么重發(fā)可能將消息再次寫入日志中。

從0.11.0.0開始嚣州,kafka生產(chǎn)者也支持冪等投遞選項鲫售,從而保證重發(fā)消息不會在日志中重復(fù)寫入。為了實現(xiàn)這種特性该肴,broker分配給每個生產(chǎn)者一個ID龟虎,并且使用生產(chǎn)者發(fā)送每條消息的序列數(shù)字,實現(xiàn)重復(fù)消息刪除沙庐。

同時從0.11.0.0開始鲤妥,生產(chǎn)者支持使用類事務(wù)語義向多個topic分區(qū)發(fā)送消息的能力:要么所有消息成功寫入所有分區(qū),要么不寫入任何分區(qū)拱雏。這個特性的主要使用場景就是為了kafka topic的"恰好一次"處理棉安。

并不是我們所有的使用場景都需要如此強的保證,對那些延遲敏感的用戶铸抑,我們允許生產(chǎn)者指定它期望的持久化級別贡耽。生產(chǎn)者可以指定它們要等待消息提交,但是生產(chǎn)者也能指定執(zhí)行完全異步發(fā)送,或者等待只要leader(而不是所有的副本)確認(rèn)消息即可蒲赂。

現(xiàn)在讓我們從消費者的角度描述這個語義阱冶。所有副本具有相同的offset,相同的日志滥嘴,消費者控制其在日志中的位置木蹬。如果消費者從來沒有出問題,它能保存這個位置信息在內(nèi)存中若皱,但是如果消費者故障镊叁,我們要topic分區(qū)被另一個消費者接管,新的處理需要選擇一個開始處理的合適的位置走触,它有幾個處理消息并更新位置的選項:

  1. 它能讀取消息晦譬,然后在日志中保存它的位置,最終處理消息互广。在這種情況下敛腌,消費者進程可能在保存位置之后,但在消息處理之前崩潰惫皱。這種情況下像樊,進程接管并從已經(jīng)保存的位置開始處理,即使這個位置之前少量消息還沒有被處理逸吵。這就是"最多一次"語義,如果消費故障缝裁,消息可能沒有被處理扫皱。

  2. 它能讀取消息,處理消息捷绑,最終在日志中保存它的位置韩脑。在這種情況下,消費者進程在處理消息之后粹污,但是在保存位置之前崩潰段多。這種場景,當(dāng)新的進程接管并接受少量已經(jīng)被處理過的消息壮吩。這就對應(yīng)于"至少一次"的語義进苍,許多情況下,消息有主鍵因此更新是冪等的(兩次收到相同的消息鸭叙,只是用另一個自身的副本覆蓋記錄)觉啊。

那么(用戶實際需要的)"恰好一次"語義呢?當(dāng)從kafka的topic消費并產(chǎn)生到另一個topic時(即kafka stram應(yīng)用場景)沈贝,我們可以利用上面提到的0.11.0.0中的新的事務(wù)性的生產(chǎn)者的能力杠人。

消費者的位置以topic的消息形式保存。因此我們能在接收topic處理數(shù)據(jù)結(jié)果的相同事務(wù)中把offset寫到kafka中。如果事務(wù)中止,消費者的位置將回到老的地方昭躺,在topic上生成的數(shù)據(jù)對其他消費者不可見,當(dāng)然這取決于它們的隔離級別(參考參數(shù):isolation.level)。默認(rèn)是read_uncommitted隔離級別,即所有消息對所有消費者可見军援,即使它們是中止事務(wù)的一部分。但是如果設(shè)置read_committed,消費者僅能消費從已提交事務(wù)中返回的消息哮内。

當(dāng)寫到外部系統(tǒng)時,這限制是需要協(xié)調(diào)消費者的位置與實際存儲輸出惊畏。實現(xiàn)這個目標(biāo)的經(jīng)典方法就是在消費者位置存儲和消費者輸出存儲之間引入二階提交。但是可以通過讓消費者將offset存儲在與其輸出相同的位置來更簡單地處理口猜,這是更好的须尚,因為消費者可能想要寫入的許多輸出系統(tǒng)可能不支持兩階段提交咙咽。 例如,一個Kafka Connect連接器寂玲,它填充HDFS中的數(shù)據(jù)以及它讀取的數(shù)據(jù)的偏移量想许,以確保數(shù)據(jù)和偏移都被更新或兩者都不更新。 我們遵循許多其他數(shù)據(jù)系統(tǒng)的類似模式断序,這些數(shù)據(jù)系統(tǒng)需要這些更強的語義流纹,并且消息沒有主鍵以允許重復(fù)數(shù)據(jù)刪除。

因此违诗,kafka在kafka stream中支持消息"恰好一次"投遞漱凝。事務(wù)性的生產(chǎn)者和消費者通常被用來在kafka topic之間傳輸數(shù)據(jù)或者處理數(shù)據(jù)時,提供"恰好一次"投遞较雕。

其他目標(biāo)系統(tǒng)中"恰好一次"投遞通常需要與此類系統(tǒng)合作碉哑,kafka提供offset,使得實現(xiàn)這點是可行的亮蒋。因此扣典,kafka默認(rèn)保證至少一次投遞,并允許用戶通過關(guān)閉生產(chǎn)者的重試機制并在消費者處理一批消息之前提交offset來實現(xiàn)"最多一次"投遞慎玖。

7.復(fù)制

kafka通過服務(wù)器配置數(shù)值復(fù)制每個topic的分區(qū)日志贮尖。當(dāng)集群中某臺服務(wù)器故障后,允許自動故障轉(zhuǎn)移到其他副本趁怔,因此碰到這種故障時湿硝,消息任然可用。

其他消息系統(tǒng)提供一些相關(guān)的復(fù)制特性润努,但是关斜,在我們看來,沒有很大的用處铺浇,并且有很大的缺點:它們是不活動的痢畜,吞吐量嚴(yán)重受到影響,需要復(fù)雜的手動配置等鳍侣。kafka默認(rèn)具備復(fù)制功能丁稀,事實上我們把復(fù)制因子為1的topic當(dāng)做沒有復(fù)制的topic來實現(xiàn)的(這時候每個分區(qū)只有1個leader,沒有任何follower)倚聚。

復(fù)制單元是topic的分區(qū)线衫。在沒有故障的情況下,每個kafka的分區(qū)有一個leader惑折,以及0個或者多個follower授账。包括leader在內(nèi)的總副本數(shù)就是副本因子(例如1個leader枯跑,2個follower,那么副本因子為3)白热,所有的讀和寫在分區(qū)的leader上執(zhí)行全肮。整個kafka集群通常有很多的分區(qū),這些分區(qū)的leader均勻分布在集群的所有broker上棘捣。follower的日志完全和leader的日志一樣:有完全一樣的offset辜腺,消息順序也完全一樣(當(dāng)然,某些時刻乍恐,leader可能有一些還沒有被復(fù)制到follower的消息在最新的日志中评疗,但是這些日志對客戶端是不可見的)。

follower從leader消費消息茵烈,和一個普通的kafka消費者一樣百匆。然后將消息保存在它們自己的日志中。follower從leader拉取日志呜投,這是非常好的特性怖现,這讓follower可以很自然的批量處理日志介牙。

和許多分布式系統(tǒng)一樣,自動處理故障需要一個節(jié)點存活的明確的定義。對kafka節(jié)點來說琅束,存活有兩個條件:

  1. 節(jié)點必須能通過zookeeper的心跳機制與其保持回話抹锄;
  2. 如果是follower稿存,它必須復(fù)制發(fā)生在leader上的寫入栅贴,不能落后太多。

我們認(rèn)為節(jié)點滿足這兩個條件就是"In Sync"(處于同步中)洒扎,從而避免混淆存活和故障兩個概念辑甜。In-Sync的副本集合被稱為ISR,leader持有ISR信息袍冷。如果follower死亡磷醋,卡住,或者落后胡诗,leaer就會把他從ISR列表中移除邓线。卡住和落后副本由配置參數(shù)replica.lag.time.max.ms決定乃戈。

在分布式系統(tǒng)術(shù)語中褂痰,我們只嘗試處理真正有故障的“故障/恢復(fù)”模型亩进,例如其中節(jié)點突然停止工作症虑,然后恢復(fù)(可能不知道它們已經(jīng)死亡)。 Kafka沒有處理所謂的“拜占庭(Byzantine)”故障归薛,即節(jié)點產(chǎn)生任意或惡意的響應(yīng)(可能是由于錯誤或犯規(guī))谍憔。

拜占庭將軍問題:拜占庭是東羅馬帝國的首都匪蝙。由于當(dāng)時拜占庭羅馬帝國國土遼闊,為了防御目的习贫,因此每個軍隊都分隔很遠(yuǎn)逛球,將軍與將軍之間只能靠信差傳消息。 在戰(zhàn)爭的時候苫昌,拜占庭軍隊內(nèi)所有將軍和副官必須達(dá)成一致的共識颤绕,決定是否有贏的機會才去攻打敵人的陣營。但是祟身,在軍隊內(nèi)有可能存有叛徒和敵軍的間諜奥务,左右將軍們的決定又?jǐn)_亂整體軍隊的秩序。在進行共識時袜硫,結(jié)果并不代表大多數(shù)人的意見氯葬。這時候,在已知有成員謀反的情況下婉陷,其余忠誠的將軍在不受叛徒的影響下如何達(dá)成一致的協(xié)議帚称,拜占庭問題就此形成。

我們現(xiàn)在能更加精確的定義消息被認(rèn)為提交秽澳,即當(dāng)分區(qū)所有ISR都同步了該消息到日志中闯睹。并且只有已經(jīng)提交的消息才會被投遞到消費者,這就意味著消費者不需要擔(dān)心可能看到那些如果leader故障而丟失的消息担神。反過來說瞻坝,生產(chǎn)者可以決定是否等待消息被提交,這取決于它們在延遲和可靠性之間平衡的偏向性杏瞻。這個偏向性通過生產(chǎn)者使用acks參數(shù)來控制所刀。需要注意的是,topic還有一個ISR最小數(shù)量的設(shè)置(參數(shù)min.insync.replicas)捞挥,用于檢查生產(chǎn)者請求確認(rèn)一個消息已經(jīng)被寫入ISR集合的最小數(shù)量浮创,這個參數(shù)只有在acks=-1時才能生效。所以如果生產(chǎn)者請求一個不太嚴(yán)格的確認(rèn)機制(例如acks=0砌函,或者acks=1)斩披,即使ISR數(shù)量比參數(shù)指定的數(shù)量還低,消息仍能被提交讹俊,也能被消費垦沉。

kafka提供的保護機制的前提是在任何時候只要還有一個存活的ISR,那么已經(jīng)被提交的消息就不會丟失仍劈。

復(fù)制日志: 法定人數(shù)(Quorums)厕倍,ISR,狀態(tài)機(State Machines)

kafka分區(qū)的核心是復(fù)制日志贩疙。在分布式數(shù)據(jù)系統(tǒng)中讹弯,復(fù)制日志是最基本的原語之一况既,也有許多方法可以實現(xiàn)一個。

復(fù)制日志模擬了對一系列值的順序達(dá)成共識的過程(通常將日志條目編號為0,1,2组民,...)棒仍,有許多方法實現(xiàn)它,但是最簡單和最快的方法是leader選擇提供給它值的順序臭胜。只要leader存在莫其,所有副本只能拷貝leader的值和順序。

當(dāng)然如果leader沒有故障耸三,我們不需要復(fù)制榜配。但是當(dāng)leader死亡了,我們需要從follower中選擇一個新的leader吕晌。但是那些follower本身可能落后蛋褥,或者故障。因此我們必須確保選擇一個最新的follower睛驳。一個日志復(fù)制算法必須提供這樣的基本保護烙心,如果我們告訴客戶端消息被提交了,然后leader故障乏沸,那么我們選舉的新的leader必須有這個消息淫茵。這就帶來了一個trade-off:如果leader在申明消息已提交前,等待更多follower確認(rèn)消息蹬跃,那么就會有更多潛在的leader匙瘪。

這種權(quán)衡的一種通用做法就是用多數(shù)投票從而達(dá)成提交決定和leader選舉。這不是kafka的行為蝶缀,而是讓我們拋出這個問題來理解trade-off丹喻。假設(shè)我們有2n+1個副本,leader申明消息為提交前必須有n+1個副本收到消息翁都,如果我們選舉了一個新的leader碍论,那么它必須是n+1個副本中擁有最完整的日志的follower。然后柄慰,只要不超過n個副本出現(xiàn)故障鳍悠,就一定能保證leader有所有完整的已提交消息。

這是因為在n+1個副本之間坐搔,一定有至少一個副本包含了所有已提交消息藏研,這個副本的日志是最完整的,因此將被選舉為新的leader概行。每種選舉算法還有許多其他細(xì)節(jié)需要處理蠢挡,我們現(xiàn)在先忽略它(因為這不是這里討論的重點)。

多數(shù)投票方法有一個非常好的屬性:延遲只取決于更快的服務(wù)器。意思是袒哥,如果副本因子為3,延遲由更快的follower決定消略,而不是最慢的那個(比如有3個副本R1,R2和R3堡称,三個響應(yīng)時間分別是1ms,2ms艺演,4ms却紧,那么只會延遲2ms,而不是4ms)胎撤。

這些實現(xiàn)有豐富的算法晓殊,例如zookeeper的Zab,Raft伤提,和Viewstamped Replication巫俺。我們了解到的與Kafka實際實施的最相似的學(xué)術(shù)算法是微軟的PacificA 。感興趣的同學(xué)可以戳下面的鏈接鏈接了解這些方法更多的細(xì)節(jié):

多數(shù)投票的缺點是它不能承受許多故障肿男,讓你選擇不出leader介汹。容忍一個故障,需要3份數(shù)據(jù)拷貝舶沛。容忍2個故障嘹承,需要5份數(shù)據(jù)拷貝。根據(jù)我們的經(jīng)驗如庭,對于一個實際的系統(tǒng)來說叹卷,僅能承受1個故障是不夠的,但是每個都寫5次坪它,需要5倍的磁盤空間骤竹,只有1/5的吞吐量,對大容量數(shù)據(jù)來說往毡,又不太實用瘤载。這可能就是為什么法定人數(shù)仲裁算法更多的出現(xiàn)在分片的集群配置上,例如ZooKeeper卖擅。而很少出現(xiàn)在主要數(shù)據(jù)存儲上鸣奔。例如HDFS中,namenode的高可用特性是建立在基于多數(shù)投票算法惩阶,但是卻沒有用在數(shù)據(jù)本身(datanode)挎狸。ISR集合持久化保存在ZooKeeper中。正因為如此断楷,ISR中的任何副本都可以被選舉為leader锨匆。

kafka采用了一個略有不同的方法選擇它的仲裁集合。沒有使用大多數(shù)投票,kafka動態(tài)維護了一個跟上leader的ISR集合恐锣,只有這個集合里的成員能夠參與選舉茅主。kafka分區(qū)的寫操作,必須直到所有ISR接收到了這個請求土榴,才能認(rèn)為已提交诀姚。對于kafka用法而言,這是一個重要因素玷禽。因為有許多分區(qū)赫段,并確保leader均衡是非常重要的。有了ISR模型和n+1副本矢赁,kafka的topic能承受n個副本故障而不會丟失已經(jīng)提交的消息(只要還有1個ISR就能正常運行)糯笙。

對于很多我們希望處理的使用場景,我們認(rèn)為trade-off是合理的撩银。在實踐中给涕,為了容忍n個副本故障,大多數(shù)投票和ISR方法都要在提交消息之前等待相同數(shù)量副本確認(rèn)(為了一個節(jié)點故障后集群還能正常工作额获,大多數(shù)法定人數(shù)需要3個副本和1個節(jié)點確認(rèn)稠炬,而ISR方式需要2個副本和1個節(jié)點確認(rèn)),提交能力與最慢服務(wù)器無關(guān)是大多數(shù)投票方法的優(yōu)點咪啡。我們認(rèn)為ISR方法能通過允許客戶端選擇是否阻塞消息提交(acks參數(shù))來改善首启,由于所需的復(fù)制因子較低,額外的吞吐量和磁盤空間是值得的撤摸。

另一個重要的設(shè)計是kafka不需要崩潰的節(jié)點在所有它們的數(shù)據(jù)完整的情況下才恢復(fù)毅桃。這里有兩個主要的問題,首先准夷,磁盤錯誤是持久化數(shù)據(jù)系統(tǒng)操作時最常見的問題钥飞,它們經(jīng)常不會留下完整的數(shù)據(jù)。其次衫嵌,即使這不是問題读宙,但是我們不需要為了我們一致性保證,每次寫操作都fsync楔绞,這可能降低2~3個數(shù)量級的性能结闸。我們的協(xié)議是允許一個副本重新加入ISR,但是在重新加入前酒朵,需要確保它必須完全再次同步所有數(shù)據(jù)桦锄,即使在崩潰時丟失的沒有刷到磁盤上的數(shù)據(jù)。

Unclean leader election: What if they all die?

注意kafka保護數(shù)據(jù)不丟失是需要ISR中至少還有一個副本的前提蔫耽。如果復(fù)制分區(qū)所有節(jié)點都死掉了结耀,保護機制不再適用。

所以一個實際的系統(tǒng)當(dāng)所有副本都死掉后需要做一些合理的事情,如果你恰好不幸碰到這種的事情图甜,考慮將要發(fā)生的事情就非常重要了碍粥,這里有兩種方法可以實施:

  1. 等待ISR中的一個副本活過來,并選擇這個副本為leader(上帝保佑希望它有完整的數(shù)據(jù))黑毅。
  2. 選擇第一個活過來的副本為leader(不一定非要是ISR中的副本)嚼摩。

這是非常簡單的在可用性和一致性之間權(quán)衡,如果我們等待ISR中的副本博肋,那么只要這些副本故障低斋,我們就處于不可用狀態(tài)蜂厅。如果這些副本被破壞匪凡,或者它們的數(shù)據(jù)丟失,那么我們將永久不可用掘猿。如果一個非ISR副本活過來病游,我們也允許它成為leader,那么即使它不能保證有每一條已提交消息稠通,但是所有副本恢復(fù)后還是得以它的日志為準(zhǔn)衬衬。

kafka從0.11.0.0以后,默認(rèn)選擇第一種策略改橘,寧愿等待一個一致性的副本滋尉。當(dāng)然這個可以通過配置unclean.leader.election.enable改變這個行為,為了支持可用性比一致性更重要的用戶場景飞主。

這個問題不止在kafka中才有狮惜,基于法定人數(shù)選舉方案也會遇到這樣的問題。例如碌识,在一個多數(shù)投票方案中碾篡,如果大多數(shù)服務(wù)器永久性故障,那么你也必須做出抉擇筏餐,如果選擇高可用开泽,那么數(shù)據(jù)就可能丟失。

Availability and Durability Guarantees

當(dāng)消息寫入kafka時魁瞪,生產(chǎn)者能選擇等待消息被0穆律,1或者 all (-1)個副本確認(rèn)。需要注意的是导俘,被所有副本確認(rèn)(acks=all)并不保證所有分配的副本收到消息众旗,而是ISR都收到消息,這一點需要特別注意趟畏。

例如贡歧,如果一個topic創(chuàng)建時有兩個副本,然后其中一個故障了,這時候ISR只有一個副本利朵,那么即使指定acks=all律想,并且寫入成功。然而绍弟,如果這剩下的一個副本如果也出現(xiàn)故障技即,數(shù)據(jù)仍然可能丟失。

確保分區(qū)最大可用性樟遣,這種行為可能不是那些把持久性(數(shù)據(jù)不能丟失)看的比可用性更重要的用戶所需要的而叼。因此,kafka提供了兩個topic級別的配置豹悬,被用在消息持久性比可用性更重要的場景:

  • Disable unclean leader election - 關(guān)閉unclean leader選舉(unclean.leader.election.enable)葵陵。如果所有副本不可用,那么分區(qū)也不可用瞻佛,直到最后的leader再次恢復(fù)為止脱篙,寧愿不可用也不愿冒消息丟失的風(fēng)險。

  • Specify a minimum ISR size - 指定ISR最小數(shù)(min.insync.replicas)伤柄。只有ISR數(shù)量超過這個最小數(shù)(可以等于)绊困,分區(qū)才能寫入成功。為了防止消息只寫入一個副本而導(dǎo)致數(shù)據(jù)丟失适刀,這個配置只有在生產(chǎn)者配置acks=all的情況下才生效秤朗,從而保證消息將被ISR確認(rèn)的最小數(shù)量(例如min.insync.replicas=2,那么至少需要2個ISR確認(rèn)笔喉,生產(chǎn)者才會收到寫入成功的響應(yīng))取视。這個配置提供了在持久性和可用性之間的權(quán)衡取舍。設(shè)置更大的min.insync.replicas然遏,能更好的保證一致性贫途,因為消息能被保證寫入更多的副本中,從而減少數(shù)據(jù)丟失的可能性待侵。不過丢早,它也會降低可用性,當(dāng)ISR數(shù)量下降到低于min.insync.replicas的閾值時秧倾,這個分區(qū)將不再能寫入消息怨酝。

復(fù)制管理

上面關(guān)于復(fù)制日志的討論實際上只涵蓋了一個日志,即一個主題分區(qū)那先。然而kafka集群一般會管理成百上千分區(qū)农猬,我們試圖用round-robin方式平衡集群里的分區(qū),避免高容量topic的所有分區(qū)集中在幾個節(jié)點上(而不是盡可能分布在集群各個節(jié)點上)售淡。同樣的斤葱,我們試圖均衡leader慷垮,以便每個節(jié)點成為其分區(qū)的比例份額的leader(假設(shè)集群有3個broker,總計有2個topic揍堕,每個topic有3個分區(qū)料身,3個副本,那么每個broker應(yīng)該有2個leader)衩茸。

優(yōu)化leader選舉過程非常重要芹血,因為這是分區(qū)不可用的關(guān)鍵窗口。一個幼稚的leader選舉行為是leader故障后楞慈,每個分區(qū)都嘗試選舉幔烛。

相反,kafka的做法是選舉某個broker為controller囊蓝,controller發(fā)現(xiàn)broker級別故障后饿悬,承擔(dān)改變這個故障broker上所有受影響分區(qū)的leader。這樣做的結(jié)果是慎颗,我們能批量處理leader變更通知乡恕,即使很多分區(qū)我們的選舉過程代價也很低言询,而且更快俯萎。如果controller故障,某個存活的broker將成為新的controller(這個競爭發(fā)生的概率很低运杭,選舉代價不大夫啊,畢竟不可能broker經(jīng)常故障)。

8.日志壓縮

說明:kafka中的日志壓縮和我們平常通過tar或者zip命令將一個afei.log日志文件壓縮為afei.log.tar.gz或者afei.log.zip是完全不一樣的原理辆憔。

日志壓縮確保kafka總是保留單個topic分區(qū)的日志數(shù)據(jù)中撇眯,每個消息key的最新的、最后已知的值虱咧。它對應(yīng)的用戶場景是熊榛,應(yīng)用崩潰或者系統(tǒng)故障后的狀態(tài)恢復(fù),或者應(yīng)用重啟后腕巡,重新加載緩存玄坦。接下來讓我們深入了解這些用戶場景更多的細(xì)節(jié),然后描述kafka日志壓縮是如何工作的绘沉。

到目前為止煎楣,我們僅描述了更簡單的數(shù)據(jù)保留方法,其中舊日志數(shù)據(jù)在固定的時間周期后或者當(dāng)日志達(dá)到某個預(yù)定大小時被丟棄车伞。這適用于時間事件數(shù)據(jù)择懂,例如每個獨立的日志記錄。 但是另玖,還有一類重要的數(shù)據(jù)流是對KEY的可變數(shù)據(jù)的更改日志(例如困曙,對數(shù)據(jù)庫表的更改)表伦。

讓我們討論一個具體例子,我們有一個topic慷丽,包含了用戶的郵箱地址信息绑榴,每次用戶更新它們的郵箱地址,我們發(fā)送一個消息到這個topic上盈魁,并且這個消息的key是用戶ID∠柙酰現(xiàn)在用戶ID為123的用戶在一段時間內(nèi)發(fā)送了下面這些消息,每條消息對應(yīng)修改后新的郵箱地址(其他用戶ID修改的消息暫時忽略):

123 => afei@sina.com
        .
        .
        .
123 => afei@163.com
        .
        .
        .
123 => afei@gmail.com

kafka的日志壓縮機制能提供給我們一個更細(xì)粒度的保留機制杨耙,因此我們能保證保留下每個KEY的最后一次更新(上面的例子就是afei@gmail.com)赤套。

通過這樣做后,我們能保證日志包含每個KEY的最終值的完整快照珊膜,而不只是最近改變的KEY容握。這意味著下游消費者能恢復(fù)它們的狀態(tài),而并不需要保留所有改變的完整的日志车柠。讓我們從一些有用的用例開始剔氏,從而了解如何使用它。

  1. 數(shù)據(jù)庫變更訂閱竹祷。通常在多個數(shù)據(jù)系統(tǒng)中有一個數(shù)據(jù)集谈跛,并且這些系統(tǒng)中的某個系統(tǒng)就是一種數(shù)據(jù)庫(例如關(guān)系型數(shù)據(jù)庫系統(tǒng),或者KV存儲系統(tǒng))塑陵。例如感憾,你可能有一個數(shù)據(jù)庫,一個緩存令花,一個搜索集群阻桅,一個hadoop集群。每一個數(shù)據(jù)庫的變更兼都,需要反映到緩存 嫂沉,搜索集群中,并最終反映到hadoop中扮碧。在只處理實時更新的情況下趟章,只需要最近的日志即可。 但是芬萍,如果您希望能夠重新加載緩存或恢復(fù)失敗的搜索節(jié)點尤揣,則可能需要完整的數(shù)據(jù)集。
  2. 事件源柬祠。這是一種應(yīng)用程序設(shè)計風(fēng)格北戏,它使用變更日志作為應(yīng)用程序的主存儲。
  3. 高可用日志(Journaling )漫蛔。 通過記錄對本地狀態(tài)的改變嗜愈,本地計算過程能達(dá)到容錯的目的旧蛾。如果它失敗的話,另一個進程可以重新加載這些更改并繼續(xù)執(zhí)行蠕嫁。一個具體的例子是在流查詢系統(tǒng)中處理計數(shù)锨天,聚合和其他分組處理。

這些用例中剃毒,每種場景都需要處理變化的實時反饋數(shù)據(jù)病袄,但是偶爾一臺服務(wù)器崩潰,或者數(shù)據(jù)需要被重新加載赘阀,或者被重新處理益缠,都需要完全加載。

一般的想法很簡單基公,如果我們需要保留無限多的日志幅慌,我們記錄上面這些場景每次變更,然后轰豆,我們捕獲系統(tǒng)從一開始的每次狀態(tài)胰伍。用這些完整的日志,我們通過應(yīng)用日志中前N條能恢復(fù)到任意點酸休。這樣假象的完整的日志骂租,對系統(tǒng)來說不是很符合實際,對于一個穩(wěn)定的數(shù)據(jù)集來說雨席,多次更新一條記錄導(dǎo)致日志增長而不受限制菩咨。簡單的日志保留機制將拋棄舊的更新吠式,從而限制空間陡厘。但是日志不再能恢復(fù)當(dāng)前狀態(tài),因為現(xiàn)在從日志的開始位置恢復(fù)特占,可能不再能重新創(chuàng)建當(dāng)前的狀態(tài)糙置。

日志壓縮是一個對于每條記錄來說更細(xì)粒度的保留機制,而不是基于時間的粗粒度的保留機制是目。這種選擇刪除記錄的想法谤饭,讓我們保留每個KEY的最近更新,日志能保證至少有每個KEY的最新狀態(tài)懊纳。

保留策略能針對每個topic設(shè)置揉抵,因此單個集群中,一些topic的保留策略是尺寸或者時間嗤疯,而其他topic的保留策略可以是日志壓縮冤今。

這個功能的靈感來自一個LinkedIn的最古老,最成功的基礎(chǔ)架構(gòu)茂缚,一個叫做databus的數(shù)據(jù)庫變更日志緩存服務(wù)戏罢。不像許多日志結(jié)構(gòu)存儲系統(tǒng)系統(tǒng)屋谭,kafka為了訂閱和組織數(shù)據(jù)而生,以便能更快速的線性讀和寫龟糕。也不像databus桐磁,kafka扮演了一個真實的存儲,所以即使在上游數(shù)據(jù)源無法重放的情況下它也很有用讲岁。

基本概念

下面是一張圖片我擂,顯示了Kafka日志的邏輯結(jié)構(gòu)以及每條消息的偏移量:


log cleaner anatomy

日志頭部和傳統(tǒng)的kafka日志是相同的。它有密集且有序的offset缓艳,并保留所有消息扶踊。日志壓縮提供了一個處理日志尾部的選項。這張圖片展示了一個已壓縮尾部的日志郎任。需要注意的是秧耗,日志尾部的消息保留了它們第一次寫入時的原始的offset,并且這個offset從來不會改變舶治。并且所有offset都是日志中有效的位置分井,即使消息已經(jīng)被壓縮處理過的offset。例如霉猛,如上圖所示尺锚,36,37,38這三個offset是完全等價的位置,在這3個offset上的讀都會返回offset為38位置的消息(即使36和37兩個offset指定的日志已經(jīng)被刪除)惜浅。

壓縮在后臺完成瘫辩,通過周期性的重新復(fù)制日志段。清理不會阻塞讀坛悉,并且為了避免影響生產(chǎn)者和消費者伐厌,可以限制使用不超過配置的I / O吞吐量。

kafka壓縮一個日志段的實際過程更像這樣:


log coompaction

日志壓縮提供什么保證?

日志壓縮提供如下保證:

  1. 任何消費者都會看到所寫的每條消息裸影,這些消息將具有連續(xù)的偏移量挣轨。 topic的參數(shù)min.compaction.lag.ms可用于保證消息寫入后必須經(jīng)過的最小時間才能被壓縮。即 它提供了每條消息保留不壓縮狀態(tài)的時間下限轩猩。
  2. 消息順序總是不變卷扮,壓縮不會重新對消息排序,只是刪除一些消息均践。
  3. 消息的offset不會改變晤锹,offset永遠(yuǎn)是日志中位置的唯一標(biāo)識符。

日志壓縮細(xì)節(jié)

日志壓縮被一個日志清理程序控制彤委,它是一個后臺線程池鞭铆。它會重新拷貝日志段文件,并刪除那么KEY已經(jīng)在日志頭部中出現(xiàn)的記錄葫慎。每個壓縮線程按照如下方式工作:

  1. 選擇日志頭與日志尾比率最高的日志衔彻;
  2. 創(chuàng)建一個簡單的摘要薇宠,日志頭部中的每個KEY的最后一個偏移量。
  3. 從頭到尾重新拷貝日志艰额,并刪除那些在日志更后面的地方出現(xiàn)過的KEY澄港。新的,干凈的段被立即交換到日志中柄沮,因此所需的額外磁盤空間只是一個額外的日志段回梧,而不是日志的完整副本。
  4. 日志頭概要實際上只是一個空間緊湊的hash表祖搓,每個entry恰好用24個字節(jié)狱意,這樣做的結(jié)果就是一個8G的清理緩沖區(qū),一次迭代清理大概能清理366G的日志頭(假設(shè)每個消息是1K)拯欧。

配置日志清理

日志清理默認(rèn)被開啟详囤,這將開啟一個清理線程池。為了在一個特定的topic上打開日志清理镐作,你可以增加一些屬性:

  • log.cleanup.policy=compact

配置壓縮(compact)日志的清理策略藏姐,還可以配置刪除(delete)日志的清理策略,delete是默認(rèn)策略该贾;

  • log.cleaner.min.compaction.lag.ms=5000

其含義是羔杨,消息在日志中保持不壓縮的最短時間,僅適用于正在被壓縮的日志杨蛋。如果沒有配置兜材,則除了最新的日志段(當(dāng)前正被寫入,即活動段)逞力,其他所有日志段都可以壓縮曙寡。活動段不能被壓縮掏击,即使這個段里所有的日志比參數(shù)log.cleaner.min.compaction.lag.ms配置的更老卵皂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砚亭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殴玛,老刑警劉巖捅膘,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滚粟,居然都是意外死亡寻仗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門凡壤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來署尤,“玉大人耙替,你說我怎么就攤上這事〔芴澹” “怎么了俗扇?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長箕别。 經(jīng)常有香客問我铜幽,道長,這世上最難降的妖魔是什么串稀? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任除抛,我火速辦了婚禮,結(jié)果婚禮上母截,老公的妹妹穿的比我還像新娘到忽。我一直安慰自己,他們只是感情好清寇,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布绘趋。 她就那樣靜靜地躺著,像睡著了一般颗管。 火紅的嫁衣襯著肌膚如雪陷遮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天垦江,我揣著相機與錄音帽馋,去河邊找鬼。 笑死比吭,一個胖子當(dāng)著我的面吹牛绽族,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衩藤,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吧慢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赏表?” 一聲冷哼從身側(cè)響起检诗,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓢剿,沒想到半個月后逢慌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡间狂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年攻泼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡忙菠,死狀恐怖何鸡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牛欢,我是刑警寧澤骡男,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站氢惋,受9級特大地震影響洞翩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焰望,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一骚亿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧熊赖,春花似錦来屠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至传趾,卻和暖如春迎膜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浆兰。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工磕仅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人簸呈。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓榕订,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜕便。 傳聞我的和親對象是個殘疾皇子劫恒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容