源碼地址
有紕漏,錯誤,歡迎指正,謝謝
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並發量),出現問題:
- 庫存剩余數 > 應該剩余數,存在少賣現象;
樂觀鎖—版本號
修改表結構:
- 添加字段:版本號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個,不太象話;
結論
- 不存在超賣,少賣的情況;
- 高並發下,不能響應所有請求,只能響應少部分請求;
- 因為樂觀鎖的機制,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個
結論
- 不存在超賣,少賣的情況;
- 高並發下,不能響應所有請求,但是響應絕大部分請求;
- 在寫方面,優於樂觀鎖;