場景
大家經常遇到這樣的需求,尤其是支付中心接口的時候:
查詢滿足某種條件的訂單,調用第三方接口成功,更改訂單狀態。
常見實現示例
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();
}
}
注意:該示例依然有“在事務中調用遠程接口的問題”
