
框架篇:分布式锁
前言
java有synchronize和Lock,mysql 修改类的sql也带有锁。锁定数据状态,让数据状态在并发场景,按我们预想逻辑进行状态转移,然而在分布式,集群的情况下,怎么去锁定数据状态呢
- 数据库的分布式锁方案
- 基于redis实现分布式锁
- 基于zookeeper实现分布式锁
数据库的分布式锁方案
数据库分布锁的难点
- 单点故障?数据库可以多搞个数据库备份
- 没有失效时间?每次加锁时,插入一个期待的有效时间;A:定时任务,隔一段时间清理时间失效锁。B:下次加锁时则先判断当前时间是否大于锁的有效时间,以此判断锁是否失效
- 不可重入?在数据加锁时加入一个幂等唯一值字段,下次获取时,先判断这个字段是否一致,一致则说明是当前操作重入操作
基于redis实现分布式锁
- redis 是一个快速访问的高性能服务,相比数据库,在redis实现锁比直接在数据库的数据加锁,性能好。同时也为数据库减压,减少事务执行因为锁的问题阻塞
- 引入jedis
setnx + expire
- setnx + expire 存在死锁的问题。setnx()方法作用就是SET IF NOT EXIST,expire()方法就是给锁加一个过期时间。由于这是两条Redis命令,不具有原子性
lua脚本(正确方式)
- lua脚本在Redis的执行过程是原子性,要么成功,要么失败。
set {key} {value} nx ex {second} (正确方式)
- 这是Redis的SET指令扩展参数,具有原子性
删除redis分布锁
基于Redlock算法实现分布式锁
- 以上redis分布锁的缺点就是它加锁时只作用在一个Redis节点上,即使redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况。redis主从同步不能保证一致性,master会优先返回结果,在同步数据到slave
- 例如:在redis的master节点上拿到了锁 -> 这个加锁的key还没有同步到slave节点 -> master故障,发生故障转移,slave节点升级为master节点 -> 导致锁丢失
- RedLock算法的实现步骤
1: 获取当前时间,以毫秒为单位2: 按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。3: 加锁后客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且获取锁使用的时间小于锁失效时间时,锁才算获取成功。(如:10s> 30ms+40ms+50ms+20ms+50ms)4: 如果取到了锁,key的真正有效时间就变啦,等于锁失效时间减去获取锁所使用的时间。5: 如果获取锁失败(没有在至少N/2+1个master实例取到锁,或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)
- 代码示例
基于 zookeeper 实现分布式锁
- maven引入
- Redlock算法往往需要多个redis集群才能实现,东西越多,就越容易出错。但是如何实现一个高效高可用的分布式锁呢 ? zookeeper
- zookeeper特点
○ 最终一致性:客户端的操作状态会在 zookeepr 集群保持一致
○ 可靠性:zookeeper 集群具有简单、健壮、良好的性能
○ 原子性:操作只能成功或者失败,没有中间状态
○ 时间顺序性:如果消息 A 在消息 B 发布,则 A 则排在 B 前面
- zookeeper 临时顺序节点:临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉(可解决分布式锁的自动失效)。另外,在临时节点下面不能创建子节点,集群zk环境下,同一个路径的临时节点只能成功创建一个
- zookeeper 监视器:zookeeper创建一个节点时,会注册一个该节点的监视器,当节点状态发生改变时,watch会被触发,zooKeeper将会向客户端发送一条通知
- zookeeper 分布式锁原理
创建临时有序节点,每个线程均能创建节点成功,但是其序号不同,只有序号最小的可以拥有锁,其它线程只需要监听比自己序号小的节点状态即可1: 在指定的节点下创建一个锁目录lock2: 线程X进来获取锁在lock目录下,并创建临时有序节点3: 线程X获取lock目录下所有子节点,并获取比自己小的兄弟节点,如果不存在比自己小的节点,说明当前线程序号最小,顺利获取锁4: 此时线程Y进来创建临时节点并获取兄弟节点,判断自己是否为最小序号节点,发现不是,于是设置监听(watch)比自己小的节点(这里是为了发生上面说的羊群效应)5: 线程X执行完逻辑,删除自己的节点,线程Y监听到节点有变化,进一步判断自己是已经是最小节点,顺利获取锁
- 代码实例
参数文章
- 记一次分布式锁-基于数据库[1]
- Redis分布式锁的正确实现方式[2]
- redis实现分布式锁,单机-集群-红锁[3]
- 如何能通俗的讲解Zookeeper分布式锁的应用场景?[4]
- 基于Zookeeper开源客户端Curator实现分布式锁[5]
文章转载自公众号:潜行前行
