的方案去實現的,這樣會有什么問題呢,假設如果放到一個實例里面,全部用一個單機事務去解決,這樣是能比較方便的解決數據一致性問題。但是存在兩個問題,一是無法進行多實例部署,用戶量增長以后,無法快速應對。二是,PHP中做事務,如果PHP遇到異常,有時並不會自動終止事務,導致DB被鎖住,這是第一個版本。之后,我們推出了第二個版本V2,這個版本的時候,我們已經開發好了,庫存管理系統,優惠券管理系統,PHP中,已經不直接通過DB去修改庫存和優惠券,而是通過接口訪問的方式去請求SERVER進行修改。這個版本,實際上已經從邏輯上,把訂單系統和庫存管理,優惠券管理系統已經獨立出來了。數據層面已經可以獨立部署,不再依賴一個單機事務去實現數據一致性功能了。但這個版本雖然解決了數據分布的問題,但同時引入了一個新的問題,就是數據在訂單,庫存,優惠券之間無法保證一致性。舉個例子:下個訂單,調用庫存成功,鎖定優惠券失敗,生成訂單失敗。這時候就會導致優惠券數據不一致性情況出來,未下單的優惠券也被鎖住了。有同事可能會問:訂單如果創建失敗,那直接回滾優惠券操作,即去解鎖優惠券系統即可實現數據一致性。不錯,很多時候,是可以這么操作,但如果你回滾的時候,失敗了呢?你是繼續在這等着直到成功,還是繼續等着?呵呵。。
正是因為這樣,我們開發了V3版本,去解決這個問題。
V3版本,我們把訂單系統的邏輯從PHP中抽離出來,為什么盡量不在PHP里面做這塊邏輯呢?主要有兩個考慮點:1、因為訂單服務這塊邏輯特別重要,是影響用戶操作的重要邏輯,且變動少,寫成一個SERVER相對容易保持穩定。2、PHP使用CI框架做事務的時候,如果事務中出現異常,可能導致事務不結束,一直死鎖的問題。
訂單系統的邏輯架構大致如下:


訂單系統中,統一通過接口調用,去訪問庫存管理,優惠券系統,通過mysql提供的事務機制去操作數據庫部分。這里有一個前提條件,即是庫存管理與優惠券系統的接口均要實現可重入的特性(可參考上一篇文章“如何實現可重入接口”)。另外,還要引入一個差錯控制服務,用於做一些數據不一致的事后補嘗機制。差錯控制可以理解為一個消息隊列機制,還有一個消費者服務從隊列中取出消息進行消費。我們這里采用阿里雲的ONS服務做為消息隊列,通過一個消費者去訂單消息進行消費。
生成訂單的邏輯如下:
1、先把生成的訂單號發到差錯控制服務中。(這里必須要有個延時處理的機制,延時給消費者消費消息,因為要確保后面的流程有個結果,可以延時5分鍾以上)
2、使用訂單號作為庫存單號去操作庫存管理系統。
A)如果失敗,則使用相同訂單號去進行回滾請求操作。(這里不論成功失敗,均返回失敗,結束流程)
B)如果成功,繼續往下執行。
3、使用訂單號去鎖定優惠券系統。
A)如果失敗,嘗試庫存回滾操作,嘗試執行解鎖操作。(這里不論解鎖成功失敗,均返回失敗,結束流程)
B)如果成功,繼續往下執行。
4、開啟事務,創建訂單相關數據。
A)如果創建失敗,回滾事務,調用庫存回滾操作,調用優惠券解鎖操作。(不論調用成功與否,均返回失敗,結束流程)
B)如果創建成功,提交事務,返回成功。
大概流程如上所述。
另外,差錯控制服務,這里也大概描述一下其工作流程。
1、去訂單庫中查看該訂單是否已經生成,如果已經生成,說明數據全部一致,無須做任何操作,直接消費此消息。
2、如果發現訂單未創建,則其中可能是其中某個環節失敗了。
A)使用該訂單號去調用庫存回滾操作。如果失敗,結束流程,返回稍后重新消費,等待消息隊列重試推過來。
B)如果成功,繼續往下執行,調用優惠券系統進行解瑣優惠券。如果失敗,則返回稍后重新消費,等待消息隊列重推消息。如果成功,則消費此消息。
大致思路是通過一個差錯補嘗機制,非實時的自動進行數據一致性修復的方法,來保證絕大多數情況下的數據一致性。