使用消息隊列解決分佈式事務一致性問題

如何可靠保存憑證(消息)

  有兩種方法:

業務與消息耦合的方式

  支付寶在完成扣款的同時,同時記錄消息數據,這個消息數據與業務數據保存在同一數據庫實例裏(消息記錄表表名爲message);

1
2
3
4
5
Begin transaction
         update set amount=amount-10000 where userId=1;
         insert into message(userId, amount,status) values(1, 10000, 1);
End transaction
commit;

  上述事務能保證只要支付寶賬戶裏被扣了錢,消息一定能保存下來。

  當上述事務提交成功後,我們通過實時消息服務將此消息通知餘額寶,餘額寶處理成功後發送回覆成功消息,支付寶收到回覆後刪除該條消息數據。

業務與消息解耦方式

  上述保存消息的方式使得消息數據和業務數據緊耦合在一起,從架構上看不夠優雅,而且容易誘發其他問題。爲了解耦,可以採用以下方式。

  1)支付寶在扣款事務提交之前,向實時消息服務請求發送消息,實時消息服務只記錄消息數據,而不真正發送,只有消息發送成功後纔會提交事務;

  2)當支付寶扣款事務被提交成功後,向實時消息服務確認發送。只有在得到確認發送指令後,實時消息服務才真正發送該消息;

  3)當支付寶扣款事務提交失敗回滾後,向實時消息服務取消發送。在得到取消發送指令後,該消息將不會被髮送;

  4)對於那些未確認的消息或者取消的消息,需要有一個消息狀態確認系統定時去支付寶系統查詢這個消息的狀態並進行更新。爲什麼需要這一步驟,舉個例子:假設在第2步支付寶扣款事務被成功提交後,系統掛了,此時消息狀態並未被更新爲“確認發送”,從而導致消息不能被髮送。

  優點:消息數據獨立存儲,降低業務系統與消息系統間的耦合;

  缺點:一次消息發送需要兩次請求;業務處理服務需要實現消息狀態回查接口。

如何解決消息重複投遞的問題

  還有一個很嚴重的問題就是消息重複投遞,以我們支付寶轉賬到餘額寶爲例,如果相同的消息被重複投遞兩次,那麼我們餘額寶賬戶將會增加2萬而不是1萬了。

  爲什麼相同的消息會被重複投遞?比如餘額寶處理完消息msg後,發送了處理成功的消息給支付寶,正常情況下支付寶應該要刪除消息msg,但如果支付寶這時候悲劇的掛了,重啓後一看消息msg還在,就會繼續發送消息msg。

  解決方法很簡單,在餘額寶這邊增加消息應用狀態表(message_apply),通俗來說就是個賬本,用於記錄消息的消費情況,每次來一個消息,在真正執行之前,先去消息應用狀態表中查詢一遍,如果找到說明是重複消息,丟棄即可,如果沒找到才執行,同時插入到消息應用狀態表(同一事務)。

1
2
3
4
5
6
7
8
for each msg in queue
  Begin transaction
    select count(*) as cnt from message_apply where msg_id=msg.msg_id;
    if cnt==0 then
      update set amount=amount+10000 where userId=1;
      insert into message_apply(msg_id) values(msg.msg_id);
  End transaction
  commit;

  Ebay的研發人員早在2008年就提出了應用消息狀態確認表來解決消息重複投遞的問題:http://queue.acm.org/detail.cfm?id=1394128

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