Redis分布式锁的应用及实现
分布式锁应用场景
实际工作场景中可以出现多线程情况下多数据库的某一条记录进行处理操作,会导致数据出现异常,例如,库存超卖情况,这种情况就要使用到锁机制,可以根据不同的应用场景来使用不同的锁。
1.使用数据库乐观锁,乐观锁就是在数据库表中添加version字段,在查询的时候将version一并查询出来,在更新的时候通过比较version+1值与数据库的关系来判断有没有其他线程 更新过此条记录。注意,乐观锁获取锁失败的处理机制由业务决定,如果在此之前有做数据库其他更新操作,是否抛出异常回滚数据需要由业务决定。
2.悲观锁,可以使用数据库select for update 语句来实现悲观锁,这时候本质上是获取行锁中的排它锁。(仅对innerdb引擎而言)。注意,此机制在并发情况下可能会占用过多连接池信息。
3.redis分布式锁
redis分布式锁有多种实现方式,因为redis是单线程,可以利用自增序列来进行判断有没有拿锁成功,也可以利用set nx px指令来进行。下面给出一种的实现方式。需要注意,redis客户端版本不同,api接口不同,如果不能设置锁的过期时间,需要自行实现获取锁逻辑。
/** * @description: Redis分布式锁实现 * @createDate: 2020/4/10 */ @Service @AllArgsConstructor @Slf4j public class CommonManager { private final AccountDao accountDao; private final StringRedisTemplate stringRedisTemplate; /** * 查询用户的昵称 * * @param userId * @return */ public String querynickname(Integer userId) { return Optional.ofNullable(accountDao.getAccountById(userId)) .map(AccountPO::getnickname) .orElse(null); } /** * 通用redis分布式锁 * @param key * @param value * @return 是否拿锁成功 */ public boolean getLock(String key, String value){ //对应setnx命令 if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){ //可以成功设置,也就是key不存在 return true; } //判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁 String currentValue = stringRedisTemplate.opsForValue().get(key); //如果锁过期,currentValue不为空且小于当前时间 if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){ //获取上一个锁的时间value,对应getset,如果key存在 String oldValue =stringRedisTemplate.opsForValue().getAndSet(key,value); //假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。 //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。 if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue) ){ //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的时间戳,也是防止并发 return true; } } return false; } /** * 解锁 * @param key * @param value */ public void unlock(String key,String value){ try { String currentValue = stringRedisTemplate.opsForValue().get(key); if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value) ){ //删除key stringRedisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { log.error("[Redis分布式锁] 解锁出现异常了,{}",e); } } }
代码是通用的
这里给出类似的实现方案及说明
https://segmentfault.com/a/1190000011421467
他们写的更细一些