集群并发-更新库存,验证用分布式锁的必要性
2023-07-17 本文已影响0人
___TheOne___
1. 背景
点餐项目开发"优惠券需求",用户领取优惠券时、额外增加优惠券库存时,都更新库存值。另外项目是 集群部署。
那么集群并发-更新库存,是否有使用分布式锁的必要性?
2. 猜想
答:有必要加Redission分布式锁。
// 语句中 【stock - i >= 0】,防止优惠券超发
@Transactional(rollbackFor = Exception.class)
@Modifying(clearAutomatically = true)
@Query(value = "update t_coupon set stock = stock - ?1 where id = ?2 and stock - ?1 >=0", nativeQuery = true)
int deductionCount( int i,Long id);
Pgsql数据库,通过语句SHOW transaction_isolation;
可知事务默认隔离级别为 read committed【读提交】
。
虽然上述扣减库存的update语句,显式加了事务注解@Transactional,
但还是会出现集群并发扣减时,更新被覆盖-数据不一致的问题。
【执行场景设想】
两个事务同时针对同一个优惠券库存量执行扣减操作,它们都希望将库存值减1。
- 事务A读取库存当前值,假设为10,然后减少1,得到9。
- 但在事务A将结果提交到数据库之前,事务B也读取了字段的当前值,仍然是10,并将其减少1,得到9。
最后,事务A 和 事务B 都将 值9 写回数据库,导致最终结果不正确。
3. 验证
- 创建一个优惠券,初始库存设置为1000;
- 书写一个测试验证接口,每请求一次,对应优惠券库存扣减1
【Dao方法先只加事务注解,暂时不加分布式锁】; - 使用Jmeter压力测试
启动1000个线程,并发调用接口扣减库存。
【最终结果】
Jmeter压力测试后,此优惠券的库存量为191,并非预想中的0。
直接验证了集群并发-事务中更新库存,添加分布式锁的必要性!
- 后续将 优惠券扣减1的代码,适用Redission锁进行控制,再次执行Jmeter压力测试,最终此优惠券的库存量为预期的0。
【没有出现集群并发更新覆盖、也没有出现超发】。
4. 思考
-
虽然使用分布式锁解决了问题,但相应的只要是锁,就会带来性能开销,所以应尽可能减小锁的范围。
分布式锁应只包裹更新优惠券库存代码。
如果优惠券库存更新成功,才插入用户-优惠券记录。 - 优惠券场景下,需考虑的场景:
1> 某一个用户领取某一个券时,加分布式锁-串行执行,防止券被超领;
思考:考虑到后续有 针对某一个用户可“一键领取”多张优惠券,所以锁粒度应为 “租户id + 用户id + 优惠券id”。使得某一个用户领取不同的券是可以并行领取。Eg: String userCouponKey = "COUPON:USER:KEY" + tenantId + userId + couponId;
若所锁粒度为“租户id + 用户id”,某一个用户领取多个券只能串行执行效率低下,不符合需求。
2> 某一个优惠券超发限制(即防止优惠券库存stock为负数);
3> 集群并发-更新库存量,并发控制。Eg: String couponStockKey = "COUPON:STOCK:KEY" + tenantId + couponId;