文章目录

  • 一、Redis分布式锁
  • 1.1、分布式锁1.0
  • 1.2、分布式锁2.0
  • 1.3、分布式锁3.0
  • 1.4、分布式锁4.0



一、Redis分布式锁

分布式应用进行逻辑处理时经常会遇到并发问题。

比如一个操作要修改用户的状态,修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状态这两个操作不是原子的。

redis分布式锁如何实现锁等待 redis分布式锁等待队列_redis

这个时候就要使用到分布式锁来限制程序的并发执行。

分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。
占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑(释放锁)。

那么在加锁到释放锁这个过程中会出现很多问题,这就需要通过各种各样的分布式锁实现方案来解决这些问题

1.1、分布式锁1.0

在redis缓存中存储一个key标识正在有一个线程在操作库存预减的操作,而在存储的时候选在String类型,在StringRedisTemplate中调用opsForValue().setIfAbsent()方法,该方法在存储的过程中会先检查在缓存中是否存在这一个键,如果存在那么就不会继续存储,如果不存在就会将这个key存储在redis中。通过这个方法就会实现多个线程同时操作库存预减时只会有一个线程获取到机会,存储在redis的这个key就相当于是一个锁,只有等当前获得这个锁的线程结束操作得到时候,释放锁,才能继续让其他线程进行操作,需要注意的是,在操作完毕之后一定要释放锁,否则就会造成死锁的情况

redis分布式锁如何实现锁等待 redis分布式锁等待队列_redis分布式锁如何实现锁等待_02


但是这还会造成一种情况:在锁释放前系统宕机,导致死锁

这样的情况下可以通过为这个key添加过期时间,大概估计库存预减需要耗费多少时间,就为这个key设置多少时间过期,这样即使因为系统宕机导致死锁的问题也就解决了。

1.2、分布式锁2.0

但是呢,由于在StringRedisTemplate中opsForValue的setIfAbsent()重载方法中设置过期时间底层没有使用setnx命令,这就导致设值跟检查key是否存在不是一个原子性操作,这也就有可能导致并发问题,最终的解决方法就是使用lua脚本(Redis批处理命令)

redis分布式锁如何实现锁等待 redis分布式锁等待队列_redis分布式锁如何实现锁等待_03

redis分布式锁如何实现锁等待 redis分布式锁等待队列_数据库_04

1.3、分布式锁3.0

在分布式2.0中可能会出现另一个问题:第一个线程由于处理业务时间过长导致redis缓存中的key已经过期,导致第二个线程已经开始进行库存预减这个业务,同时有可能第一个线程在第二个线程还没有释放锁的时候,第一个线程已经走到了释放锁的步骤,这样就导致第一个线程释放了第二个线程的锁,这样持续下去就导致我们加的锁没有意义

redis分布式锁如何实现锁等待 redis分布式锁等待队列_数据库_05

所以,我们需要对上述问题进行优化,既然是其他线程释放了其他线程的锁,那么我们就让每一个线程只能释放自己的锁,这样的话,就需要我们为存入的key设置一个唯一的value值

redis分布式锁如何实现锁等待 redis分布式锁等待队列_redis分布式锁如何实现锁等待_06

1.4、分布式锁4.0

如果仔细研究,会发现上述的通过设置过期时间解决释放锁的问题中,设置的过期时间是一个估算的值,但是如果实际上执行的业务操作时间大于设置的过期时间的话,那么分布式3.0的解决方法就没有用了

面对这样的问题,我们可以通过设置定时任务来解决,当我们将key缓存之后就开启定时任务,在过期时间内的一个时间点来检查缓存中的key是否存在,如果不存在说明业务已经执行完毕,缓存中的key已经被当前线程主动删除了,如果还存在,说明业务还没有执行完毕,这个时候就对这个key重新设置过期时间,延长key的存活时间,这种方案被叫做WatchDog(看门狗)

上述看门狗方案已经有开源框架实现–>Redis的客户端Redssion
使用Redssion,需要在项目中添加依赖

redis分布式锁如何实现锁等待 redis分布式锁等待队列_redis分布式锁如何实现锁等待_07

redis分布式锁如何实现锁等待 redis分布式锁等待队列_redis_08