訊息推送平臺有沒有保證資料不丟?

Java3y發表於2023-05-15

我們在使用mq的時候,就會很自然思考一個問題:怎麼保證資料不丟失

現在austin接入層是把訊息發到mq,下發邏輯層從mq消費資料,隨後呼叫對應渠道介面來下發訊息。

訊息推送平臺?推送下發【郵件】【簡訊】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別

訊息丟棄一般我們考慮的是消費端,於是重點看的是下發邏輯層。

(因為對於mq使用方來說:生產端只要配置mq相關的引數,在呼叫下發時有回撥重試機制。那就足夠了,生產端能做的東西確實不多)

目前為止,下發邏輯層(消費端)使用的是自動提交offset策略。只要消費端存在系統重啟或者程式被kill掉,那就會有丟訊息的情況。

spring.kafka.consumer.enable-auto-commit=true

當前下發邏輯層(消費端)有可能放大了這個丟棄訊息的問題,因為現在是消費到mq資料後,會把訊息給到執行緒池去處理。執行緒池會指定一個阻塞佇列,那佇列數量越大,可能由重啟所丟棄的訊息就越多

這裡我的策略是:當應用重啟的時候,系統裡的執行緒池是優雅關閉的(儘可能等待一段時間,等阻塞佇列裡沒有訊息了,再關閉執行緒池)。

但回到問題的本質上,只要消費端是自動提交offset策略,就一定會有丟訊息的問題。所以要做到消費端的訊息不丟,我們就要設定為手動提交offset,這個是必要條件。

有沒有必要保證不丟

在探討具體的技術實現方案之前,我們來看看在業務上有沒有必要保證訊息不丟。我剛接觸到訊息推送平臺的時候,當時那個交接的哥們告訴我和我學長:訊息少發比多發要好

1、重要的訊息使用者很可能會手動重試觸發

austin是一個傳送各類渠道訊息的平臺,從我的經驗來說,這裡面最重要的是簡訊渠道。經過austin下發很可能是登陸驗證碼,銀行卡提現驗證碼,這類訊息從全域性上看是最重要的。

而其他渠道,例如push通知欄的通知訊息,微信渠道的營銷訊息,這種訊息即便使用者沒收到,也不會對使用者帶來很大的使用體驗問題。這種訊息或許對絕大數使用者都是無感知的(少發幾條,使用者可能更樂意)。

我們先假設使用者的某一次銀行卡提現的驗證碼恰好因為我們重啟系統而丟棄。這時候,絕大數使用者可能懷疑自己的訊號問題,會繼續操作,重新傳送一次

(因為客服經常找我排查這種問題,每次都能看到有好幾條下發記錄。當然了,能到技術的,99%的問題都不是由系統重啟丟失訊息導致的,更多可能是使用者的客戶端本身確實就存在問題)

2、訊息是有時效性的。比如驗證碼這種簡訊一般就5min的時效性,由於系統的問題,你超過這個時間給使用者傳送,對使用者的體驗是非常差的。

3、訊息推送平臺是有全鏈路追蹤的,是可以知道下發的訊息有沒有到達到使用者手上,至少都可以知道在我們的系統內部執行過程中有沒有丟。如果這條訊息真的那麼重要,那可以單獨為丟棄的訊息單獨做重發處理,這些功能在訊息推送平臺都是支援的。

這個問題我以前的同事也跟我探討過,就是把上面的內容給我隔壁的老哥聽的,他說:你就盡扯淡吧,到面試的時候人家可不認你,丟了就是丟了,其他都是藉口

我說:沒事,要是不認的話,就把我們處理訂單那一套給他講講嘛,反正處理的思路都是一樣的。

不過啊,廣告訂單邏輯處理又相對沒那麼複雜,廣告訂單最後是以入資料庫作為標準的,又可以接受一定的延遲,只要能保證處理完就行了。

要想client端消費資料不能丟,肯定是不能使用autoCommit的,所以必須是手動提交的。

<span style="color:#000080">候選者</span>:我們這邊是這樣實現的:

<span style="color:#000080">候選者</span>:一、從Kafka拉取訊息(一次批次拉取500條,這裡主要看配置)時

<span style="color:#000080">候選者</span>:二、為每條拉取的訊息分配一個msgId(遞增)

<span style="color:#000080">候選者</span>:三、將msgId存入記憶體佇列(sortSet)中

<span style="color:#000080">候選者</span>:四、使用Map儲存msgId與msg(有offset相關的資訊)的對映關係,透過msgId用來獲取相關元資訊

<span style="color:#000080">候選者</span>:五、當業務處理完訊息後,ack時,獲取當前處理的訊息msgId,然後從sortSet刪除該msgId(此時代表已經處理過了)

<span style="color:#000080">候選者</span>:六、接著與sortSet佇列(本地記憶體佇列)的首部第一個Id比較(其實就是最小的msgId),如果當前msgId<=sort Set第一個ID,則提交當前offset

<span style="color:#000080">候選者</span>:七、系統即便掛了,在下次重啟時就會從sortSet隊首的訊息開始拉取,實現至少處理一次語義

<span style="color:#000080">候選者</span>:八、會有少量的訊息重複,但只要下游做好冪等就OK了。

<span style="color:#ab4642">面試官</span>:嗯,你也提到了冪等,你們這業務怎麼實現冪等性的呢?

<span style="color:#000080">候選者</span>:嗯,還是以處理訂單訊息為例好了。

<span style="color:#000080">候選者</span>:冪等Key我們由訂單編號+訂單狀態所組成(一筆訂單的狀態只會處理一次)

<span style="color:#000080">候選者</span>:在處理之前,我們首先會去查Redis是否存在該Key,如果存在,則說明我們已經處理過了,直接丟掉

<span style="color:#000080">候選者</span>:如果Redis沒處理過,則繼續往下處理,最終的邏輯是將處理過的資料插入到業務DB上,再到最後把冪等Key插入到Redis上

<span style="color:#000080">候選者</span>:顯然,單純透過Redis是無法保證冪等的(:

<span style="color:#000080">候選者</span>:所以,Redis其實只是一個「前置」處理,最終的冪等性是依賴資料庫的唯一Key來保證的(唯一Key實際上也是訂單編號+狀態)

<span style="color:#000080">候選者</span>:總的來說,就是透過Redis做前置處理,DB唯一索引做最終保證來實現冪等性的

保證austin資料不丟需要做什麼?

保證資料不丟簡單來說,就是我們要在消費端手動ack offset,不能再用自動提交策略了。這樣當我們系統重啟時,kafka會自動從未ackoffset中拉取。

如果要實現訊息推送平臺不丟訊息的話,有幾個問題是需要考慮的:

1、訊息少發比多發要好,那麼要實現訊息不丟,就必須要在系統內實現冪等。因為現在的訊息不丟,一般都是基於【至少一次]消費語義去做的。

2、那實現冪等的邏輯是在呼叫渠道下發介面前,還是渠道下發介面後?

如果做在下發介面前,那是不是會有可能第一次下發記錄寫入了,但實際呼叫下發介面卻失敗了,後面的重試都被冪等處理掉了。

如果做在下發介面後,那是不是會有可能呼叫呼叫下發介面成功了,但寫入冪等處理的訊息失敗了,後面的重試就會導致訊息多發

3、訊息是有時效性的,那如果重試的處理時間過長,那是不是要考慮把這條訊息給丟棄掉,不再重試了。

4、重試的訊息不應該影響到正常訊息的下發,他得作為一種補償的機制,而非主流程

稍微細想下技術實現,應該不太好搞,還有很多細節的地方得關注到。比如業務上的:應該是不需要所有的渠道的所有型別訊息都得實現訊息不丟吧?現在的設計是追求高效能的,能在短時間內下發批次的訊息。而如果做到所有訊息不丟,肯定會影響到下發的速率

什麼時候動手?

1、對於這個功能吧,有用肯定是有用,但這功能又沒那麼急

2、我估摸對現有程式碼改動還是蠻大的,現在我還沒想好該怎麼實現比較好,也一直沒下手。

3、最近工作的事挺多的,沒那麼有空

結論:先看看想要這個功能的人多不多,不多就鴿一會。

都看到這了,如果按上面的理由,我不實現這個功能,你認不認可?

訊息推送平臺?推送下發【郵件】【簡訊】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別

相關文章