昨晚某技術羣裏你們熱火的在討論分佈式事務的問題,想起了本身前幾年因爲技術太渣也犯過不少相關錯誤,現結合本身以前一次BUG案例由感而寫此文,但願對看到文章的同窗們多少有些幫助(若是發現錯誤之處,歡迎交流)。mysql
一個註冊業務,用戶註冊成功後,後臺調用另一個服務同步完成開通資金帳戶,後來加了一個需求同時還要把註冊用戶數據同步到另外一個業務系統中。sql
真實狀況邏輯更復雜,如今簡化方便描述後相關僞代碼以下:服務器
@Transactional public void register(){ //保存用戶 saveUser(); //初始化帳戶 initAccount(); //推送用戶信息 pushUser(); }
後面兩個方法能夠理解成是其餘業務系統的遠程服務;異步
代碼上線後一段時間內倒也平穩沒出什麼問題(用戶數少),然而是bug遲早會復現的,最終在一個週末問題觸發了,接到領導通知,網站不能註冊,趕忙連上服務器看日誌,發現日誌中大量超時及mysql死鎖異常,而後瞬間明白了問題緣由,畢竟鍋是本身引發的。分佈式
pushUser()裏面是一個請求其餘業務系統的HTTP接口,此處當時沒加connectTimeout超時限制,那天此業務系統接口異常,請求了60多秒還沒響應,這個總體方法上還有一個大事務,接着就形成了大量死鎖,再以後網站就不能註冊。優化
上面的這個示例能夠說是分佈式事務中常見的一類問題;一個業務的完成,要依賴於其餘項目的多個遠程服務;但上面的那種寫法明顯問題很大,極易引起各類BUG,至少存在如下問題:網站
事務範圍過大spa
註冊業務嚴重耦合其餘業務系統接口日誌
數據不一致性問題code
如今回想起來,當時果真是不知者無畏,啥代碼都敢寫。
如今根據目前的認知水平針對上面問題,從新提供一個優化思路,使用MQ來解耦下面兩個遠程服務。
用戶信息保存成功後,插一條記錄到user_task_record表(user_id,account_flag,push_flag,create_time,update_time等字段,兩個狀態字段初始值默認爲0);這兩個操做在同一個事務內;
扔一條消息到MQ中;
消費者接受到廣播消息後分別再處理initAccount、pushUser邏輯;相應消費者接收到消息處理成功後,修改user_task_record表對應狀態字段爲1;
失敗重試機制,由於接口可能會有調用失敗的狀況,新增一個5分鐘一次的定時任務,掃user_task_record表狀態爲0的記錄,扔消息到MQ中;
若是業務重要還能夠加入監控預警,設定一個閥值,若是發現user_task_record表中create_time大於閥值而且狀態一直是0的記錄,能夠給相關人員短信,郵件預警。
注意:因爲有失敗重試機制,因此業務系統的相關接口必須是冪等的(冪等很重要),即我能夠調用屢次,不會產生重複數據。在本例中咱們的消費者接受到消息後,能夠先從user_task表中查取下對應狀態是否爲1,若是是1,說明業務邏輯已經執行成功,只用確認下消息扔給下一個消費者處理;不等於1的就執行相應業務邏輯。
相關僞代碼以下:
public void register(){ //保存用戶 @Transactional saveUser(); //生成一條消息 producer.send(message); }
簡易流程圖以下:
安利下mq,mq在實際開發中能幫咱們不少忙:
在傳統的事務處理中,多個系統之間的交互耦合到一個事務中,響應時間長,影響系統可用性。引入分佈式事務消息,業務系統和消息隊列之間,組成一個事務處理,能保證分佈式系統之間數據的最終一致;下游業務系統(訂單交易、購物車、積分、其餘)相互隔離,並行處理。
常見使用場景:
異步解耦
削峯填谷
異步通知
分佈式事務處理
......
最後總結:
咱們把2個同步遠程調用方法改爲異步了
事務範圍變小了
引入MQ,把業務解耦了,保證了分佈式系統事務的最終一致性
項目更高可用了,不會由於其餘業務系統引發項目宕機不可用