抢红包案例:使用悲观锁方式修复红包超发的bug
超发问题分析
针对这个案例,用户抢到红包后,红包总量应-1,当多个用户同时抢红包,多个线程同时读得库存为n,相应的逻辑执行后,最后将均执update T_RED_PACKET set stock = stock - 1 where id = #{id}
使用数据库锁的解决方案
使用悲观锁(排它锁 for update)
线程1在查询红包数时,使用排他锁 select id, user_id as userId, amount, send_date as sendDate, total, unit_amount as unitAmount, stock, version, note from T_RED_PACKET where id = #{id} for update
然后进行后续的操作(redPacketDao.decreaseRedPacket 和 userRedPacketDao.grapRedPacket),更新红包数量,最后提交事务。
线程2在查询红包数时,如果线程1还未释放排他锁,它将等待
线程3同线程2,依次类推
使用乐观锁(依靠表的设计和代码)
在红包表添加version版本字段或者timestamp时间戳字段,这里使用version
线程1查询后,执行更新变成update T_RED_PACKET set stock = stock - 1, version = version + 1 where id = #{id} and version = #{version}
保证修改的数据是和它查询出来的数据是一致的,而其他线程并未进行修改。当然,如果更新失败,表示在更新操作之前有其他线程已经更新了该红包数,那么就可以尝试重入机制来保证更新成功。
总结
- 1.悲观锁使用排他锁,当程序独占锁时,其他程序就连查询都是不允许的,导致吞吐较低。如果在查询较多的情况下,可使用乐观锁。
- 2.乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。
代码改造
RedPacketDao新增接口方法
/** * 获取红包信息. 悲观锁的实现方式 * * @param id * --红包id * @return 红包具体信息 */ public RedPacket getRedPacketForUpdate(Long id);
RedPacket.xml配置映射文件
<!-- 查询红包具体信息 悲观锁的实现方式for update --> <select id="getRedPacketForUpdate" parameterType="long" resultType="com.artisan.redpacket.pojo.RedPacket"> select id, user_id as userId, amount, send_date as sendDate, total, unit_amount as unitAmount, stock, version, note from T_RED_PACKET where id = #{id} for update </select>
悲观锁是一种利用数据库内部机制提供的锁的方法,也就是对更新的数据加锁,这样在并发期间一旦有一个事务持有了数据库记录的锁,其他的线程将不能再对数据进行更新.
在 SQL 中加入的 for update 语句,意味着将持有对数据库记录的行更新锁(因为这里使用主键查询,所以只会对行加锁。如果使用的是非主键查询,要考虑是否对全表加锁的问题,加锁后可能引发其他查询的阻塞〉,那就意味着在高并发的场景下 , 当一条事务持有了这个更新锁才能往下操作,其他的线程如果要更新这条记录,都需要等待,这样就不会出现超发现象引发的数据一致性问题了.
目前只是对数据库加了一个锁,当加的锁比较多的时候,数据库的性能还会持续下降,所以要区分不同的业务场景,慎重使用。
悲观锁导致性能下降的原因探究
一旦线程1 提交了事务,那么锁就会被释放,这个时候被挂起的线程就会开始竞争红包资源,那么竞争到的线程就会被 CPU 恢复到运行状态,继续运行。
于是频繁挂起,等待持有锁线程释放资源, 一旦释放资源后,就开始抢夺,恢复线程,直至所有红包资源抢完。
在高并发的过程中,使用悲观锁就会造成大量的线程被挂起和恢复,这将十分消耗资源,这就是为什么使用悲观锁性能不佳的原因。
有些时候,我们也会把悲观锁称为独占锁,毕竟只有一个线程可以独占这个资源,或者称为阻塞锁,因为它会造成其他线程的阻塞。无论如何它都会造成并发能力的下降,从而导致 CPU频繁切换线程上下文,造成性能低下。
为了克服这个问题,提高并发的能力,避免大量线程因为阻塞导致 CPU 进行大量的上下文切换,目前比较普遍的是乐观锁机制。
浙公网安备 33010602011771号