本文原始碼:GitHub·點這里 || GitEE·點這里
一、冪等性概念
1、冪等簡介
編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同,就是說,一次和多次請求某一個資源會產生同樣的作用影響,
2、HTTP請求
遵循Http協議的請求,越來越強調Rest請求風格,可以更好的規范和理解介面的設計,
GET:用于獲取資源,不應有副作用,所以是冪等的;
POST:用于創建資源,重復提交POST請求可能產生兩個不同的資源,有副作用不滿足冪等性;
PUT:用于更新操作,重復提交PUT請求只會對其URL中指定的資源有副作用,滿足冪等性;
DELETE:用于洗掉資源,有副作用,但它應該滿足冪等性;
HEAD:和GET本質是一樣的,但HEAD不含有呈現資料,僅是HTTP頭資訊,沒有副作用,滿足冪等性;
OPTIONS:用于獲取當前URL所支持的請求方法,滿足冪等性;
二、場景業務分析
1、訂單支付

實際開發中,經常會面對訂單支付問題,基本流程如下:
- 客戶端發起訂單支付請求 ;
- 支付前系統本地相關業務處理 ;
- 請求第三方支付服務執行扣款;
- 第三方支付回傳處理結果;
- 本地服務基于支付結果回應客戶端;
該業務流程中要處理相當復雜的問題,比如事務,分布式事務,介面延遲超時,客戶端重復提交等等,這里只基于冪等介面角度來看該流程,其他問題后續再聊,
2、冪等介面
當上述流程的支付請求有明確結果的時候:失敗或成功,這樣業務流程都好處理,但是例如支付場景如果請求超時,如何判斷服務的結果狀態:客戶端請求超時,本地服務超時,請求支付超時,支付回呼超時,客戶端回應超時等等,
這就需要設計流程化的狀態管理,
3、基礎操作案例
模擬管理上述流程,設計冪等介面:
表結構設計
CREATE TABLE `dp_order_state` (
`order_id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT '訂單id',
`token_id` VARCHAR (50) DEFAULT NULL COMMENT '防重復提交',
`state` INT (1) DEFAULT '1' COMMENT '1創建訂單,2本地業務,3支付業務',
PRIMARY KEY (`order_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '訂單狀態表';
CREATE TABLE `dp_state_record` (
`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`order_id` BIGINT (20) NOT NULL COMMENT '訂單id',
`state_dec` VARCHAR (50) DEFAULT NULL COMMENT '狀態描述',
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '狀態記錄表';
模擬業務流程
將訂單創建,本地業務,支付業務,分開分段管理提交,分階段測驗例外熔斷的業務,
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderStateMapper orderStateMapper ;
@Resource
private StateRecordMapper stateRecordMapper ;
@Override
public OrderState queryOrder(OrderState orderState) {
Map<String,Object> paramMap = new HashMap<>() ;
paramMap.put("order_id",orderState.getOrderId());
List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
if (orderStateList != null && orderStateList.size()>0){
return orderStateList.get(0) ;
}
return null ;
}
@Override
public boolean createOrder(OrderState orderState) {
int saveRes = orderStateMapper.insert(orderState);
if (saveRes > 0){
saveStateRecord(orderState.getOrderId(),"訂單創建成功");
}
return saveRes > 0 ;
}
@Override
public boolean localBiz(OrderState orderState) {
orderState.setState(2);
int updateRes = orderStateMapper.updateState(orderState) ;
if (updateRes > 0){
saveStateRecord(orderState.getOrderId(),"本地業務成功");
}
return updateRes > 0;
}
@Override
public boolean paymentBiz(OrderState orderState) {
orderState.setState(3);
int updateRes = orderStateMapper.updateState(orderState) ;
if (updateRes > 0){
saveStateRecord(orderState.getOrderId(),"支付業務成功");
}
return updateRes > 0;
}
private void saveStateRecord (Long orderId,String stateDec){
StateRecord stateRecord = new StateRecord() ;
stateRecord.setOrderId(orderId);
stateRecord.setStateDec(stateDec);
stateRecordMapper.insert(stateRecord) ;
}
}
測驗介面
根據訂單狀態,分段補償執行未完成的業務,如果該訂單已經完成,多次提交不影響最終結果,
@Api(value = "https://www.cnblogs.com/cicada-smile/p/OrderController")
@RestController
public class OrderController {
@Resource
private OrderService orderService ;
@PostMapping("/submitOrder")
public String submitOrder (OrderState orderState){
OrderState orderState01 = orderService.queryOrder(orderState) ;
if (orderState01 == null){
// 正常業務流程
orderService.createOrder(orderState) ;
orderService.localBiz(orderState) ;
orderService.paymentBiz(orderState) ;
} else {
switch (orderState01.getState()){
case 1:
// 訂單創建成功:后推執行本地和支付業務
orderService.localBiz(orderState01) ;
orderService.paymentBiz(orderState01) ;
break ;
case 2:
// 訂單本地業務成功:后推執行支付業務
orderService.paymentBiz(orderState01) ;
break ;
default:
break ;
}
}
return "success" ;
}
}
絮叨一句:實際開發中,該流程是不會由頁面多次提交完成,訂單是不能重復提交的,下面會演示如何控制,這里業務是執行后推到完成,也可能業務向前清理,把整個流程置為失敗,這里涉及關鍵狀態判斷,要選取一個狀態作為成功或失敗的標識,判斷后續操作流程,在分布式系統中這種復雜流程最難處理的是分布式事務,最終一致性問題,后續再聊,
三、介面重復提交
1、表單重復提交
在實際情況中,介面如果處理時間過長,用戶可能會點擊多次提交按鈕,導致資料重復,
常見的一個解決方案:在表單提交中隱藏一個token_id引數,一起提交到介面服務中,資料庫存盤訂單和關聯的tokenId,如果多次提交,直接回傳頁面提示資訊即可,
2、演示案例
訂單關聯Token查詢
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Boolean queryToken(OrderState orderState) {
Map<String,Object> paramMap = new HashMap<>() ;
paramMap.put("order_id",orderState.getOrderId());
paramMap.put("token_id",orderState.getTokenId());
List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
return orderStateList.size() > 0 ;
}
}
測驗介面
@RestController
public class OrderController {
@Resource
private OrderService orderService ;
@PostMapping("/repeatSub")
public String repeatSub (OrderState orderState){
boolean flag = orderService.queryToken(orderState) ;
if (flag){
return "請勿重復提交訂單" ;
}
return "success" ;
}
}
四、源代碼地址
GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推薦閱讀:資料和架構管理
| 序號 | 標題 |
|---|---|
| A01 | 資料源管理:主從庫動態路由,AOP模式讀寫分離 |
| A02 | 資料源管理:基于JDBC模式,適配和管理動態資料源 |
| A03 | 資料源管理:動態權限校驗,表結構和資料遷移流程 |
| A04 | 資料源管理:關系型分庫分表,列式庫分布式計算 |
| A05 | 資料源管理:PostGreSQL環境整合,JSON型別應用 |
| A06 | 資料源管理:基于DataX組件,同步資料和原始碼分析 |
| A07 | 資料源管理:OLAP查詢引擎,ClickHouse集群化管理 |
| C01 | 架構基礎:單服務.集群.分布式,基本區別和聯系 |
| C02 | 架構設計:分布式業務系統中,全域ID生成策略 |
| C03 | 架構設計:分布式系統調度,Zookeeper集群化管理 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/185241.html
標籤:Java
