分布式锁【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 实例上加锁,只有超过一半的实例同意加锁才能成功,以提高锁的可靠性和容错性。

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

相关推荐
waicsdn_haha4 分钟前
MySql-9.1.0安装详细教程(保姆级)
java·数据库·后端·mysql·php·性能测试·数据库开发
明达技术33 分钟前
MR30分布式IO模块,为港口岸桥安全增效保驾护航
分布式·安全
hjxxlsx43 分钟前
C# 趋势图:洞察其发展轨迹与未来走向
服务器·数据库·c#
途中刂1 小时前
基于NodeJs+Express+MySQL 实现的个人博客项目
数据库·mysql·node.js·express
Sunmanit1 小时前
异步将用户信息存入 Redis 缓存
java·数据库·redis·缓存
Steven_Mmm1 小时前
高并发-缓存预热
redis·缓存
技术路上的苦行僧1 小时前
分布式专题(6)之MongoDB复制(副本)集实战及其原理分析
分布式·mongodb复制集
jikuaidi6yuan1 小时前
性能参数对比
数据库·mongodb
天地人和20162 小时前
[go-zero] 子查询,使用到Squirrel包
开发语言·数据库·golang
凡人的AI工具箱2 小时前
每天40分玩转Django:Django认证系统
开发语言·数据库·后端·python·django