Kafka如何保證消息不丟失不重複

首先需要思考下邊幾個問題:

消息丟失是什麼造成的,從生產端和消費端兩個角度來考慮

消息重複是什麼造成的,從生產端和消費端兩個角度來考慮

如何保證消息有序

如果保證消息不重不漏,損失的是什麼

大概總結下

消費端重複消費:建立去重表

消費端丟失數據:關閉自動提交offset,處理完之後受到移位

生產端重複發送:這個不重要,消費端消費之前從去重表中判重就可以

生產端丟失數據:這個是最麻煩的情況

解決策略:

1、異步方式緩衝區滿了,就阻塞在那,等着緩衝區可用,不能清空緩衝區

2、發送消息之後回調函數,發送成功就發送下一條,發送失敗就記在日誌裏,等着定時腳本來掃描

(發送失敗可能並不真的發送失敗,只是沒收到反饋,定時腳本可能會重發)

如何保證有序:

如果一個發送失敗了,後邊的就不能繼續發了,不然重發的那個肯定就亂序了

生產者在收到發送成功的反饋之前,不能發下一條數據,但我感覺生產者是一個流,阻塞正產者感覺業務不可行,怎麼會因爲一條消息發出去沒收到反饋,就阻塞生產者

同步發送模式:發出消息後,必須阻塞等待收到通知後,才發送下一條消息

異步發送模式:一直往緩衝區寫,然後一把寫到隊列中去

兩者都是各有利弊:
同步發送模式雖然吞吐量小,但是發一條收到確認後再發下一條,既能保證不丟失消息,又能保證順序

Kafka消息保證生產的消息不丟失和不重複消費的問題

1、使用同步模式的時候 ,有3種狀態保證消息被安全生產,在配置爲1(只保證寫入leader成功的話),如果剛好leader partition掛了,數據就會丟失

2、使用異步模式的時候,當緩存區滿了,如果配置爲0(還沒收到確認的情況下,緩衝池一滿,就清空緩衝池裏的消息),數據就會被立馬丟棄掉

在數據生產時避免數據丟失的方法:

只要能避免上述兩種情況,那麼就可以保證消息不會被丟失。

1、在同步模式的時候,確認機制設置爲-1,也就是讓消息寫入leader和所有的副本。

2、在異步模式的時候,在消息發送出去了,但還沒收到確認的時候,緩衝池滿了,在配置文件中設置成不限制阻塞超時時間,也就是生產端一直阻塞着,這樣也能保證消息不丟失。

在數據消費時,避免數據丟失的方法:如果使用了strom,要開啓strom的ackfail機制,如果沒有使用strom,確實數據被完成處理時,再更新offset值,低級API中需要手動控制offset值。

消息隊列的問題都要從源頭找問題,就是生產者是否有問題。

如果數據發送成功,但是接受response的時候丟失了,機器重啓之後就會重發。

重發很好解決,消費端增加去重表就能解決,但是如果生產者丟失了數據,問題就很麻煩了。

數據重複消費的情況,如果處理

1、去重:將消息的唯一標識保存到外部介質中,每次消費處理時判斷是否處理過

2、不管:大數據場景中,報表系統或者日誌信息丟失幾條都無所謂,不會影響最終的統計分析結果。

Kafka到底會不會丟數據?通常不會,但有些情況下的確有可能會發生,下面的參數配置及Best practice列表可以較好的保證數據的持久性(當然是trade-off,犧牲了吞吐量)

block.on.buffer.full = true
acks = all
retries = MAX_VALUE
max.in.flight.requests.per.connection = 1
使用KafkaProducer.send(record, callback)
callback邏輯中顯式關閉producer:close(0) 
unclean.leader.election.enable=false
replication.factor = 3 
min.insync.replicas = 2
replication.factor > min.insync.replicas
enable.auto.commit=false
消息處理完成之後再提交位移
給出列表之後,我們從兩個方面來探討一下數據爲什麼會丟失:

1、Producer端

      目前比較新版本的Kafka正式替換了Scala版本的old producer,使用了由java重寫的producer,新版本的producer採用異步發送機制,KafkaProducer.send(ProducerRecord)方法僅僅是把這條消息放入一個緩存中(即RecordAccumulator,本質上使用隊列來緩存記錄),同時後臺的IO線程會不斷掃描該緩存區,將滿足條件的消息封裝到某個batch中然後發送出去,顯然,這個過程中就有一個數據丟失的窗口:若IO線程發送之前client端掛掉了,累積在accumulator中的數據的確有可能會丟失。

     Producer的另一個問題是消息亂序問題,假設客戶端代碼依次執行下面的語句將兩條消息發到相同的分區

producer.send(record1);

producer.send(record2);

如果此時由於某些原因(比如瞬間的網絡抖動)導致record1沒有成功發送,同時kafka又配置了重試機制和max.in.flight.requests.per.connection大於1(默認值是5,本來就是大於1的),那麼重試record1成功後,record1在分區中就在record2之後,從而造成消息的亂序,很多某些要求強順序保證的場景是不允許出現這種情況的。

發送之後重發就會丟失順序

鑑於producer的這兩個問題,我們應該如何規避呢?對於消息丟失的問題,很容易想到的一個方法就是:既然異步發送有可能丟失數據,我改成同步發送總可以吧?比如這樣:
producer.send(record).get();

這樣當然是可以的,但是性能會很差,不建議這樣使用,因此特意總結了一份配置列表,個人認爲該配置清單應該能夠比較好的規避producer端數據丟失情況的發生:(但說明一下,軟件配置的很多決策都是trade-off,下面的配置也不例外:應用了這些配置,你可能會發現你的producer/consumer吞吐量會下降,這是正常的,因爲你換取了更高的數據安全性)

block.on.buffer.full = true 儘管該參數在0.9.0已經被標識爲“deprecated”,但鑑於它的含義非常直觀,所以這裏還是顯式設置它爲true,使得producer將一直等待緩衝區直至其變爲可用,否則如果producer生產速度過快耗盡了緩衝區,producer將拋出異常,緩衝區滿了就阻塞在那,不要拋異常,也不要丟失數據。

acks =all 所有的folloer都相應了才認爲消息提交成功,即“committed”

retried = Max 無限重試,直到你意識到出現了問題

max.in.flight.requests.per.connection = 1限制客戶端在單個連接上能夠發送的未響應請求的個數,設置此值是1 表示kafka broker在響應請求之前client不能再向同一個broker發送請求,注意:設置此參數是爲了避免消息亂序

使用KafkaProducer.send(record,callback)而不是send(record)方法,自定義回調邏輯處理消息發送失敗,比如記錄在日誌中,用定時腳本掃描重處理callback邏輯中最好顯示關閉producer,close(0)注意:設置此參數是爲了避免消息亂序(僅僅因爲一條消息發送沒收到反饋就關閉生產者,感覺代價很大)

unclean.leader.election.enable = false 關閉unclean leader選舉,即不允許非ISR中的副本被選舉爲leader,以避免數據丟失

replication.factor>=3 這個完全是個人建議,參考了hadoop及業界通用的三備份原則

min.insync.replicas > 1消息至少要被寫入到這麼多副本纔算成功,也是提升數據持久性的一個參數,與acks配合使用保證replication.factor>min.insync.replicas 如果兩者相等,當一個副本掛掉了分區也就沒法正常工作了,通常設置replication.factor = min.insync,replicas + 1即可。

2、Consumer端

     consumer端丟失消息的情形比較簡單:如果在消息處理完成前就提交了offset,那麼就有可能造成數據的丟失,由於kafka consumer默認是自動提交位移的,所以在後臺提交位移前一定要保證消息被正常處理了,因此不建議採用很重的處理邏輯,如果處理耗時很長,則建議把邏輯放到另一個線程中取做,爲了避免數據丟失,現給出兩點建議:

enable.auto.commit = false 關閉自動提交位移

在消息被完整處理之後再手動提交位移

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章