老萬教你最簡單接口冪等性控制

什麼是接口冪等性

接口冪等性,簡單來講就是指一個接口調用一次和調用N次的效果是同樣的,不會產生其餘的反作用。
注意:
這裏的效果同樣返回結果同樣的區別,好比咱們都知道查詢接口具備自然的冪等性,可是屢次調用查詢接口的過程當中,若是有其餘操做對查詢的數據進行了新增、修改、刪除操做,那麼查詢接口的返回結果就會不一直,可是這並不能說明該查詢接口不具備冪等性。java

場景說明

典型場景,對指定訂單發起一筆付款交易,不管交易接口調用一次仍是N次,都只能扣用戶帳戶一次錢。
通常最簡單的冪等處理就是經過訂單狀態來進行控制,僞代碼以下:web

begin:
     根據訂單號查詢訂單狀態;
     if(待支付){
         扣款操做;
         更新訂單狀態爲已支付;
     }
end;    

代碼實戰

一、訂單表說明微信

CREATE TABLE `tb_order` (
  `id` bigint(20NOT NULL AUTO_INCREMENT,
  `goods_code` varchar(255DEFAULT NULL COMMENT '商品編碼',
  `goods_num` int(10DEFAULT NULL COMMENT '商品數量',
  `money` decimal(20,0DEFAULT NULL COMMENT '總金額',
  `status` tinyint(1DEFAULT NULL COMMENT '訂單狀態(0未支付,1支付)',
  `account` varchar(30CHARACTER 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'530000'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

本文分享自微信公衆號 - 跟着老萬學java(douzhe_2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索