打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
几种分布式锁
userphoto

2023.10.10 北京

关注

数据库锁

数据库锁是分布式锁的一种简单实现,其加锁的原理实际就是数据库的唯一约束或者行锁。其基本原理(伪代码)如下:

数据库锁简单实现 

try{    // 按唯一键进行insert操作    insertByUnique(uniqueKey);    // 如果没有抛出异常,则认为拿到了锁    // 略去业务代码}catch(唯一键冲突异常){    // 发生唯一键冲突,说明已有其它服务拿到了这个锁    // 略去异常处理代码}finally{    // 完成业务处理后,释放锁    deleteByUniqueKey(uniqueKey);}

阻塞性

锁同步机制中,未获取到锁的一方往往需要阻塞当前线程、并等待锁释放后再次竞争。数据库锁利用行锁可以方便的实现这个功能。

数据库锁:阻塞锁简单实现 

try{    // 按唯一键进行selectForUpdate操作,以争夺其行锁    selectForUpdate(uniqueKey);    // 如果没有抛出异常,则认为拿到了锁    // 略去业务代码}catch(锁超时异常){    // 超时处理}finally{    // 完成业务处理后,释放锁    // 退出事务即可}

与简单实现不同的是,这里的“加锁”操作不是通过insert uniqueKey实现,而是select for update实现。与之对应的,释放锁的操作也从delete uniqueKey变成了退出当前事务。

锁超时

一般来说,数据库操作的超时时间即这把锁的超时时间。这使得数据库锁无法为不同的锁设置不同的超时时间。

个别情况下(insert了锁、而没有delete),还需要增加一个定时任务,用于清理数据库中写入的锁。

可重入

数据库的锁本身就是可重入的。对于代码来说,只要保证对同一个锁的多次请求在同一个数据库事务里,就能够做到可重入。

如果特别为加锁、解锁声明了独立的事务(如REQUEST_NEW),则需要在数据库锁上增加一个对象标志,用以标记当前获得这个锁的请求是谁,并根据这个标志来实现同一个请求的可重入。

读写锁

由于数据库的读操作一般不加锁(特别声明了for update等除外),因此,数据库锁实现不了读写锁。

其它

其它如公平/非公平锁、信号量等,数据库锁都较难实现。

Redis锁

用Redis实现分布式锁,需要分单机和集群两种情况来讨论。

单机情况

单机情况下,Redis主要依靠SETNX命令(SET if Not eXists)来实现分布式锁。这个命令的功能是:当且仅当 key 不存在时,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

注:2019-01-16 使用setNX命令时,不应简单地将value值设定为固定值,而应当使用一个随机数,并根据此随机数来释放锁。这样可以防止其它线程错误地删除锁。此时,删除锁的脚本应当是:

if redis.call("get",KEYS[1]) == ARGV[1] then    return redis.call("del",KEYS[1])else    return 0end

集群情况

集群情况比较复杂。Redis官方提出了一个“RedLock”算法,其基本思路是:客户端遍历Redis集群中的每个实例,并尝试获取该实例上的锁;如果能够成功获得集群实例中半数以上的锁,则视为获得了这个锁;否则认为没有得到这个锁,并删除已经获得的少数实例上的锁。

实际的原理要更复杂一些,考虑了各种超时、重试、性能、同步等问题。细节可以参见:Redis分布式锁

注:2018-04-28 RedLock应该是3.0版本之前提出来的。在3.0版本之前,Redis集群自身不做一致性哈希操作,各台master之间相互独立、互不影响;一致性哈希是由客户端来实现的。但在3.0版本之后,Redis集群在服务端实现了一致性哈希,集群服务对外“视图”与单机服务是一样的。这种情况下,RedLock就失去了它的意义。在3.0版本后的Redis集群服务中,同一个Key会经由一致性哈希算法而放到同一个插槽(Slot)、同一台Master上去;这与单机服务是一样的。

代码实现

基于Redis实现的分布式锁,已经有成熟的代码库了,参见Redis分布式锁中的介绍,或者redisson的Github地址。关于这个库的分布式锁特性,参见分布式锁和同步器

ZooKepper锁

相对于Redis锁,ZooKeeper锁的原理更好理解一些。其基本思路是:客户端在ZooKeeper的lockName路径下创建一个节点,并判断此节点是否此路径下的第一个节点;如果是,则得到锁,并在业务处理完毕后删除这个节点;如果不是,说明没有得到锁。

稍感遗憾的是,我没有找到相关的代码库,而只有一些实现此功能的代码样例。参见:使用zookeeper实现的分布式锁

阻塞性

由于客户端可以监听ZooKeeper对某个节点的操作(是否被删除),因此,当需要阻塞锁时,当前线程只需要监听ZooKeeper服务中上一个节点即可。如果上一个节点被删除了,说明锁已释放,并且自己已经是lockName路径下的最小节点,从而获得锁。

锁超时

为了让ZooKeeper锁能自动超时,客户端在lockName路径下创建的节点一般是临时顺序节点。这样,如果客户端发生问题导致断开连接,在会话超时之后,节点删除,进而释放这个锁。

可重入

按照基本思路来看,每次使用ZooKeeper加锁时都会生成一个新的节点,因此它本身并不支持可重入。

如果要求可重入,那么需要在客户端(或者线程上下文中)临时存储当前节点,并在此基础上进行判断处理。

读写锁

读写锁的思路可以参考 使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)。简单来说,竞争锁时,需要判断已有节点中是否有写入锁。如果有,则需要等待写入锁释放;如果没有,则认为自己已经获得了锁。

其它

其它如公平/非公平锁、信号量等,Zookeeper锁也可以实现,只是比较复杂。

参考

Redis分布式锁

redisson的Github地址

分布式锁和同步器

zookeeper 分布式锁服务

使用zookeeper实现的分布式锁

使用ZooKeeper实现Java跨JVM的分布式锁

使用分布式锁时考虑哪些问题

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
分布式锁原理及实现方式
11 张图深入理解分布式锁原理
Java分布式面试题集合
分布式锁实现方案(REDIS,ZOOKEEPER,TAIR)
《我想进大厂》之分布式锁夺命连环9问 | 大理版人在囧途
分布式锁
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服