二、訊息的重複消費
三、秒殺系統的簡單設計
思考題在生產階段,訊息佇列通過最常用的請求確認機制,來保證訊息的可靠傳遞。只要 producer 收到了 broker 的確認響應,就可以保證訊息在生產階段不會丟失。有些訊息佇列在長時間沒收到傳送確認響應後,會自動重試,如果重試再失敗,就會以返回值或者異常的方式告知使用者。
需要注意,正確處理返回值或者捕獲異常,就可以保證這個階段的訊息不會丟失。
只要 broker 在正常執行,就不會出現丟失訊息的問題,但是如果 broker 出現了故障,比如程序死掉了或者伺服器宕機了,還是可能會丟失訊息的。
如果對訊息的可靠性要求非常高,可以通過配置 broker 引數來避免因為宕機丟訊息。
消費階段採用和生產階段類似的確認機制來保證訊息的可靠傳遞,客戶端從 broker 拉取訊息後,執行使用者的消費業務邏輯,成功後,才會給 broker 傳送消費確認響應。如果 broker 沒有收到消費確認響應,下次拉訊息的時候還會返回同一條訊息,確保訊息不會在網路傳輸過程中丟失,也不會因為客戶端在執行消費邏輯**錯導致丟失。
如果訊息在網路傳輸過程中傳送錯誤,由於傳送方收不到確認,會通過重發來保證訊息不丟失。但是,如果確認響應在網路傳輸時丟失,也會導致重發訊息。也就是說,無論是 broker 還是 consumer 都是有可能收到重複訊息的。
在 mqtt ( message queuing telemetry transport ) 協議中,給出了三種傳遞訊息時能夠提供的服務質量標準,這三種服務質量從低到高依次是:
rocketmq、rabbitmq 和 kafka 都是 at least once。
用冪等性解決重複訊息問題
一般解決重複訊息的辦法是,在消費端,讓我們消費訊息的操作具備冪等性。但是,不是所有的業務都能設計成天然冪等的,這裡就需要一些方法和技巧來實現冪等。
將賬戶 x 的餘額加 100 元。可以通過改造業務邏輯,讓它具備冪等性。
首先,我們可以限定,對於每個轉賬單每個賬戶只可以執行一次變更操作,在分布式系統中,這個限制實現的方法非常多,最簡單的是我們在資料庫中建一張轉賬流水表,這個表有三個字段:轉賬單 id、賬戶 id 和變更金額,然後給轉賬單 id 和賬戶 id 這兩個字段聯合起來建立乙個唯一約束,這樣對於相同的轉賬單 id 和賬戶 id,表裡至多只能存在一條記錄。
這樣,我們消費訊息的邏輯可以變為:「在轉賬流水表中增加一條轉賬記錄,然後再根據轉賬記錄,非同步操作更新使用者餘額即可。」
只要是支援類似「insert if not exist」語義的儲存類系統都可以用於實現冪等,比如,你可以用 redis 的 setnx 命令來替代資料庫中的唯一約束,來實現冪等消費。
將賬戶 x 的餘額增加 100 元,加上乙個前置條件,變為:如果賬戶 x 當前的餘額為 500 元,將餘額加 100 元,這個操作就具備了冪等性。
對應到訊息佇列中的使用時,可以在發訊息時在訊息體中帶上當前的餘額,在消費的時候進行判斷資料庫中,當前餘額是否與訊息中的餘額相等,只有相等才執行變更操作。
更加通用的方法是,給你的資料增加乙個版本號屬性,每次更資料前,比較當前資料的版本號是否和訊息中的版本號一致,如果不一致就拒絕更新資料,更新資料的同時將版本號 +1,一樣可以實現冪等更新。
還有一種通用性最強,適用範圍最廣的實現冪等性方法:記錄並檢查操作,也稱為「token 機制或者 guid(全域性唯一 id)機制」,實現的思路特別簡單:在執行資料更新操作之前,先檢查一下是否執行過這個更新操作。
具體的實現方法是,在傳送訊息時,給每條訊息指定乙個全域性唯一的 id,消費時,先根據這個 id 檢查這條訊息是否有被消費過,如果沒有消費過,才更新資料,然後將消費狀態置為已消費。
在「檢查消費狀態,然後更新資料並且設定消費狀態」中,三個操作必須作為一組操作保證原子性,才能真正實現冪等,否則就會出現 bug。
在分布式系統中,無論是用分布式事務還是分布式鎖都是比較麻煩的。不推薦這種方法。
// 查詢秒殺結果
result result = results.
remove
(uuid)
;// 檢查秒殺結果並返回響應
if(null != result && result.
success()
)}catch
(throwable ignored)
finally
// 返回秒殺失敗
return response.
fail()
;}// 在這裡處理後端服務返回的秒殺結果
public
void
onresult
(result result)}}
}1、為什麼大部分訊息佇列都選擇只提供 at least once 的服務質量,而不是級別更高的 exactly once 呢?
答:訊息佇列即使做到了exactly once級別,consumer也還是要做冪等。因為在consumer從訊息佇列取訊息這裡,如果consumer消費成功,但是ack失敗,consumer還是會取到重複的訊息。為了確保訊息沒有被丟失或者重複,佇列需採取一定的類似回查的手段,檢測消費者是否有收到訊息進行處理。會導致效能下降和複雜度上公升。
Akka學習筆記(六) 訊息傳遞可靠性
關於訊息傳送,有兩條基本規則 問題是,我們要保證訊息傳遞在什麼環節可靠 訊息已經發到網路上了?訊息被遠端主機接收到了?訊息已經在接收者actor的郵箱裡了?目標actor是否能處理這個訊息?訊息在目標actor上開始處理了?訊息在目標actor上已經處理完畢了?上面每一條都有不同的挑戰和成本。為什麼...
Akka學習筆記 Actor訊息傳遞 2
文章目錄 hide 3 teacher actor 我們在前面僅僅討論了actorref的quoterequest,並沒有看到message的類!這裡將介紹,如下 1packageme.rerun.akkanotes.messaging.protocols 2 3objectteacherproto...
uc os iii學習筆記 訊息傳遞
訊息佇列 如圖所示,訊息通過os msg q結構體組成乙個訊息佇列,其中inptr指標指向下乙個訊息插入的位置,outptr指標指向下乙個將要取出的訊息的位置。在os msg結構是訊息的結構體,其中msgsize顧名思義就是訊息的長度引數,msgts就是訊息的時間戳,而msgptr指標指向的就是資料...