分布式锁【Redis场景分布式锁篇】

文章目录

1.Redis分布式锁

锁通常用来控制共享资源,比如一个进程内有多个线程竞争一个数据的使用权限,解决方式之一就是加锁。分布式锁,就是在分布式场景下的锁,比如多台不同机器上的进程,去竞争同一项资源,就是分布式锁。

一个优秀的分布式锁需要具有互斥性、抗死锁性、对称性、可靠性。

  • 互斥性:一把锁只能由一个竞争者获取。
  • 抗死锁性:避免锁因为异常永远不会被释放。
  • 对称性:加锁和解锁必须是同一个竞争者。
  • 可靠性:需要有一定的异常处理能力、容灾能力。

接下来,主要探究Redis是怎么实现这个分布式锁。Go!

版本一

Redis的SETNX在设置kv的时候,如果key不存在,会设置这个kv并返回1;否则直接返回0。

基于以上SETNX的特性,我们就可以实现加锁。

版本二

对于上述的加锁,有一个直观的问题,就是获取锁的这个服务挂掉了,那锁怎么办呢?所以我们需要一个过期时间来对锁进行兜底处理。

bash 复制代码
> SET k 123 NX EX 10
"OK"

以上这个命令就可以实现原子性的增加过期时间了(SETNX和expire不是原子操作)。

如果说业务还没有完成,锁却过期了,还是不安全的,怎么办?

那么这里就有一个WatchDog机制,定时向Redis进行续期操作,防止锁在业务完成之前过期,续期的开始时间可以是过期时间的三分之一(当然也会消耗部分资源,续期操作也可能导致其他请求方无法使用,这就需要考虑业务安全性)。

版本三

上面的锁是可以用了,但是还是出现问题了。例如,服务 A 获取了锁,但由于某些原因导致锁过期,服务 A 未停止执行其任务。这时,服务 B 获取了锁并开始执行任务。然而,当服务 A 恢复后试图释放锁时,由于锁已过期且被服务 B 获取,服务 A 实际上会误释放服务 B 的锁,导致服务 B 的任务中断或产生冲突。

那么怎么办?

分布式锁需要满足谁申请谁释放原则,不能释放别人的锁。一般分布式锁会对上锁的key做一个唯一性id,这个id就是owner的值。

这就大事告成了吗?不不不,这个加owner值,在检查锁,再释放锁这块不是原子化的操作,可能获取时还是自己的,删除的时候却是别人的了。所以,这里在这一块就使用Lua脚本。

以上的方案确保了抗死锁性、互斥性、安全性。但是可靠性不能确保。以下简单探讨一下可靠性怎么保证。

  1. 主从容灾
  2. 多机部署

2.分布式锁其它方案

回到最初,我们为什么需要分布式锁呢?

最典型的案例就是库存超卖现象。对于单机项目使用单机锁就可以解决,如果多级部署就需要用到分布式锁。

那么如何实现分布式锁呢?

  1. 主动轮询:服务会定期向锁服务发送请求,直到成功获取锁或超时。
  2. 监听回调:服务无需主动轮询,而是通过锁服务的回调通知来感知锁的状态变化。

1.主动轮询型

主动轮询型分布式锁的核心是通过一个标识(通常是一个键)来表示锁的状态。加锁操作意味着向这个键写入特定的值,而解锁操作则对应删除这个键。如果在尝试加锁时发现锁已被其他服务获取,当前服务会定期轮询该键的状态,直到锁被释放为止,然后再尝试加锁并执行自身的操作。

主要是用Redis或MySQL实现。

1.MySQL分布式锁

MySQL 实现分布式锁主要是通过一张专用的锁表,其中每一行代表一个锁。加锁操作通过向表中插入或更新记录完成,解锁操作则通过删除或修改相应记录实现。MySQL 利用事务和唯一约束,确保加锁操作的原子性。然而,与 Redis 不同,MySQL 锁缺乏内置的过期机制,在处理死锁问题时存在一定挑战。

具体来说,数据库锁无法像 Redis 那样设置自动过期时间。如果持有锁的进程异常退出或挂掉,而未能释放锁记录,其他线程会因为等待该锁被释放而陷入阻塞,从而导致系统死锁。这种问题在高可用场景下尤为严重,需要额外的逻辑来处理锁的超时与清理。

2.Redis分布式锁

Redis 分布式锁的实现通常基于唯一的key来标识一把锁。加锁操作通过向 Redis 插入一条键值数据完成,解锁操作则通过删除该key实现。如果在尝试加锁时发现锁已被他人持有(即key已存在),当前线程会通过轮询机制反复检查锁的状态,直到锁被释放后重新尝试加锁。

Redis 的分布式锁由于其单线程架构和高性能的特点,能有效避免竞争条件问题。此外,通过设置key的自动过期时间,可以防止因锁持有者异常退出导致锁长期不释放的情况。

2.监听回调型

什么是监听回调型?

例如,你在餐厅排队等座位。你拿到了一个排队号码,这就像是你"获取锁"。你不需要不断询问前台是否有座位,而是通过餐厅的叫号系统(广播、显示屏等)来等待通知,这就像是监听回调。当轮到你时,系统会通知你,你就可以去就座,这就相当于"获得锁并执行操作"。用餐后,当你离开餐厅时,你释放了座位,其他顾客可以继续排队,这类似于"释放锁"。整个过程中,你只需等待系统通知,而无需主动询问或轮询,大大提高了效率。

1.Etcd

Etcd是一个使用Go语言编写的高可用的分布式键值对kv数据仓库。

2.Zookeeper

zookeeper是一个用于提供分布式应用程序协调服务的组件。

总结

1、分布式锁实现要点是什么?

(怎么加锁、怎么解锁、怎么用)

加锁时候要设置owner过期时间,前者是便于解锁时进行拥有者判断,后者是作为异常情况的兜底。解锁时候要先判断owner,是自己的再释放,这里还要注意这两步操作的原子性,可以用lua脚本来进行保证。

(ps:Redis实现分布式锁的原理是什么?)

客户端想要加锁,就会去Redis中查找对应的key是否存在,如果不存在,就创建这个key,表示加锁成功;当另一个客户端也想加锁时,发现这个key已经存在了,就会返回0,表示加锁失败。

(ps:那你这个客户端执行SETNX加锁,结果执行del删除释放锁一直没成功,造成死锁,怎么办?)

给这个锁设置过期时间,若超过过期时间,Redis自动释放锁。

(ps:当客户端A成功加锁,但被客户端解锁了,要是有其他客户端加锁,就会和A共享数据,咋办?)

为了避免客户端A被错误解锁后,其他客户端与A共享数据的问题,可以为每个锁添加唯一的 client_id 标识,确保每个客户端的锁操作独立。当客户端A成功加锁时,它会将自己的 client_id 存储在锁的key中。在解锁时,使用 Lua 脚本原子操作,首先检查 Redis 中存储的 client_id 是否与当前客户端的标识一致,只有一致时才允许删除锁,确保锁的释放只能由持有者完成。这样,即使其他客户端加锁,也不会误解锁或与A共享数据,从而保证了锁的独立性和数据的安全性。

(ps:锁过期时间到了,但是业务还没有完成?)

启动一个WatchDog,在过期之前,刷新一下过期时间。

2、为什么需要owner呢?

对称性。

3、Lua一定可以保证原子性吗?

lua本身不具备原子性,用lua保证原子性是因为Redis是单线程执行,一个流程放进lua来执行,相当于是打包在一起,Redis执行它时不会被其他请求打断。(将查询,删除key打包,其中只有删除是写操作,所以这个流程本身是原子性的)

4、RedLock是什么?

RedLock 是一种分布式锁算法,通过在多个 Redis 实例上加锁,只有超过一半的实例同意加锁才能成功,以提高锁的可靠性和容错性。

感觉不错就点个赞,给作者充充电吧~

相关推荐
koping_wu10 分钟前
【RabbitMQ】架构原理、消息丢失、重复消费、顺序消费、事务消息
分布式·架构·rabbitmq
nongcunqq1 小时前
abap 操作 excel
java·数据库·excel
rain bye bye2 小时前
calibre LVS 跑不起来 就将setup 的LVS Option connect下的 connect all nets by name 打开。
服务器·数据库·lvs
喵桑..2 小时前
kafka源码阅读
分布式·kafka
阿里云大数据AI技术3 小时前
云栖实录|MaxCompute全新升级:AI时代的原生数据仓库
大数据·数据库·云原生
酷ku的森3 小时前
RabbitMQ的概述
分布式·rabbitmq
不剪发的Tony老师4 小时前
Valentina Studio:一款跨平台的数据库管理工具
数据库·sql
weixin_307779134 小时前
在 Microsoft Azure 上部署 ClickHouse 数据仓库:托管服务与自行部署的全面指南
开发语言·数据库·数据仓库·云计算·azure
六元七角八分4 小时前
pom.xml
xml·数据库
虚行4 小时前
Mysql 数据同步中间件 对比
数据库·mysql·中间件