重温分布式锁
前面的文章说过,分布式锁的出现是为了解决跨JVM,多台服务器共享一个资源的问题,避免并发出现数据不安全,数据不一致的问题。
本章我们讲述的分布式锁是使用Redisson实现的,其提供了分布式锁的功能组件,可以用来弥补基于 Redis的原子性操作的缺陷,下面分析基于Redis实现分布式锁的不足之处。
比如,如果redis负责存储分布式锁的节点发生了宕机的情况 ,但是该锁又是处于被锁住的状态,这种情况就会出现死锁。为了避免这种情况,Redisson提供了个监控锁的“看门狗”,作用是在Redisson实例被关闭之前,不断延长分布式锁的有效期,默认情况下,这个时间 是30秒,当然我们可以通过Config.lockWarchDogTimeout进行设置。
Redisson为我们提供了多个同业务场景的锁,根据功能特性,我们可以分成可重入锁,公平锁,联锁,红锁,读写锁,**信号量 **,闭锁。
下面介绍可重入锁
分布式锁之一次性锁
这种一次性锁是在某一个时间段内,只能允许一个线程共享资源,其他的线程统统打回,这种业务场景适用于,“用户注册 ”,“抢红包”,“重复提交”,下面我们验证用于 “重复提交”
@RequestMapping("/testRedissonOnceLock")
public ResultVo testRedissonOnceLock(@RequestBody UserLoginDto userLoginDto) {
// 定义锁的名字
String key = "redisson:myOnceLock" + userLoginDto.getUserName();
// 获取分布式锁(一次性的)
RLock rLock = redissonClient.getLock(key);
// 一次性锁
rLock.lock(1L,TimeUnit.SECONDS);
// 根据 用户名称查询账号
UserReg userReg = userRegMapper.selectByUserName(userLoginDto.getUserName());
if (userReg == null) {
UserReg insertUserReg = new UserReg(){
{
setCreateTime(new Date());
setPassword(userLoginDto.getPassword());
setUserName(userLoginDto.getUserName());
}
};
userRegMapper.insertSelective(insertUserReg);
log.info("{}注册成功",userLoginDto.getUserName());
return ResultVo.success("注册成功");
}
rLock.forceUnlock();
return ResultVo.success();
}
分布式锁之可重入锁
可重入锁就是当线程 获取不到锁 的时候会等待一段时间,重新去尝试获取锁,如果不能获取锁 ,并且重试的时间达到了上限,则 意味着 该线程就会 被抛弃 。
这种场景适用于同一时刻并发产生很多线程,但是同一时刻不能获取到分布式锁,但是却允许隔一定的时间后重新获取到,典型的应用场景就是商城高并发抢购,比如防止超库。
@RequestMapping("/testReplyRedissonLock")
public ResultVo testReplyRedissonLock(@RequestBody BookRobDto dto) {
String key = "Replylock" + dto.getBookNo() + "_" + dto.getUserId();
// 获取分布式锁
RLock lock=redissonClient.getLock(key);
// 尝试获取分布式锁,如果返回true,即代表成功获取了分布式锁
try {
log.info("开始抢购");
boolean result = lock.tryLock(100,5L,TimeUnit.SECONDS);
if (result) {
// 判断此书籍是否能够被抢购
BookStock bookStock = bookStockMapper.selectBookRobByBookNo(dto.getBookNo());
if (bookStock == null || Integer.valueOf(bookStock.getStock()) <= 0) {
return ResultVo.error("库存不足");
}
// 先查询用户是否抢购过此书籍
Integer buyedTotal = bookRobMapper.countByBookNoUserId(dto.getUserId(), dto.getBookNo());
if (buyedTotal > 0) {
return ResultVo.error("您已抢购过该书籍");
}
bookStock.setStock(String.valueOf(Integer.valueOf(bookStock.getStock()) - 1));
log.info("更新之前的库存" + bookStock.getStock());
int res = bookStockMapper.updateStockWithLock(dto.getBookNo());
log.info("更新库存完成");
if (res > 0 ) {
// 创建书籍抢购记录实体信息
BookRob entity = new BookRob();
// 从提交的用户抢购请求实体信息中对应的字段取值
// 复制到新创建的书籍抢购记录实体的相应字段中
BeanUtils.copyProperties(dto,entity);
// 设置抢购时间
entity.setRobTime(new Date());
// 插入用户注册信息
bookRobMapper.insertSelective(entity);
log.info("插入抢购记录成功");
}
return ResultVo.success("抢购成功");
}else {
log.error("库存不足1");
}
} catch (InterruptedException e) {
return ResultVo.error("异常");
} finally {
// 释放锁
lock.forceUnlock();
}
return ResultVo.error("抢购失败");
}
<update id="updateStockWithLock">
update book_stock SET stock = stock - 1
where isActive=1 AND book_no=#{bookNo} and stock > 0 and (stock -1)>=0
</update>