type
status
date
slug
summary
tags
category
icon
password
URL
文章来源说明
为什么需要分布式锁
- 多线程的时候,为了避免同时操作共享变量带来的数据问题,通常用互斥锁来实现,不过其范围在同一个进程中
- 如果多个进程同时操作如恶化互斥
- 微服务中通常会有多个进程需要同时修改mysql同一个记录为了避免错误所以需要引入分布式锁来解决问题
- 想要实现分布式锁,一般都是借助外部系统,所有进程都去这个系统上申请加锁
- 外部系统可以是mysql,redis,zookeepr,但是为了性能当前主流都会选择redis和zookeepr

redis分布式锁实现
- 实现分布式锁需要互斥能力 setnx 刚好可以,但是这里的问题大家都知道可能会死锁
- 程序业务处理异常没有及时释放锁
- 进程挂了,没有机会释放锁
- 避免死锁,设置超时时间,2.6.12版本后redis 可以用1条命令来设置了,避免了原子问题
- 锁过期,和锁被别人释放问题
- 场景:
- 客户端1加锁成功,开始操作共享资源
- 客户端1操作共享资源时间过期,锁被自动释放
- 客户端2加锁成功,开始操作共享资源
- 客户端1执行完成,释放了客户端2的锁
这里有两个问题
1.锁过期,客户端1操作时间过长,导致锁自动释放被客户端2持有
2 释放了别人的锁,客户端1释放了客户2的锁
对于问题2可以在加锁时设置唯一标识,可以是自己线程id也可以是UUId
之后释放锁先判断
上面的命令get 和 del 是两步所以还有原子问题,所以需要lua脚本将其封装1个命令让redis
执行
对于问题1,主要是锁过期无法判断,我们可以开1个线程,定时去检测这个锁失效时间,锁快过期时
自动续期可见实现一把分布式锁需要考虑的东西挺多,好在redission 把这些工作都封装好了
自动续期用的就是看门狗守护线程
除此之外还实现一些其他功能
- 可重入锁
- 乐观锁
- 公平锁
- 读写锁
- Redlock
使用redission 后出现的问题
一般用redis 我们都会用主从切换,主库异常宕机,从库变主库
场景
- 客户端 1 在主库上执行 SET 命令,加锁成功
- 此时,主库异常宕机,SET 命令还未同步到从库上(主从复制是异步的)
- 从库被哨兵提升为新主库,这个锁在新的主库上,丢失了!
为此,Redis 的作者提出一种解决方案,就是我们经常听到的 Redlock(红锁)。
但是红锁使用条件很苛刻,并且需要5个单节点,还要考虑机器时钟问题,一般不建议
基于zookeeper的锁安全吗?
如果你有了解过 Zookeeper,基于它实现的分布式锁是这样的:
- 客户端 1 和 2 都尝试创建「临时节点」,例如 /lock
- 假设客户端 1 先到达,则加锁成功,客户端 2 加锁失败
- 客户端 1 操作共享资源
- 客户端 1 删除 /lock 节点,释放锁
你应该也看到了,Zookeeper 不像 Redis 那样,需要考虑锁的过期时间问题,它是采用了「临时节点」,保证客户端 1 拿到锁后,只要连接不断,就可以一直持有锁。
而且,如果客户端 1 异常崩溃了,那么这个临时节点会自动删除,保证了锁一定会被释放。
不错,没有锁过期的烦恼,还能在异常时自动释放锁,是不是觉得很完美?
其实不然。
思考一下,客户端 1 创建临时节点后,Zookeeper 是如何保证让这个客户端一直持有锁呢?
原因就在于,客户端 1 此时会与 Zookeeper 服务器维护一个 Session,这个 Session 会依赖客户端「定时心跳」来维持连接。如果 Zookeeper 长时间收不到客户端的心跳,就认为这个 Session 过期了,也会把这个临时节点删除。可见,即使是使用 Zookeeper,也无法保证进程 GC、网络延迟异常场景下的安全性。
好,现在我们来总结一下 Zookeeper 在使用分布式锁时优劣:
Zookeeper 的优点:
- 不需要考虑锁的过期时间
- watch 机制,加锁失败,可以 watch 等待锁释放,实现乐观锁
但它的劣势是:
- 性能不如 Redis
- 部署和运维成本高
- 客户端与 Zookeeper 的长时间失联,锁被释放问题
有关Notion安装或者使用上的问题,欢迎您在底部评论区留言,一起交流~
- 作者:卷神
- 链接:https://blog.952712.xyz/article/fc4df704-dee6-47ee-81a0-ed215f0d7fac
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。







