redis分布式锁安全吗?
1、为什么需要分布式锁
与分布式锁对应的是【单机锁】,在写多线程时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来【互斥】,以确保共享变量的正确性,使用范围是在同一个进程中。
若有多个进程需要同时操作一个共享资源,怎么实现互斥呢?现在的业务应用通常是微服务架构,若多个进程需要修改mysql的同一行记录时,为了避免操作乱序导致数据错误,这里就要使用【分布式锁】来解决了。
这里的做法是要有一个外部系统,当两个请求来时只会给一个进程返回成功,另一个等待。这个外部系统可以用mysql、redis或zookeeper,为了追求更好的性能,我们这里通常使用redis或Zookeeper。
2、 分布式锁怎么实现
要实现【分布式锁】,就必须要有互斥的能力。我们可以使用SETNX命令,SET if Not eXists,若key不存在才会设置它的值。此时只有一个请求能成功获取锁,操作完成后还要及时释放锁。如何释放?——使用DEL命令删除这个key。
但有个很大的问题:1)程序处理业务逻辑异常,没及时释放锁;2)进程挂了,没机会释放锁。那么这个客户端就会一直占用这个锁,造成【死锁】。
3、如何避免死锁
在申请锁时给key设置一个过期时间。这样无论客户端是否异常,这个锁都可以在x秒后自动释放。但这样还是有问题,现在的加锁和设置过期是两条命令,没有原子执行,还是有可能发生【死锁】。
在redis2.6.12版本之后,扩展了SET命令的参数, SET lock 1 EX 10 NX,这样就可以同时加锁和设置过期时间了。
那如果存在以下问题:1)对锁过期时间评估不准,实际上不够。2)客户端在释放锁没有检查这把锁是否还归自己持有,所就会发生释放别人锁的操作!
4、锁被别人释放怎么办
客户端在加锁时,设置一个只有自己直到的【唯一标识】进去。例如可以是自己的线程ID,也可以是UUID(随机且唯一),这样在释放的时候就要先判断锁是否还归自己持有。这里释放锁用的是GET+DEL两条命令,又会有原子性问题。所以我们就可以引入Lua脚本,让redsi执行。因为redis处理每一个请求都是单线程的,在执行lua脚本时,其他请求必须等,直到lua被处理完成。
5、评估锁过期时间
加锁时,先设置一个过期时间,然后开启一个【守护线程】,定时去检测这个锁的失效时间,若锁快要过期了,共享资源还未完成,那么久自动对锁进行【续期】。
如果是Java技术栈,有一个库已经把这些工作封装好了:Redisson。它是一个基于Java实现的Redis SDK客户端,在使用分布式锁时,他就采用了自动续期的方案来避免锁过期,这个守护线程我们一般叫做看门狗线程。此外,这个库还封装了很多功能:1)可重入锁;2)乐观锁;3)公平锁;4)读写锁;5)redlock等。
以上分析的都是在【单redis实例下可能出现的】问题,没有涉及到redis的部署架构细节。而我们工作中通常会采用主从集群+哨兵的模式部署,那当发生【主从切换】时,这个分布式锁还安全吗?(场景:客户端1在主库上执行SET,加锁成功-->此时主库宕机,SET命令还未同步到从库上-->从库被哨兵提升为主库,这个锁在新的主库上丢失了!!
6、redlock真的安全吗
首先,redlock的方案基于两个前提:1)不再需要部署从库和哨兵实例,只部署主库;2)主库要部署多个,官方推荐至少5个实例。
也就是说,要使用redlock,至少需要部署5个redis实例,而且都是主库,他们之间没有任何关系,都是一个个孤立的实例。
整体流程:
- 客户端先获取【当前时间戳T1】
- 客户端以此向这5个redis实例发起加锁请求,且每个请求会设置超时时间若某个一个实例加锁失败,就立即向下一个redis实例申请加锁。
- 若客户端从>=3个以上redis实例加锁成功,则再次获取【当前时间戳T2】,若T2-T1 < 锁过期时间,此时认为客户端加锁成功,否则认为加锁失败。
- 加锁成功,去操作共享资源。
- 加锁失败,向全部节点发起释放锁的请求(lua)
1)为什么要在多个实例上加锁?——本质上是为了保障在部分实例宕机时,剩余的实例加锁成功,整个锁服务依旧可用。
2)为什么大多数加锁成功,才算成功?——多个redis实例一起来用,其实组成了一个分布式系统。在这里总会出现异常节点,所以我们需要考虑异常节点达到多少个也依旧不会影响整个系统的【正确性】。
3)为什么步骤3加锁成功后,还要计算加锁的累计耗时?——因为操作的是多个节点,耗时肯定比单个实例更久,且因为是网络请求,可能存在延迟、丢包等。所以即使大多数加锁成功,若枷锁的累计耗时以及超过了锁的【过期时间】,那此时有些实例上的锁已经失效了,这个锁就没意义了。
4)为什么释放锁,要操作所有节点?——在某一redis节点加锁时,可能因为网络原因导致加锁失败。在释放锁时,不管有没有加锁成功,都要释放所有节点的锁。
7、redlock存在问题
性能与复杂度高:需要向多个redis节点发起加锁请求,网络延迟和节点通信成本高于单节点锁,且需处理部分节点超时的重试逻辑。
时钟依赖风险:依赖各节点时钟大致同步,若某节点时钟跳变,可能导致幻读锁(已过期的锁被判定为有效)
资源占用大:需维护多节点集群,增加部署和运维成本,对中小型应用不够友好。
适用场景:分布式系统中,单节点redis锁可能因主从切换、网络分区失效,redlock通过多节点投票机制降低锁丢失风险。容忍一定性能损耗,可以接收多节点通信开销。
8、Zookeeper
是一个分布式协调服务,基于ZAB协议(原子广播协议)提供强一致性的分布式锁、配置管理、服务发现能力。其核心数据模型为树形结构,节点可存储少量数据,并支持监听机制。
核心功能:
- 分布式锁:通过创建临时顺序节点实现,客户端监听前序节点释放事件,避免惊群效应。
- 配置中心:将配置存储在ZNode,客户端监听节点变更,实时获取最新配置。
总之,强一致性协调服务,原生支持分布式锁和监听机制,适合对一致性要求高的分布式系统,但部署维护成本高于Redis。
参考:水滴与银弹