什麼是接口冪等性
接口冪等性,簡單來講就是指一個接口調用一次和調用N次的效果是同樣的,不會產生其餘的反作用。
注意:
這裏的效果同樣和返回結果同樣的區別,好比咱們都知道查詢接口具備自然的冪等性,可是屢次調用查詢接口的過程當中,若是有其餘操做對查詢的數據進行了新增、修改、刪除操做,那麼查詢接口的返回結果就會不一直,可是這並不能說明該查詢接口不具備冪等性。java
場景說明
典型場景,對指定訂單發起一筆付款交易,不管交易接口調用一次仍是N次,都只能扣用戶帳戶一次錢。
通常最簡單的冪等處理就是經過訂單狀態來進行控制,僞代碼以下:web
begin:
根據訂單號查詢訂單狀態;
if(待支付){
扣款操做;
更新訂單狀態爲已支付;
}
end;
代碼實戰
一、訂單表說明微信
CREATE TABLE `tb_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`goods_code` varchar(255) DEFAULT NULL COMMENT '商品編碼',
`goods_num` int(10) DEFAULT NULL COMMENT '商品數量',
`money` decimal(20,0) DEFAULT NULL COMMENT '總金額',
`status` tinyint(1) DEFAULT NULL COMMENT '訂單狀態(0未支付,1支付)',
`account` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '帳戶',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
添加一條測試訂單數據:訂單id是1,狀態0的未支付訂單。併發
INSERT INTO `order`.`tb_order`(`id`, `goods_code`, `goods_num`, `money`, `status`, `account`) VALUES (1, 'wsj0001', 5, 3000, 0, 'laowan');
二、新建Order訂單工程,實現訂單支付接口app
/**
* @program: order
* @description: 訂單接口實現
* @author: wanli
* @create: 2020-09-21 16:11
**/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
OrderMapper orderMapper;
/**
* 訂單支付接口
* @param orderId
* @return
* @throws InterruptedException
*/
@Override
public String payOrder(String orderId) throws InterruptedException {
//一、查詢訂單
Order order = new Order();
order.setId(Long.parseLong(orderId));
order = orderMapper.selectByPrimaryKey(order);
//爲模擬併發,暫定100ms
Thread.sleep(100);
//二、根據訂單狀態判斷,是否進行支付
if(order.getStatus().equals(0)){
log.info("進行支付,並更新訂單狀態");
order.setStatus(1);
orderMapper.updateByPrimaryKeySelective(order);
return "支付完成";
}else{
log.info("已支付");
return "已支付";
}
}
}
說明:
這個應該是不少人的常見代碼,先查詢訂單狀態,判斷訂單狀態是不是未支付狀態,是則進行支付。ide
三、採用JMeter模擬併發支付
高併發
模擬每秒50個併發,執行結果爲:
能夠發現,在高併發的狀況下,出現了大量重複支付的狀況。
四、優化:經過for update添加悲觀鎖
4.一、OrderMapper.xml中添加selectForUpdate測試
<select id="selectForUpdate" resultType="com.laowan.order.model.Order">
select * from tb_order t where t.id = #{orderId} for update
</select>
4.二、OrderMapper.xml中添加selectForUpdate優化
public interface OrderMapper extends BaseMapper<Order> {
Order selectForUpdate(Long orderId);
}
4.三、修改支付訂單方法ui
/**
* 訂單支付接口
* @param orderId
* @return
* @throws InterruptedException
*/
@Transactional(rollbackFor = Exception.class)
@Override
public String payOrder(String orderId) throws InterruptedException {
//一、查詢訂單
Order order = new Order();
order.setId(Long.parseLong(orderId));
//經過
order = orderMapper.selectForUpdate(Long.parseLong(orderId));
//爲模擬併發,暫定100ms
Thread.sleep(100);
//二、根據訂單狀態判斷,是否進行支付
if(order.getStatus().equals(0)){
log.info("進行支付,並更新訂單狀態");
order.setStatus(1);
orderMapper.updateByPrimaryKeySelective(order);
return "支付完成";
}else{
log.info("已支付");
return "已支付";
}
}
說明:
for update必定要配合事務使用,否則執行完查詢語句後,會自動釋放鎖。
給方法添加 @Transactional事務註解後,只有方法執行完畢纔會自動釋放鎖。
4.四、再次使用JMeter壓測
訂單支付接口在高併發屢次調用的狀況下,仍然只支付了一次,接口的冪等性獲得保證。
總結:
一、不能簡單的經過訂單狀態來控制接口的冪等性,高併發狀況下,多個線程同時查到未支付狀態的訂單,就容易出現重複支付的狀況。
二、經過for update對訂單記錄加上排他鎖,使的該訂單記錄不能被其餘線程查詢到,也不能進行其餘修改操做。只有當事務提交,釋放鎖後,該訂單才能被其餘線程操做,從而保證接口的冪等性。
三、必定要給訂單號加上索引,讓查詢和修改操做只添加行鎖,避免鎖表。(Innodb引擎中,查詢和更新操做若是不走索引,就會進行全表鎖定。本例中因爲訂單號是主鍵,因此不用加)。
四、@Transactional註解不但能控制一個方法中的多個數據修改操做的原子性,也能控制鎖的釋放。好比本例中,儘管只有一個修改操做,可是必定要添加@Transactional註解讓方法執行完後自動釋放鎖。
固然實現接口冪等性還有不少其餘的方法,這裏只是說明一個最典型的錯誤場景,並經過簡單的添加悲觀鎖,實現接口的冪等性控制。
更多精彩,關注我吧。
本文分享自微信公衆號 - 跟着老萬學java(douzhe_2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。