Redis 分布式锁

复制代码
Redis分布式锁 - 胤凯 (oyto.github.io)

分布式锁

1、什么是分布式锁?

在分布式场景下的锁,比如在多台不同机器上的进程,去竞争同一项资源,就是分布式锁。

2、分布式锁有哪些特性?
  • 互斥性:只能让一个竞争者持有锁

  • 安全性:避免锁因为异常永远不被释放,当一个竞争者在持有锁期间,由于意外崩溃而导致未能主动解锁,其持有的锁也能够被兜底释放,并保证后续其它竞争者也能加锁

  • 对称性:同一个锁,加锁和解锁必须是一个竞争者,不能把其他竞争者持有的锁给释放了

  • 可靠性:需要有一定程度的异常处理能力、容灾能力。

3、分布式锁的实现方式
最简化版本

直接用 Redis 的 setnx 命令,语法:setnx key value,如果 key 不存在,则会将 key 设置为 value,并返回 1;如果 key 存在,不会有任务影响,返回 0。

通过 setnx 加锁,其他服务无法加锁,进而阻塞;用完之后,通过 delete 解锁,其他服务再去竞争锁。

支持过期时间

最简化版本有一个问题:如果获取锁的服务挂掉了,那么锁就一直得不到释放,就会导致其他服务无法获取到锁,影响到其他服务,所以这里需要一个过期时间来进行兜底。

Redis 中有 expire 命令,用来设置一个 key 的过期时间。但是 setnx 和 expire 不具备原子性,如果 setnx 获取锁之后,服务挂掉,还没来得及设置过期时间,照样石沉大海。

于是使用 set 和 expire 的原子操作:set key value nx ex seconds nx 标识 setnx 特性,ex 标识过期时间,最后一个参数就是过期时间的值。

加上过期时间,基本上这个锁就能用了。但存在一个问题:会存在服务 A 释放掉 服务 B 的锁的可能。

加上 owner

在特殊的场景:服务 A 获取到了锁,由于业务流程比较长或者网络延迟、GC卡顿等原因,导致锁过期,而业务还会继续进行,这时候,业务 B 已经拿到了锁,准备去执行。这个时候服务 A 恢复过来并做完了任务,就会释放锁,而 B 还在继续,就会导致服务 A 释放掉了 服务 B 的锁。

在真实的分布式场景中,可能存在几十个竞争者,上述情况发生的概率就会很高,导致同一份资源频繁被不同竞争者同时访问,分布式锁也就失去了意义。

发生这个问题的关键在于:竞争者可以释放掉其他竞争者的锁。所以我们可以给出进一步的解决方案:分布式锁需要满足谁申请谁释放原则,不能释放别人的锁,也就是说,分布式锁,是要有归属的。

引入 lua

加入 owner 后的版本,也还有一点点小问题。完整的流程是:竞争者获取到锁执行任务,执行完毕后检查锁是不是自己的,最后释放。

这些操作都不是原子化的,可能锁获取的时候还是自己的,删除的时候已经是别人的了。

这里就需要引入 Lua。

Redis + Lua,可以说是专门为解决原子问题而生的。

到了这里,分布式锁的前三个特性已经满足:对称性、安全性、互斥性。可以是一个可以用的分布式锁了,能满足大多数场景。

4、可靠性如何保证

还剩下可靠性没有解决。

针对一些异常情景,包括 Redis 挂掉、业务执行时间过长、网络波动等情况。

容灾考虑

前面我们谈及的内容,基本是基于单机考虑的,如果Redis挂掉了,那锁就不能获取了。这个问题该如何解决呢? ​ 一般来说,有两种方法:主从容灾和多级部署。

主从容灾

最简单的方式,就是为 Redis 配置从节点,当主节点挂掉了,从节点顶包。

主从切换的话,需要人工参与,会提高人力成本。不过 Redis 已经有成熟的解决方案,也就是哨兵模式,可以灵活自动切换,不再需要人工介入。

虽然一定程度解决了单点的容灾问题,但并不是尽善尽美的由于同步有时延,slave通过增加从节点的方式,可能会损失掉部分数据,分布式锁可能失效,这就会发生短暂的多机获取到执行权限

有没有更可靠的办法呢?

多机部署

如果对一致性高一些,可以尝试多机部署。比如 Redis 的 RedLock,大概思路就是多个机器,通常是奇数,达到一半以上同意才能算加锁成功,这样可靠性会向 ETCD 靠近。

ETCD:etcd:在前面的回答中已经介绍过,etcd是一个分布式键值存储系统,用于配置管理、服务发现和分布式协调。它是一个独立的开源项目,由CNCF维护,具有强一致性和高可用性,用于构建分布式系统的基础设施。

现在假设有5个Redis主节点,基本保证它们不会同时宕掉,获取锁和释放锁的过程中客户端会执行以下操作:

  1. 向5个Redis申请加锁;

  2. 只要超过一半,也就是3个Redis返回成功,那么就是获取到了锁。如果超过一半失败,需要向每个Redis发送解锁命令;

  3. 由于向5个Redis发送请求,会有一定时耗,所以锁剩余持有时间,需要减去请求时间。这个可以作为判断依据,如果剩余时间已经为0,那么也是获取锁失败;

  4. 使用完成之后,向5个Redis发送解锁请求。

这种模式的好处在于,如果挂了2台Redis,整个集群还是可用的,给了运维更多时间来修复。

另外,多说一句,单点Redis的所有手段,这种多机模式都可以使用,比如为每个节点配置哨兵模式,由于加锁是一半以上同意就成功,那么如果单个节点进行了主从切换,单个节点数据的丢失,就不会让锁失效了。这样增强了可靠性。

没有完全可靠的分布式锁

由于分布式系统中的三大困境,简称 NPC。

  1. N:Network Delay (网络延迟)网络延迟导致锁过期。

  2. P:Process Pause(进程暂停)比如发生 GC,导致锁超时。

  3. C:Clock Drift(时钟漂移)

相关推荐
沃尔威武1 小时前
数据库 Sinks(.net8)
数据库·.net·webview
Dreamboat¿2 小时前
SQL 注入漏洞
数据库·sql
Dontla2 小时前
go语言Windows安装教程(安装go安装Golang安装)(GOPATH、Go Modules)
开发语言·windows·golang
铁东博客3 小时前
Go实现周易大衍筮法三变取爻
开发语言·后端·golang
曹牧3 小时前
Oracle数据库中,将JSON字符串转换为多行数据
数据库·oracle·json
被摘下的星星3 小时前
MySQL count()函数的用法
数据库·mysql
末央&3 小时前
【天机论坛】项目环境搭建和数据库设计
java·数据库
徒 花3 小时前
数据库知识复习07
数据库·作业
素玥4 小时前
实训5 python连接mysql数据库
数据库·python·mysql
jnrjian4 小时前
text index 查看index column index定义 index 刷新频率 index视图
数据库·oracle