黑马点评项目实战(五)——分布式锁
一 一人一单的并发安全问题
原来使用synchronized悲观锁用户id来保证一人一单,如下图:

但在集群部署情况下,仍然会出现并发安全问题,不同jvm下的线程无法实现锁互斥,如下:
二 分布式锁
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
三 Redis分布式锁
实现

安全问题
使用Redis锁可能存在这样的安全问题,当线程1因某种原因线程阻塞时,可能会释放其他线程获取的锁。:
为了解决该问题,在释放锁时应该判断是否是自己的锁,如下:
这时仍然有可能出现问题,如下图,当线程1获取锁标识并判断一致后,还未释放锁时遇到阻塞(如jvm垃圾回收),导致后面又释放了其他线程的锁。

因此必须确保判断锁标识动作和释放锁动作的原子性。
Redis的Lua脚本
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。
1 | -- 获取锁中的线程标识 get key |
四 Redisson
Redisson分布式锁
基于Redis实现的分布式锁有以下问题:
因此使用Redisson来优化。Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
Redisson分布式锁原理
可重入
可重入锁:同一个线程多次请求锁,可能会造成死锁。因此需要允许可重入锁。
利用redis中的hash数据结构,为锁额外记录一个值,代表该锁的重入次数。
同一个线程每重入一次锁,将值+1,释放锁改为将值-1,值为0时才真正释放锁。

为保证原子性,同样要使用Lua脚本。
重试机制
redisson可以设置等待时间waitTime,在获取锁失败后,会订阅并等待释放锁的信号,然后进行重试,直到获取锁成功或waitTime消耗完。
超时续约
redisson中的watchdog在获取锁时设置了一个定时任务,每隔一段时间刷新锁的有效期(releaseTime/3),直到释放锁时取消定时任务。注意,如果设置了leaseTime就没有watchdog了。
主从一致性问题
问题产生原因:

redisson解决方案:MultiLock
将每一个redis节点都视为主节点,只有向每个节点获取锁成功,才算成功。此时如果一个redis节点宕机,并不影响锁的正常获取和释放。此外也可以给每个节点建立主从关系,此时如果一个主节点宕机且刚好没有完成同步,由于从节点没有锁的标识,其他线程也无法成功获取锁,满足要求。

