訊息冪等(去重)通用解決方案
訊息中介軟體是分散式系統常用的元件,無論是非同步化、解耦、削峰等都有廣泛的應用價值。我們通常會認為,訊息中介軟體是一個可靠的元件——這裡所謂的可靠是指,只要我把訊息成功投遞到了訊息中介軟體,訊息就不會丟失,即訊息肯定會至少保證訊息能被消費者成功消費一次,這是訊息中介軟體最基本的特性之一。
一個訊息M傳送到了訊息中介軟體,訊息投遞到了消費程式A,A接受到了訊息,然後進行消費,但在消費到一半的時候程式重啟了,這時候這個訊息並沒有標記為消費成功,這個訊息還會繼續投遞給這個消費者,直到其消費成功了,訊息中介軟體才會停止投遞。
然而這種可靠的特性導致,訊息可能被多次地投遞。舉個例子,還是剛剛這個例子,程式A接受到這個訊息M並完成消費邏輯之後,正想通知訊息中介軟體“我已經消費成功了”的時候,程式就重啟了,那麼對於訊息中介軟體來說,這個訊息並沒有成功消費過,所以他還會繼續投遞。這時候對於應用程式A來說,看起來就是這個訊息明明消費成功了,但是訊息中介軟體還在重複投遞。
這在RockectMQ的場景來看,就是同一個messageId的訊息重複投遞下來了。
簡單的訊息去重解決方案
例如:假設我們業務的訊息消費邏輯是:插入某張訂單表的資料,然後更新庫存:
insertintot_ordervalues.....
updatet_invsetcount=count-1wheregood_id='good123';
select*fromt_orderwhereorder_no='order123'
if(order!=null){
return;//訊息重複,直接返回
}
併發重複訊息
併發去重的解決方案之一
要解決上面併發場景下的訊息冪等問題,一個可取的方案是開啟事務把select 改成 select for update語句,把記錄進行鎖定。
select*fromt_orderwhereorder_no='THIS_ORDER_NO'forupdate//開啟事務if(order.status!=null){
return;//訊息重複,直接返回
}
Exactly Once(在訊息中介軟體裡,有一個投遞語義的概念,而這個語義裡有一個叫”Exactly Once”,即訊息肯定會被成功消費,並且只會被消費一次)
基於關係資料庫事務插入訊息表
假設我們業務的訊息消費邏輯是:更新MySQL資料庫的某張訂單表的狀態:
updatet_ordersetstatus='SUCCESS'whereorder_no='order123';
要實現Exaclty Once即這個訊息只被消費一次(並且肯定要保證能消費一次),我們可以這樣做:在這個資料庫中增加一個訊息消費記錄表,把訊息插入到這個表,並且把原來的訂單更新和這個插入的動作放到同一個事務中一起提交,就能保證訊息只會被消費一遍了。
- 開啟事務
- 插入訊息表(處理好主鍵衝突的問題)
- 更新訂單表(原消費邏輯)
- 提交事務