對於一個複雜的分佈式系統,如果沒有豐富的經驗和牛逼的架構能力,很難把系統做得簡單易維護,我們都知道,一個軟件的生命週期中,後期維護佔了70%,所以系統的可維護性是極其重要的, kafka 能成爲大數據領域的事實標準,很大原因是因爲運維起來很方便簡單,今天我們來看下 kafka 是怎麼來簡化運維操作的。
kafka 使用多副本來保證消息不丟失,多副本就涉及到kafka的複製機制,在一個超大規模的集羣中,時不時地這個點磁盤壞了,那個點cpu負載高了,出現各種各樣的問題,多個副本之間的複製,如果想完全自動化容錯,就要做一些考量和取捨了。我們舉個例子說明下運維中面對的複雜性,我們都知道 kafka 有個 ISR集合,我先說明下這個概念:
kafka不是完全同步,也不是完全異步,是一種ISR機制:
1. leader會維護一個與其基本保持同步的Replica列表,該列表稱爲ISR(in-sync Replica),每個Partition都會有一個ISR,而且是由leader動態維護
2. 如果一個follower比一個leader落後太多,或者超過一定時間未發起數據複製請求,則leader將其重ISR中移除
3. 當ISR中所有Replica都向Leader發送ACK時,leader才commit,這時候producer才能認爲一個請求中的消息都commit了。
如果你對技術提升很感興趣,可以加入Java高級技術來交流學習:856443934,裏面都是同行,有資源分享和技術進階思維導圖,其中:(分佈式架構、高可擴展、高性能、高並 發、Jvm性能調優、Spring,MyBatis,Nginx源碼分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql 、Zookeeper、Tomcat、Docker、Dubbo、Nginx)。歡迎一到五年的工程師加入,合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!
在這種機制下,如果一個 producer 一個請求發送的消息條數太多,導致flower瞬間落後leader太多怎麼辦?如果 follower不停的移入移出 ISR 會不會影響性能?如果對這種情況加了報警,就有可能造成告警轟炸,如果我們不加報警,如果是broker 掛掉或者 broker 因爲IO性能或者GC問題夯住的情況導致落後leader太多,這種真正需要報警情況怎麼辦呢?今天我們來看下 kafka 是怎麼在設計上讓我們完全避免這種運維中頭疼的問題的。
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
一個副本怎麼纔算是跟得上leader的副本
一個副本不能 “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集合中踢出去
一個副本被踢出 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
值
一個配置全部搞定
如果你對技術提升很感興趣,可以加入Java高級技術來交流學習:856443934,裏面都是同行,有資源分享和技術進階思維導圖,其中:(分佈式架構、高可擴展、高性能、高並 發、Jvm性能調優、Spring,MyBatis,Nginx源碼分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql 、Zookeeper、Tomcat、Docker、Dubbo、Nginx)。歡迎一到五年的工程師加入,合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!
其實只有兩種情況是異常的,一種就是卡住,另外一種是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 的寫入速度做任何的估計嘍。