问题

怎样实现redis分布式锁?

回答
好的,咱们就来聊聊 Redis 如何实现分布式锁,这玩意儿在多实例、高并发的场景下,那可是个必备神器。我尽量讲得接地气点,避免那些生硬的技术术语,就好像咱们平时聊天一样。

为啥需要分布式锁?

你有没有想过,在一个并发环境下,好几个进程(你可以理解成同时在跑的几个程序)都想去修改同一个数据,比如一个共享的计数器,或者一个正在进行的活动,大家一起动手,那数据不就乱套了?这就好比街上的红绿灯,如果所有车都一窝蜂往前冲,那不得堵成一锅粥?分布式锁就是那个“红绿灯”,它能协调大家,让同一时间只有一个“玩家”能访问关键资源。

Redis 怎么搭这个“红绿灯”?

Redis 本身是个高性能的内存数据库,它的一些特性让它非常适合做分布式锁的“调度中心”。最核心的思路就是:谁先成功地在 Redis 里占住一个“位置”,谁就拥有了锁,其他人就得等。

核心实现思路:SETNX 命令

Redis 有一个叫做 `SETNX` 的命令,全称是 `SET if Not eXists`,意思就是“如果这个键不存在,就设置它”。这简直是为分布式锁量身定做的。

想象一下,我们用 Redis 里的一个键(比如叫做 `my_lock`)来代表一把锁。

1. 获取锁: 当一个进程想获取锁时,它就尝试执行 `SETNX my_lock some_value`。
如果 `my_lock` 原本不存在,`SETNX` 就会成功,返回 1,这个进程就成功拿到了锁。`some_value` 也很重要,后面会说。
如果 `my_lock` 已经存在了(说明别的进程已经拿到了锁),`SETNX` 就会失败,返回 0,这个进程就得乖乖等着。

2. 释放锁: 当拿到锁的进程完成了它的任务,它就需要释放这把锁,让别人有机会拿到。通常的做法是直接 `DEL my_lock`。

问题来了,这样做够“靠谱”吗?

你可能会问,上面这种方式听起来挺简单,但有没有什么坑?当然有!这就像咱们刚学会骑自行车,感觉挺好的,但真要骑远路,还得考虑刹车、变速什么的。

坑一:锁会一直被占用,万一持有锁的进程挂了呢?

如果一个进程在拿到锁后,因为某种原因(比如程序崩了,网络断了)没能正常释放锁,那这把锁就会永远被占用,其他进程就永远拿不到锁了。这相当于交通灯坏了,一直绿灯,结果一直堵。

解决方案:给锁设置一个过期时间(TTL)

Redis 提供了 `EXPIRE` 命令,可以给一个键设置过期时间。所以,我们在使用 `SETNX` 的时候,紧接着应该设置一个过期时间,比如 30 秒。

获取锁步骤升级:
1. `SETNX my_lock some_value` (尝试加锁)
2. 如果 `SETNX` 成功(返回 1),则立即执行 `EXPIRE my_lock 30` (设置 30 秒过期)。

这里又有一个小问题:`SETNX` 和 `EXPIRE` 是两个独立的命令。如果 `SETNX` 成功了,但在执行 `EXPIRE` 之前,进程挂了,那锁还是没有过期时间,还是会死锁。

更靠谱的获取锁方式:SET 命令的组合

Redis 5.0 以后,`SET` 命令可以直接支持设置过期时间和条件,这让加锁操作变得原子化,非常方便。

```
SET my_lock some_value NX PX 30000
```

`NX`: 仅当键不存在时,设置成功。
`PX 30000`: 设置过期时间为 30000 毫秒(也就是 30 秒)。

这样一来,加锁和设置过期时间就变成了一个原子操作,大大降低了死锁的风险。

坑二:谁设置的过期时间,谁才能删除锁?

上面我们提到,持有锁的进程需要释放锁,通常是 `DEL my_lock`。但问题是,如果锁因为过期而被删除了,而这时持有锁的进程恰好又执行完了任务,它再去执行 `DEL my_lock`,会不会把别人(新拿到锁的进程)的锁删掉?

这就像你放了一个计时沙漏,沙漏漏完了,锁就没了。但你以为锁还在你手里,你就去把别人的沙漏也打翻了,这当然不行。

解决方案:给锁加上一个“主人”标识,并用 Lua 脚本判断删除

1. `some_value` 的作用: 在 `SET my_lock some_value NX PX 30000` 这个命令里,`some_value` 就用来标识“谁”拿到了这把锁。通常我们会生成一个唯一的随机值(比如 UUID),并将其作为 `some_value` 传递。

2. Lua 脚本保证原子性删除: 为了确保只有持有锁的进程才能删除它自己的锁,我们需要使用 Lua 脚本。Redis 可以执行 Lua 脚本,脚本里的操作是原子的。

比如,释放锁的操作可以这样写成一个 Lua 脚本:

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

`KEYS[1]`:代表锁的键名,比如 `my_lock`。
`ARGV[1]`:代表锁的值(也就是我们之前设置的唯一随机值)。

当进程要释放锁时,它会执行这个 Lua 脚本,传入锁的键名和它自己持有的那个唯一随机值。脚本会先检查 Redis 中 `my_lock` 的值是否等于它传递过来的随机值,如果相等,说明锁确实还是它持有的, then 才执行 `DEL` 操作。否则,就什么都不做,返回 0。

这样,就完美解决了“误删他人锁”的问题。

坑三:续租(Renew Lease)

有些任务可能需要比锁的过期时间更长的时间才能完成。如果任务还没做完,锁就过期了,那么别的进程可能会抢到锁,导致数据不一致。

解决方案:定时续租

持有锁的进程可以启动一个独立的线程或进程,在锁过期前的一段时间(比如快过期了,还剩 10 秒时),再次执行加锁操作(`SET my_lock some_value NX PX 30000`),如果成功,就相当于“续命”了。

更高级的分布式锁实现:Redlock 算法

上面说的这些都是基于单个 Redis 实例的。如果你的 Redis 实例是主从复制,主宕机了怎么办?或者你的 Redis 集群之间可能出现短暂的不一致怎么办?

这时候,就需要更健壮的 Redlock 算法了。Redlock 的思路是在多个独立的 Redis 实例上同时尝试获取锁。只有在大部分 Redis 实例上都成功获取到锁,才算真正获得了分布式锁。释放锁时,也要在所有实例上都执行释放操作。

Redlock 的实现会更复杂一些,需要考虑网络延迟、实例可用性等多种因素。如果你追求极高的可靠性,可以深入研究 Redlock。但对于很多场景,基于单个 Redis 实例并配合 Lua 脚本的方案已经足够应对大部分并发需求了。

总结一下,一个比较完善的 Redis 分布式锁,你需要考虑:

1. 原子性加锁: 使用 `SET key value NX PX time` 来确保加锁和设置过期时间是原子操作。
2. 锁的唯一标识: 使用一个随机值作为锁的 value,防止误释放。
3. 原子性解锁: 使用 Lua 脚本,在释放锁时判断锁的值是否为自己持有。
4. 过期时间: 合理设置锁的过期时间,防止死锁。
5. 续租: 对于耗时操作,考虑实现锁的续租机制。

实践建议:

客户端库: 很多成熟的 Redis 客户端库(如 Java 的 Jedis、Redisson)已经内置了分布式锁的实现,可以直接调用,省去自己造轮子的麻烦。Redisson 的分布式锁实现尤其强大,支持自动续租、可重入等特性。
业务场景: 仔细评估你的业务场景对锁的需求,不要过度设计。有时候,简单的排他锁已经够用了,不需要 Redlock 那么复杂的机制。
监控: 部署了分布式锁后,一定要做好相关的监控,比如锁的争用情况、持有时间过长的锁等等,方便及时发现问题。

希望我这么讲,能让你对 Redis 分布式锁有个更清晰的认识。说到底,就是利用 Redis 的高性能和原子操作能力,为你的应用加一把“安全锁”。

网友意见

user avatar

一、写在前面

现在面试,一般都会聊聊分布式系统这块的东西。通常面试官都会从服务框架(Spring Cloud、Dubbo)聊起,一路聊到分布式事务、分布式锁、ZooKeeper等知识。

所以咱们这篇文章就来聊聊分布式锁这块知识,具体的来看看Redis分布式锁的实现原理。

说实话,如果在公司里落地生产环境用分布式锁的时候,一定是会用开源类库的,比如Redis分布式锁,一般就是用Redisson框架就好了,非常的简便易用。

大家如果有兴趣,可以去看看Redisson的官网,看看如何在项目中引入Redisson的依赖,然后基于Redis实现分布式锁的加锁与释放锁。

下面给大家看一段简单的使用代码片段,先直观的感受一下:

怎么样,上面那段代码,是不是感觉简单的不行!

此外,人家还支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构,都可以给你完美实现。

二、Redisson实现Redis分布式锁的底层原理

好的,接下来就通过一张手绘图,给大家说说Redisson这个开源框架对Redis分布式锁的实现原理。

(1)加锁机制

咱们来看上面那张图,现在某个客户端要加锁。如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器。

这里注意,仅仅只是选择一台机器!这点很关键!

紧接着,就会发送一段lua脚本到redis上,那段lua脚本如下所示:

为啥要用lua脚本呢?

因为一大坨复杂的业务逻辑,可以通过封装在lua脚本中发送给redis,保证这段复杂业务逻辑执行的原子性。

那么,这段lua脚本是什么意思呢?

KEYS[1]代表的是你加锁的那个key,比如说:

       RLock lock = redisson.getLock("myLock");     

这里你自己设置了加锁的那个锁key就是“myLock”。

ARGV[1]代表的就是锁key的默认生存时间,默认30秒。

ARGV[2]代表的是加锁的客户端的ID,类似于下面这样:

       8743c9c0-0795-4907-87fd-6c719a6b4586:1     

给大家解释一下,第一段if判断语句,就是用“exists myLock”命令判断一下,如果你要加锁的那个锁key不存在的话,你就进行加锁。

如何加锁呢?很简单,用下面的命令:

       hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1     

通过这个命令设置一个hash数据结构,这行命令执行后,会出现一个类似下面的数据结构:

上述就代表“8743c9c0-0795-4907-87fd-6c719a6b4586:1”这个客户端对“myLock”这个锁key完成了加锁。

接着会执行“pexpire myLock 30000”命令,设置myLock这个锁key的生存时间是30秒。

好了,到此为止,ok,加锁完成了。

(2)锁互斥机制

那么在这个时候,如果客户端2来尝试加锁,执行了同样的一段lua脚本,会咋样呢?

很简单,第一个if判断会执行“exists myLock”,发现myLock这个锁key已经存在了。

接着第二个if判断,判断一下,myLock锁key的hash数据结构中,是否包含客户端2的ID,但是明显不是的,因为那里包含的是客户端1的ID。

所以,客户端2会获取到pttl myLock返回的一个数字,这个数字代表了myLock这个锁key的剩余生存时间。比如还剩15000毫秒的生存时间。

此时客户端2会进入一个while循环,不停的尝试加锁。

(3)watch dog自动延期机制

客户端1加锁的锁key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?

简单!只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

(4)可重入加锁机制

那如果客户端1都已经持有了这把锁了,结果可重入的加锁会怎么样呢?

比如下面这种代码:

这时我们来分析一下上面那段lua脚本。

第一个if判断肯定不成立,“exists myLock”会显示锁key已经存在了。

第二个if判断会成立,因为myLock的hash数据结构中包含的那个ID,就是客户端1的那个ID,也就是“8743c9c0-0795-4907-87fd-6c719a6b4586:1”

此时就会执行可重入加锁的逻辑,他会用:

       incrby myLock 8743c9c0-0795-4907-87fd-6c71a6b4586:1 1     

通过这个命令,对客户端1的加锁次数,累加1。

此时myLock数据结构变为下面这样:

大家看到了吧,那个myLock的hash数据结构中的那个客户端ID,就对应着加锁的次数

(5)释放锁机制

如果执行lock.unlock(),就可以释放分布式锁,此时的业务逻辑也是非常简单的。其实说白了,就是每次都对myLock数据结构中的那个加锁次数减1。

如果发现加锁次数是0了,说明这个客户端已经不再持有锁了,此时就会用:“del myLock”命令,从redis里删除这个key。

然后呢,另外的客户端2就可以尝试完成加锁了。

这就是所谓的分布式锁的开源Redisson框架的实现机制。

一般我们在生产系统中,可以用Redisson框架提供的这个类库来基于redis进行分布式锁的加锁与释放锁。

(6)上述Redis分布式锁的缺点

其实上面那种方案最大的问题,就是如果你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master slave实例。

但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master。接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。

此时就会导致多个客户端对一个分布式锁完成了加锁。这时系统在业务语义上一定会出现问题,导致各种脏数据的产生。

所以这个就是redis cluster,或者是redis master-slave架构的主从异步复制导致的redis分布式锁的最大缺陷:在redis master实例宕机的时候,可能导致多个客户端同时完成加锁。

本文分享自华为云社区《redis分布式锁实现原理学习》,原文作者:minjie 。


点击关注,第一时间了解华为云新鲜技术~

类似的话题

  • 回答
    好的,咱们就来聊聊 Redis 如何实现分布式锁,这玩意儿在多实例、高并发的场景下,那可是个必备神器。我尽量讲得接地气点,避免那些生硬的技术术语,就好像咱们平时聊天一样。为啥需要分布式锁?你有没有想过,在一个并发环境下,好几个进程(你可以理解成同时在跑的几个程序)都想去修改同一个数据,比如一个共享的.............
  • 回答
    想要实现二手捷达零成本用车,这可不是个简单任务,但也不是完全不可能,关键在于你如何去“玩”这辆车。咱们抛开那些条条框框,直接聊聊怎么把它盘活,让它能跑起来,而且不掏一分钱。首先,得承认,零成本是个理想状态,尤其是在初始购车阶段。但咱们的目标是“用车”零成本,也就是跑在路上不花钱,这就得从几个方面下手.............
  • 回答
    这个问题听起来很简单,但背后涉及了计算机处理数字的一些本质。咱们就来聊聊,为什么咱们平时做除法再乘法,有时候会“跑偏”,以及怎么才能让它“回归正轨”。为什么会“跑偏”?电脑处理数字,尤其是小数(也就是我们说的浮点数),并不是像咱们在纸上写写画画那么精确。它使用的是一种叫做“浮点数表示法”的系统。这个.............
  • 回答
    想要实现强人工智能,这可不是一件简单的事,它更像是人类大脑这场复杂交响乐的二次创作,而且我们手中只有模糊的乐谱和一些零散的乐器。不过,我们可以试着把这个过程拆解开来,就像一个经验丰富的老匠人,一点点告诉你他会怎么着手。首先,我们得明确一下我们想要的是什么。强人工智能,或者叫通用人工智能(AGI),它.............
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    .......
  • 回答
    哈哈,你这个问题问得挺有意思的!让咱们掰开了揉碎了聊聊即时战略游戏,特别是像《魔兽争霸3》(WAR3)这种经典里的AI是怎么回事儿。别担心,我不会让你看到一堆冷冰冰的算法堆砌,咱们就像老朋友唠嗑一样,把这事儿给你讲透了。首先,得明白一个事儿:WAR3 的 AI,其实是个精明的“指挥官”你玩WAR3的.............
  • 回答
    地铁隧道里的动态广告,那种在飞驰的列车窗外掠过的、仿佛电影场景般的画面,其实是利用了人眼的视觉暂留原理,巧妙地结合了物理空间和数字技术。设想一下,你坐在飞驰的地铁列车里,窗外呼啸而过的景色会因为列车的速度而变得模糊,形成一种流动的效果。而隧道里的动态广告,就是捕捉了这一点,并加以创造。广告的载体并非.............
  • 回答
    微信红包的随机算法,这玩意儿说起来跟咱们平时玩儿的“抓阄”有点意思,但人家背后可是下了不少功夫的。我尽量给你掰扯得明白点,不整那些虚头巴脑的。核心思路:怎么让钱分得“不均匀”?咱们都知道,微信红包有个特点,就是发出去的钱,每个人抢到的金额都不一样,有的人多,有的人少,这才叫“随机”。这背后的算法,本.............
  • 回答
    好的,咱们来聊聊火车转向架这玩意儿,它是怎么把沉甸甸的车体稳稳托住,又怎么让火车在轨道上灵活变身,这其中的门道可不少。转向架:车体的大脚丫,也是灵活的枢纽你可以把转向架想象成火车车体的“大脚丫”,它承担着整个车体的重量,并且直接接触铁轨。但它可不是简单的一对轮子,而是由一套精巧的结构组成的,主要目的.............
  • 回答
    咱们来聊聊数据库事务,特别是它那两个很关键的特点——原子性和一致性,看看它们是怎么做到让咱们的数据“稳如泰山”的。原子性:要么全做,要么全不做你可以把原子性想象成一个“全有或全无”的开关。一个事务,就像一个完整的操作流程,比如你从银行账户A转账100块到账户B。这个过程包含两个核心步骤:首先,从账户.............
  • 回答
    .......
  • 回答
    咱们国家是社会主义国家,这点大家伙心里都明白。说起共同富裕,那更是咱们奋斗的大方向,也是凝聚人心的目标。道理很简单,就是大家伙日子都越过越好,差距不能太大,日子苦的人得有盼头,有能力的人得带着大家伙一起奔小康。“先富带动后富”,这个提法提出来的时候,咱们国家刚改革开放不久,很多地方都还没富裕起来。那.............
  • 回答
    评价中科大潘建伟团队在「祖冲之号」量子计算原型机上展示的量子计算优越性中科大潘建伟团队在「祖冲之号」量子计算原型机上展示的量子计算优越性,是量子计算领域一项里程碑式的成就,具有极其重要的科学和技术意义。总的来说,这是一次成功的“量子优越性”或“量子霸权”展示,表明在特定计算任务上,现有的量子计算机已.............
  • 回答
    为什么说房地产是 M2 的蓄水池?理解房地产为何成为 M2(广义货币供应量)的蓄水池,需要从货币的发行与流通、信贷扩张、以及资产配置等角度进行分析。简单来说,房地产作为一种重要的资产类别,能够吸收并锁定大量的社会财富,从而在一定程度上控制货币的流通速度,起到“蓄水”的作用。以下是详细的解释:1. 货.............
  • 回答
    中方提出的健康码国际互认,无疑是一个极具前瞻性和雄心勃勃的设想。如果能够顺利实现,其意义将是深远的,它不仅仅是疫情常态化防控下的一个技术工具,更可能重塑全球旅行、经贸往来乃至国际关系的格局。健康码国际互认的深远意义:1. 重塑全球旅行与经济复苏的引擎: 便利化和恢复跨境流动: 当前,各.............
  • 回答
    在职场中,与领导建立顺畅、高效的沟通,并在此基础上实现“向上管理”,是每个渴望进步的职场人士都应该掌握的关键技能。这不是简单的拍马屁或溜须,而是基于尊重、理解和共同目标的策略性互动。下面我将从多个维度,详尽地阐述如何与领导进行有效沟通,从而实现你的向上管理目标。核心理念:你是领导的“解决方案提供者”.............
  • 回答
    实现一个HTTP服务器需要掌握网络编程、HTTP协议、服务器架构设计等知识,并根据具体需求选择编程语言和工具。以下是详细的步骤和所需知识: 一、HTTP服务器的核心功能1. 接收客户端请求 解析HTTP请求行(方法、路径、协议版本) 解析请求头(如 `UserAgent`、`Ac.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有