對於一個複雜的分佈式系統,若是沒有豐富的經驗和牛逼的架構能力,很難把系統作得簡單易維護,咱們都知道,一個軟件的生命週期中,後期維護佔了70%,因此係統的可維護性是極其重要的, kafka 能成爲大數據領域的事實標準,很大緣由是由於運維起來很方便簡單,今天咱們來看下 kafka 是怎麼來簡化運維操做的。html
kafka 使用多副原本保證消息不丟失,多副本就涉及到kafka的複製機制,在一個超大規模的集羣中,時不時地這個點磁盤壞了,那個點cpu負載高了,出現各類各樣的問題,多個副本之間的複製,若是想徹底自動化容錯,就要作一些考量和取捨了。咱們舉個例子說明下運維中面對的複雜性,咱們都知道 kafka 有個 ISR集合,我先說明下這個概念:java
kafka不是徹底同步,也不是徹底異步,是一種ISR機制: 算法
1. leader會維護一個與其基本保持同步的Replica列表,該列表稱爲ISR(in-sync Replica),每一個Partition都會有一個ISR,並且是由leader動態維護 apache
2. 若是一個follower比一個leader落後太多,或者超過必定時間未發起數據複製請求,則leader將其重ISR中移除 架構
3. 當ISR中全部Replica都向Leader發送ACK時,leader才commit,這時候producer才能認爲一個請求中的消息都commit了。運維
在這種機制下, 若是一個 producer 一個請求發送的消息條數太多,致使flower瞬間落後leader太多怎麼辦?若是 follower不停的移入移出 ISR 會不會影響性能?若是對這種狀況加了報警,就有可能形成告警轟炸,若是咱們不加報警,若是是broker 掛掉或者 broker 由於IO性能或者GC問題夯住的狀況致使落後leader太多,這種真正須要報警狀況怎麼辦呢? 今天咱們來看下 kafka 是怎麼在設計上讓咱們徹底避免這種運維中頭疼的問題的。異步
kafka 每一個分區都是由順序追加的不可變的消息序列組成,每條消息都一個惟一的offset 來標記位置。分佈式
kafka中的副本機制是以分區粒度進行復制的,咱們在kafka中建立 topic的時候,均可以設置一個複製因子,這個複製因子決定着分區副本的個數,若是leader 掛掉了,kafka 會把分區主節點failover到其餘副本節點,這樣就能保證這個分區的消息是可用的。leader節點負責接收producer 打過來的消息,其餘副本節點(follower)從主節點上拷貝消息。源碼分析
kakfa 日誌複製算法提供的保證是當一條消息在 producer 端認爲已經 committed的以後,若是leader 節點掛掉了,其餘節點被選舉成爲了 leader 節點後,這條消息一樣是能夠被消費到的。性能
這樣的話,leader 選舉的時候,只能從 ISR集合中選舉,集合中的每一個點都必須是和leader消息同步的,也就是沒有延遲,分區的leader 維護ISR 集合列表,若是某個點落後太多,就從 ISR集合中踢出去。 producer 發送一條消息到leader節點後, 只有當ISR中全部Replica都向Leader發送ACK確認這條消息時,leader才commit,這時候producer才能認爲這條消息commit了,正是由於如此,kafka客戶端的寫性能取決於ISR集合中的最慢的一個broker的接收消息的性能,若是一個點性能太差,就必須儘快的識別出來,而後從ISR集合中踢出去,以避免形成性能問題。kafka 複製機制詳情參考 https://kafka.apache.org/documentation.html#replication
一個副本不能 「caught up」 leader 節點,就有可能被從 ISR集合中踢出去,咱們舉個例子來講明,什麼纔是真正的 「caught up」 —— 跟leader節點消息同步。
kafka 中的一個單分區的 topic — foo,複製因子爲 3 ,分區分佈和 leader 和 follower 以下圖,如今broker 2和3 是 follower 並且都在 ISR 集合中。咱們設置 replica.lag.max.messages 爲4,只要 follower 只要不落後leader 大於3條消息,就而後是跟得上leader的節點,就不會被踢出去, 設置 replica.lag.time.max.ms 爲 500ms, 意味着只要 follower 在每 500ms內發送fetch請求,就不會被認爲已經dead ,不會從ISR集合中踢出去。
如今 producer 發送一條消息,offset 爲3, 這時候 broker 3 發生了 GC, 入下圖:
由於 broker 3 如今在 ISR 集合中, 因此要麼 broker 3 拉取同步上這條 offset 爲3 的消息,要麼 3 被從 ISR集合中踢出去,否則這條消息就不會 committed, 由於 replica.lag.max.messages=4 爲4, broker 3 只落後一條消息,不會從ISR集合中踢出去, broker 3 若是這時候 GC 100ms, GC 結束,而後拉取到 offset 爲3的消息,就再次跟 leader 保持徹底同步,整個過程一直在 ISR集合中,以下圖:
一個副本被踢出 ISR集合的幾種緣由:
一個副本在一段時間內都沒有跟得上 leader 節點,也就是跟leader節點的差距大於 replica.lag.max.messages , 一般狀況是 IO性能跟不上,或者CPU 負載過高,致使 broker 在磁盤上追加消息的速度低於接收leader 消息的速度。
一個 broker 在很長時間內(大於 replica.lag.time.max.ms )都沒有向 leader 發送fetch 請求, 多是由於 broker 發生了 full GC, 或者由於別的緣由掛掉了。
一個新 的 broker 節點,好比同一個 broker id, 磁盤壞掉,新換了一臺機器,或者一個分區 reassign 到一個新的broker 節點上,都會從分區leader 上現存的最老的消息開始同步。
因此說 kafka 0.8 版本後設置了兩個參數 , replica.lag.max.messages 用來識別性能一直很慢的節點, replica.lag.time.max.ms 用來識別卡住的節點。
從上面的狀況來看,兩個參數看似已經足夠了,若是一個副本超過 replica.lag.time.max.ms 尚未發送fetch同步請求, 能夠認爲這個副本節點卡住了,而後踢出去,可是還有一種比較特殊的狀況沒有考慮到,咱們上文中設置 replica.lag.max.messages 爲4,之因此設置爲 4, 是咱們已經知道 producer 每次請求打過來的消息數都在 4 如下,若是咱們的參數是做用於多個 topic 的狀況,那麼這個 producer 最大打過來的消息數目就很差估計了,或者說在常常出現流量抖動的狀況下,就會出現一個什麼狀況呢,咱們仍是使用例子說明:
若是咱們的 topic — foo 的 producer 由於流量抖動打過來一個 包含 4條消息的請求,咱們設置的 replica.lag.max.messages 仍是爲4, 這個時候,全部的 follower 都會由於超出落後條數被踢出 ISR集合:
而後,由於 follower 是正常的,因此下一次 fetch 請求就會又追上 leader, 這時候就會再次加入 ISR 集合,若是常常性的抖動,就會不斷的移入移出ISR集合,會形成使人頭疼的 告警轟炸。
這裏的核心問題是,在海量的 topic 狀況下,或者常常性的流量抖動狀況下,咱們不能對 topic 的producer 每次打過來的消息數目作任何假設,因此就不太好定出來一個 合適的 eplica.lag.max.messages
值
其實只有兩種狀況是異常的,一種就是卡住,另一種是follower 性能慢,若是咱們只根據 follower 落後 leader 多少來判斷是否應該把 follower 提出ISR集合,就必需要對流量進行預測估計,怎麼才能避免這種不靠譜的估計呢,kafka 給出 的方案是這樣的,對 replica.lag.time.max.ms 這個配置的含義作了加強,和以前同樣,若是 follower 卡住超過這個時間不發送fetch請求, 會被踢出ISR集合,新的加強邏輯是,在 follower 落後 leader 超過 eplica.lag.max.messages 條消息的時候,不會立馬踢出ISR 集合,而是持續落後超過 replica.lag.time.max.ms 時間,纔會被踢出,這樣就能避免流量抖動形成的運維問題,由於follower 在下一次fetch的時候就會跟上leader, 這樣就也不用對 topic 的寫入速度作任何的估計嘍。
歡迎學Java和大數據的朋友們加入java架構交流: 855835163 羣內提供免費的架構資料還有:Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的免費直播講解 能夠進來一塊兒學習交流哦