高並發寫測試悲觀鎖,樂觀鎖


源碼地址
有紕漏,錯誤,歡迎指正,謝謝

JMeter測試工具

  • 需要創建一個心的工程;
  • 添加一個線程組—這里面設置秒級並發數;
  • 添加一個請求—這里設置壓力測試的接口;參數使用:${ }可以從csv文件中獲取數據
  • 請求頭管理—添加需要修改的請求頭信息;
  • CSV文件—可以將請求的參數,以變量的形式,從csv文件中獲取,模擬多種請求數據;

具體怎么用,百度

測試場景

  • 測試場景為:請求對應商品,減少對應庫存;

  • 並發量:秒級1w請求;

  • 聲明:本次測試,請求操作失敗,直接丟棄,沒有采取補救措施,單純測試兩種鎖的寫性能;

不加鎖

Controller就不寫了,但是要測試,需要通過Request測試

Mapper:直接更新stock庫存數量

int updateStock(ProductLock lock);

<update id="updateStock" parameterType="com.example.lock.entity.ProductLock">
    update product_lock
    set stock = stock-#{stock,jdbcType=INTEGER}
	where id = #{id,jdbcType=INTEGER}
    AND
    stock > 0
</update>

Service:先查詢到商品詳情,判斷庫存是否充足,更新庫存數量

@Transactional(rollbackFor = Exception.class)
public int updateStock(ProductLockDto dto) throws Exception{
    int res=0;
    ProductLock entity=productLockMapper.selectByPrimaryKey(dto.getId());
    // 判斷:要更新的庫存 > 當前庫存
    if (entity!=null && entity.getStock().compareTo(dto.getStock())>=0){
        entity.setStock(dto.getStock());
        return productLockMapper.updateStock(entity);
    }
    return res;
}

通過JMeter測試(秒級1w並發量),出現問題:

  1. 庫存剩余數 > 應該剩余數,存在少賣現象;

樂觀鎖—版本號

修改表結構:

  1. 添加字段:版本號version

修改Mapper:

<update id="updateStock_2" parameterType="com.example.lock.entity.ProductLock">
    update product_lock
    set stock = stock-#{stock,jdbcType=INTEGER},version = version + 1
    where
    id = #{id,jdbcType=INTEGER}
    AND
    version = #{version,jdbcType=INTEGER}
    AND
    stock > 0
</update>

測試

依然秒級並發1w請求測試:(1w個請求,每個請求減少庫存數=2)

測試前數據:庫存2w,version=0

測試數據后:庫存18298,version=851

分析

首先,真正請求通過,並修改了庫存的請求數應該等於version的增量;

即:version * 2 = 減少庫存數(因為每個version修改,庫存減少2)

851 * 2 = 1702 也就等於 20000 - 18298 = 1702 說明:后端數據修改是沒有問題的;

但是:version修改次數,可以認為是成功響應請求次數:851個;

1w請求,響應了851個,不太象話;

結論

  1. 不存在超賣,少賣的情況;
  2. 高並發下,不能響應所有請求,只能響應少部分請求;
  3. 因為樂觀鎖的機制,version字段,判斷失敗,直接不做操作,導致大部分請求,無法成功;

在高並發寫入的情況下,不應該使用樂觀鎖!,后面悲觀鎖效果更好

悲觀鎖—for update

添加Mapper:

<!--悲觀鎖實現 for update 鎖表-->
<select id="selectByIdNegative" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from product_lock
    where id = #{id}
    for update
</select>
<update id="updateStock_3" parameterType="com.example.lock.entity.ProductLock">
    update product_lock
    set stock = stock-#{stock,jdbcType=INTEGER},version = version+1
    where
    id = #{id,jdbcType=INTEGER}
    AND
    stock > 0
</update>

Service層:@Transactional注解,保證上面兩個方法,是一個事務,才能實現for update 鎖表

@Transactional(rollbackFor = Exception.class)
public int updateStock_3(ProductLockDto dto){
    int res =0;
    // select-for update 查詢
    ProductLock entity = productLockMapper.selectByIdNegative(dto.getId());
    // 判斷version字段,庫存是否充足
    if (entity!=null && entity.getStock().compareTo(dto.getStock())>=0){
        entity.setStock(dto.getStock());
        res = productLockMapper.updateStock_3(entity);
    }
    if (res > 0) {
        log.info("減少庫存=>{}",dto.getStock());
    }
    return res;
}

測試

依然秒級並發1w請求測試:(1w個請求,每個請求減少庫存數=2)

測試前數據:庫存2w,version=0

測試數據后:庫存356,version=9822

分析

與之前一樣:version*2 = 減少庫存數(因為每個version修改,庫存減少2)

9822 * 2 = 19644 也就等於 20000 - 356 = 19644 說明:后端數據修改是沒有問題的;

同樣:並沒有響應所有的請求,但是從version的修改次數看,

1w請求,響應了9822個請求失敗178個

結論

  1. 不存在超賣,少賣的情況;
  2. 高並發下,不能響應所有請求,但是響應絕大部分請求;
  3. 在寫方面,優於樂觀鎖;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM