場景
大家經常遇到這樣的需求,尤其是支付中心接口的時候:
查詢滿足某種條件的訂單,調用第三方接口成功,更改訂單狀態。
常見實現示例
task1:
orders = queyrOrder(...); //查詢已離店的訂單 for (Map<String, Object> order : orders) { try { con.setAutoCommit(false); String orderNo = (String) order.get("order_no"); // 調用支付中心返現接口 callCommission(order); //無論是網絡異常還是返回結果失敗,都拋異常 // 更改訂單狀態 cashedOrder(con, order); //更改訂單狀態 con.commit(); }
task 2
orders = queyrOrder(...);//查詢“返現中”的訂單 for (Map<String, Object> order : orders) { con.setAutoCommit(false); // 調用返現任務 callCommission(order); // 更改訂單狀態 cashedOrder(con, order); con.commit(); }
示例的問題
- 在事務中存在遠程調用,容易導致事務時間過長
- 在callCommission 和 cashedOrder之間,是否有其他操作能改變該訂單狀態? 如果有,是否會出現多次調用第三方的問題?
- 第三方接口特性未知
設計方案
往兩個方面思考
- 自己系統本身數據一致性
- 如果調用第三方接口 和 更新自己系統數據 之間任何一個環節 出異常了。比如:應用重啟,機房網絡問題等。如何保證第三方接口和自己系統的數據一致性?
方案
- 加鎖,方案有:版本號樂觀鎖、select for update 悲觀鎖。
延伸問題:在原表上執行task,還是隊列表? - 第三方接口需要什么特性才能保證數據一致性? 要考慮網絡超時問題、應用發布重啟、並發等問題。
- 回調
- 提供結果查詢
- 冪等性
重構后的示例1:
(冪等接口的方案)
queueOrders = queryQueueOrders(); for(QueueOrder order : queueOrders){ callApi() startTransaction(); lock(order); updateStatus() commitTransaction(); }
(提供結果查詢的API接口的方案)
queueOrders = queryQueueOrders(); for(QueueOrder order : queueOrders){ try{ startTransaction(); if(isBeingProcess(order)){ Result r = queryAPIResult(); if(r.success()){ return; } } lock(order); callApi() updateStatus(); }finally{ commitTransaction(); } }
注意:該示例依然有“在事務中調用遠程接口的問題”