分布式锁2
一.分布式锁应用场景
1.1单机锁的实现:
-
方法或者代码块加synchronized
-
用Lock实现,加锁、解锁等
1.2多台机器
为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,就要用到分布式锁
二.实现思路
-
加锁:同一时间只能被一个线程占用
-
解锁
-
可靠行保证
-
异常情况锁不释放
三.3种分布式锁的实现方式
3.1 mysql数据库
-
设计一张表,应该包含哪些字段呢?
代码块Plain Textid方法名/资源名(唯一索引)状态(占用;未占用)时间(异常情况时,定时任务扫描释放锁)锁的持有者... -
加锁:表里插入/更新一条数据
-
解锁:修改表的锁状态
-
异常情况不释放锁:定时任务扫描释放锁
3.2 redis
-
加锁:setNx数据
-
解锁:del
-
异常情况下不释放锁:添加过期时间
可靠性分析:
-
单节点有可能挂掉,导致锁不可用,所以集群部署,一主多从
-
一主多从会有什么问题:假设主节点加锁成功,还没有同步从节点,会怎么样?
答:挂掉之后,哨兵机制,会选取新的主节点,选取完成后,因为挂掉的主节点并没有把锁的信息同步完成,所以会造成多个线程同时获取到锁
RedLock算法:
-
部署5个master节点,加锁,向5个redis 添加数据,多数以上返回值,则加锁成功;
RedLock注意事项:
-
设置多个主节点(注意不是一主多从,而是多个主节点),依次setNx值(相同的key、value)
-
当大部分节点返回成功,说明加锁成功。加锁时设置过期时间,防止Redis挂掉,还在等Redis响应结果。
-
如果没有获取到锁,需要在所有Redis实例解锁。
-
假如A、B、C、D、E5个节点,线程一ABC三个主节点加锁成功,DE加锁失败;然后C节点挂了还没来得及持久化,然后C节点就会重启,重启之后并没有加锁记录,线程二来加锁CDE返回加锁成功(AB加锁不成功),线程二也会获取锁,这种情况就会造成两个线程都获取到锁。解决办法是挂机之后延迟重启。
3.3 zk
-
加锁:创建临时节点,如果节点是序号最小的则加锁成功;否则添加watch监听序号最大的节点,进入等待
-
解锁:删除节点
-
异常情况下不释放锁:当客户端异常,与zk断开连接,临时节点自动删除
可靠性分析:
-
客户端加锁,从节点接到命令,通知主节点
-
主节点接收命令,发起投票
-
多数从节点ask,认为通过
-
Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader,然后commit;
-
Follwer把请求结果返回给Client
分析:
-
client给Follwer写数据,可是Follwer却宕机了,会出现数据不一致问题么?不可能,这种时候,client建立节点失败,根本获取不到锁。
-
client给Follwer写数据,Follwer将请求转发给Leader,Leader宕机了,会出现不一致的问题么?不可能,这种时候,Zookeeper会选取新的leader,继续上面的提到的写流程。
3.4分布式锁实现方式比较
-
性能:Redie性能比ZK好(因为zk需要多数节点ask,才算写入成功;Redis主节点写入成功就算成功)
-
加锁阻塞:ZK利用watch机制,加锁失败进入等待状态,加锁阻塞,类似于本地线程;Redis如果要实现,则需要自己写循环等待,有比较多的空转。
-
可靠性:ZK更可靠,因为zk采用zab协议,为了协调而生。Redis可靠性稍微差一点,及时使用RedLock,也无法保证100%。
四.分布式锁系统架构
4.1接口设计
-
获取锁:Lock getLock(String lockName)
-
阻塞加锁:void lock()
-
非阻塞加锁:void tryLock()
-
解锁:void unlock();
-
浙公网安备 33010602011771号