1 基于数据库实现
利用主键ID的唯一性,实现唯一约束
在数据库中的某一张表内存入数据(比如说是一个手机号,只能一个人访问),就用该手机号作为key进行存入,如果插入成功就说明可用操作;如果插入失败,说明锁给别人拿走了。
存在问题:
非阻塞:
锁获取失败后,如何处理需要自定义,比如说是进行自选、还是进行阻塞,如果阻塞后如何唤醒?
不可重入:
如果加锁操作中,自己获取的锁进行了递归获取,需要修改下锁的获取操作(可以通过在数据库插入数据的时候,插入线程id或者其他唯一标识)
死锁:
删除锁失败、或者加锁后自己宕机了,可以通过设定超时时间、使用定时任务检查(这里需要添加看门狗机制)
数据库单点故障:
高可用集群、主备模式
总结:问题太多,不容易使用
2 基于redis 分布式锁的实现
set(key,value,nx,px)需要主要, nx 和 px 必须是原子操作
nx代表:成功返回1,失败返回0
px代表:设定的超时时间
存在问题:
任务超时问题:
任务还没做完,锁就超时自动释放,下一个线程获得了锁(加入看门狗方案)
加锁和解锁不是同一个线程:
在value里面引入线程的唯一标识,只有自己可以解锁
不可重入:
可以同样按照上面那个方案解决
单点故障同时锁未传递到从:
redis的写是在主节点写入,然后传递到从节点,这时候存在当主节点写入后,还没有传递到从节点主节点就宕机的情况,采用 redlock机制
Redission:对于jedis进一步封装,加入了看门狗机制
redlock:多个 redis 实例来实现分布式锁,保障在单点故障的时候仍然可以使用
尝试从 N 个独立的 Redis实例中获取锁
计算获取锁的消耗时长,只有时间小于锁的过期时间,并且大多数(N/2+1)都成功获取了锁,才可以认为加锁成功
如果加锁失败,就到每一个实例上面释放锁
红锁存在 时钟漂移的问题
自己也搞不懂的:如果仅仅知识只是单点故障就采用红锁(性能非常差)感觉说不通啊,主从模式下,大可以通过主节点成功写入到从节点之后,主节点再返回给客户端写入成功进行解决
3 基于 zookeeper 实现的分布式锁
在 zookeeper 上面构建临时有序节点,然后去判断自己创建的节点是否是当前文件夹的第一个节点以此决定是否加锁成功。
如果加锁失败,就为节点注册一个监听器,监听自己节点的前一个节点消失,当消失的时候唤醒客户端线程
就是类似一个 AQS 的机制
- 当一个客户端需要获取锁时,在 /lock 下创建临时的且有序的子节点;
- 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,
如果是则认为获得锁;否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
- 执行业务代码,完成后,删除对应的子节点。
会话超时
如果一个已经获得锁的会话超时了,因为创建的是临时节点,所以该会话对应的临时节点会被删除,其它会话就可以获得锁了。可以看到,这种实现方式不会出现数据库的唯一索引实现方式释放锁失败的问题。
羊群效应
一个节点未获得锁,只需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应,一只羊动起来,其它羊也会一哄而上),而我们只希望它的后一个子节点收到通知。
存在问题: 客户端网络出现错误,突然和 zookeeper 断联,但是实际还在操作数据库的情况,锁会释放