分布式锁的三种实现

前言

锁机制是为了解决资源的并发操作带来的数据不一致问题

单进程情况下:即多线程并发访问

在一个JVM进程中,JDK的API提供了丰富的解决方案,比如synchronized关键字(被动同步锁标记)、lock(主动加锁)、volatile(可见性)、concurrent工具包(原子类)

多进程情况下:分布式系统中的并发访问

在一个高并发业务的服务集群中,大量的业务请求被分流到集群中不同的进程中进行处理,会出现运行在多个JVM中的相同业务逻辑对同一业务对象进行访问和操作。这种情况下,无法使用JDK提供的并发api来保证数据的一致性

分布式锁就是将位于不同进程中的数据状态集中在一个统一的进程(公共资源池)中进行协调和管理。保证不同进程对同一个数据的操作互斥,保证对资源的顺序访问

关键特性

互斥

在任意时刻,只有一个客户端能持有锁

超时过期

锁状态添加一个过去时间,防止锁的持有进程异常退出无法解锁导致的死锁

可重入

对于已经获取资源锁状态的进程,还可以对该资源加锁,应锁状态中对应的一个计数器加一;解锁时,计数器减一,计数器归零时,锁状态解除

阻塞

当获取锁失败时,执行进入阻塞状态,直到获取锁再执行后续逻辑

数据库表乐观锁

加锁机制

原理

数据库中创建一张表来存放公共资源的状态,表的字段有:

  • 主键
  • 公共资源的唯一标识 - 唯一约束
  • 创建时间

加锁时,向表中插入一条数据insert into lock_table ("my_lock_1","2018-12-08 15:15:28");

其他线程要获得my_lock_1锁的操作权限时,执行插入操作会抛出唯一约束异常,从而加锁失败。

解锁时只用删除之前插入的记录

缺点

锁的超时性、重入性、阻塞性,都需要在程序中解决

执行效率低,不适合高并发场景,尴尬的是分布式锁就是用在高并发的场景

超时解锁

当加锁的进程加锁之后抛出异常,没有执行解锁操作,就形成了死锁。需要建一个定时任务,根据创建时间将超时的锁记录删除

阻塞

只能在程序中do-while的方式不停的轮询尝试上锁,知道上锁成功,再进行后面的逻辑

重入

需要添加新的字段:获取锁线程的主机和进程信息以及上锁计数

同时还需要修改上锁和解锁逻辑:

  • 上锁:第一次上锁insert,第二次上锁update计数
  • 解锁:先检查计数,大于1时,计数减一;等于1时,删除记录

Redis实现分布式锁

加锁机制

将锁状态信息以键值对的形式保存储

  • 键:公共资源的唯一标识 lock-key
  • 值:选择比较灵活
    • 当前时间戳+过期时间:替代设置过期时间(redis维护过期),程序主动维护过期
    • 加锁操作id:锁必须由加锁者解锁
    • map对象:存放键值对 加锁操作id-计数器 实现重入锁

使用setnx命令上锁,使用getgetset命令尝试获得锁

使用到的命令

setnx(key,value) - set if not exits

如果key-value不存在,这将k-v存入缓存中并返回1,否则返回0

get(key)

获取key对应的value,不存在则返回nil

getset(key,value)

先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value

expire(key, seconds)

设置key的有效期为seconds秒

加锁流程

获得锁失败时,轮询解锁

在这里插入图片描述

扩展

ZooKeeper实现分布式锁

加锁机制

将锁状态以zk中临时顺序节点的形式存储

使用到的特性

有序节点

假如当前有一个父节点为/lock,我们可以在这个父节点下面创建子节点;zookeeper提供了一个可选的有序特性,例如我们可以创建子节点“/lock/node-”并且指明有序,那么zookeeper在生成子节点时会根据当前的子节点数量自动添加整数序号,也就是说如果是第一个创建的子节点,那么生成的子节点为/lock/node-0000000000,下一个节点则为/lock/node-0000000001,依次类推。

区别于节点数据的版本号

临时节点

客户端可以建立一个临时节点,在会话结束或者会话超时后,zookeeper会自动删除该节点。

事件监听

客户端和zk服务端通信使用长连接,在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper有如下四种事件:1)节点创建;2)节点删除;3)节点数据修改;4)子节点变更。

加锁流程

如果zk中不存在对应锁节点,创建一个临时节点/lock/my_lock(来表示这个锁状态信息),并且在这个临时节点下创建一个有序子节点 192.168.11.1-10JQKA-lock-000001(结尾表示有序节点的序号,前面表示锁持有者的身份信息)。此时,这个有序节点192.168.11.1-10JQKA-lock-000001/lock/my_lock下的第一个有序节点,获取锁权限

客户端获取锁权限后,完成业务操作(正常解锁)或抛出异常(于zk断开连接),都会删除临时有序节点192.168.11.1-10JQKA-lock-000001

在这里插入图片描述

如果zk中存在对应锁节点,查看该节点下的有序节点,如果为空,直接创建有序节点;如果不为空,在追加创建一个有序节点192.168.11.3-78910J-lock-000002,并且监听前一个节点的删除事件,当监听到前一个节点被删除,就说明自己是有序节点中的第一个,获取锁权限

在这里插入图片描述

重入锁的实现

只用在上面的基础上再加一层有序节点,加锁和解锁是对下一级有序节点进行操作,类似栈的操作(先加的锁先解)

192.168.11.1-10JQKA-lock-000001下层有序节点为空时,删除改节点-解锁

在这里插入图片描述

关键词

长连接

zookeeper的客户端与服务端使用长连接通信,简化实现分布式的操作

  • 不需要考虑死锁的超时过期,不用设置过期时间(断开连接即解锁)
  • 不需要轮询查看锁的状态(通过监听器感知解锁)
有序节点

通过有序节点保证对锁的顺序访问

扩展

  • 开源框架 Curator
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值