分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁的实现,如果不同的系统或者同一个系统不同的主机之间共享同一个资源时,往往需要互斥来防止彼此干扰,进而保证一致性


三十三·如何通过redis实现分布式锁_分布式锁 image.png

  • 互斥性:当前只有一个客户端可以获取到某个资源的锁,不能出现多个客户端获取到同一个资源的锁

  • 安全性:只有持有锁的客户端可以删除或者释放锁

  • 死锁:当持有锁的客户端没有释放掉锁而挂机了,这个时候其他的客户端就无法获取到锁从而导致了死锁

  • 容错:当某个redis或者多个redis客户端宕机了,其他客户端仍然可以获取锁或者释放锁

三十三·如何通过redis实现分布式锁_redis_02 image.png

三十三·如何通过redis实现分布式锁_分布式锁_03 image.png


上面的图展示了一个有缺陷的代码,虽然我们使用了setnx这个方法来解决分布式锁的问题,但是 setnx和后面的设置过期expire 这两块代码不是原子性的,意思就是假若setnx成功了,但是在给key设置过期时间的之前程序报错没有执行到下面代码,那么这个key就永不过期,这就造成了死锁问题了,该锁不会被释放,其他线程也永远拿不到锁了,所以我们需要一个操作,就是获取锁,并且设置过期时间的逻辑具有原子性,最好是一个操作就可以达到目的。

下面我们通过使用redis的方法就可以做到,同时获取锁并且设置过期时间

三十三·如何通过redis实现分布式锁_客户端_04 image.png

下面给一个代码示例,代码中我们设置锁,如果成功就进行业务逻辑处理,然后再释放锁,也就是把锁给删掉


三十三·如何通过redis实现分布式锁_客户端_05 image.png

还要一点注意:在代码中我们随机了一个uuid,然后在业务逻辑中我们先判断了,这个锁的值是跟当前的uuid是一致的,然后再释放锁,这个是为什么呢?

1.因为如果是在分布式的环境中会有多个线程访问到这个类,假若A线程执行这段代码需要15s,设置锁的过期时间为10s,结果程序就执行了10s,业务逻辑还没有处理完我这个锁就已经失效了;

2.这个时候A线程还没有把这个锁是删除掉,然后因为锁已经失效了,B线程这个时候就可以设置锁了,然后刚设置完,A线程走到了删除锁这个代码,把B线程刚设置的锁给删掉了,这样C,D,E,F线程都类似于这样的情况,那么锁永远都是类似于失效的状态,因为刚设置完,可能就立马被其他线程给删掉了;

解决方式:

1.首先针对于A线程可以删除其他线程的锁的问题,我们可以在程序中加一个uuid,然后设置锁的时候作为值设置进去,然后在删除锁的时候判断一下值是否相等即可

2.就是因为程序运行时间可能会大于锁的失效时间,所以在业务逻辑处理的代码中,我们可以新运行一个子线程,然后定时器循环的重置锁的过期时间,例如锁的过期时间我们设置为10s,那么我们每3s 重置一次锁的过期时间10s,这样就能保证在锁失效前,我们的代码可以顺利运行完毕。

三十三·如何通过redis实现分布式锁_redis_06 image.png

三十三·如何通过redis实现分布式锁_客户端_07 image.png

分布式锁的好用的工具 - redisson

还有一种更加好用的分布式锁的实现,那就是redisson,如果给出了代码的实例


三十三·如何通过redis实现分布式锁_redis_08 image.png

三十三·如何通过redis实现分布式锁_客户端_09 image.png