news 2026/7/15 21:15:35

Redis典型应用 - 分布式锁,引入过期时间,Lua脚本原子解锁操作,看门狗机制实现过期时间动态续约,Redlock算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis典型应用 - 分布式锁,引入过期时间,Lua脚本原子解锁操作,看门狗机制实现过期时间动态续约,Redlock算法

目录

  • 什么是分布式锁?
    • 多节点对共享资源的访问
  • 分布式锁的基础实现
    • 实现步骤
    • 引入过期时间
    • 引入校验机制
    • 引入 Lua
      • 对 Lua 的介绍
    • 引入看门狗 (watch dog)
      • 两个时间概念
      • 具体例子
      • 注意点(对锁的续约时间理解)
    • 引入 Redlock 算法
      • 解决方案

什么是分布式锁?

多节点对共享资源的访问

在分布式系统中,也会涉及到多个节点对共享资源进行访问的情况,这个时候我们需要借助来实现对共享资源的互斥访问,避免出现类似前面的“线程安全问题”

而 Java 的synchronized或者 C++的std::mutex,这样的锁只能在同一个进程中生效,也就是可以实现在一个进程中,多个线程对共享资源的互斥访问,但在分布式系统的多主机,多服务场景下就不能发挥出作用了

此时就需要使用分布式锁了

分布式锁实际上是利用一个公共服务器来记录锁的状态

可以使用Redis,也可以是 MySQL 或者 ZooKeeper,也可以是我们自己写的服务

分布式锁的基础实现

简单实现: 通过一对键值对来标记锁的状态
加锁:设置一对特殊的键值对

解锁:删除这对键值对

举一个具体的分布式系统买票场景,在 12306 上,每一个车次的车票都是有限的,当我们进行买票的时候,会执行以下逻辑

  1. 判断余票数量是否大于 0
  2. 大于 0 ,则余票数量 - 1 来更新余票数量

实现步骤:

  1. 在每一个买票服务器执行具体的买票逻辑之前,先要访问 Redis, 往 Redis 中设置一对特殊的键值对,key 是车次 001,value 是任意值如 1,如果上面设置键值对成功,我们就视为加锁成功,继续去执行后面具体的买票逻辑,执行完之后,删除这个已经设置好的键值对,视为解锁成功。
  2. 当买票服务器 1 设置好键值对,加锁成功之后,买票服务器 2 也想买票,但发现 key:车次 001 已经存在,加锁失败,则买票服务器 2 需要等到买票服务器 1 解锁之后,才能成功加上锁
  3. 在上面两个步骤中,Redis 提供的 setnx 命令刚好适合我们的场景需要:当 key 不存在时才设置,key 存在时不设置

引入过期时间:

在上面的场景中,当一个买票服务器获取到锁之后,如果此时服务器意外宕机了,则会导致解锁操作不能被执行,其他服务器则始终无法获取到锁,不能进行买票

为了应对这个问题,我们可以在一开始加锁(设置键值对)的时候同时指定过期时间,使用如下命令

set key value[expirationEXseconds|PXmilliseconds][NX|XX]

在这里面中,我们需要注意:设置键值对和指定过期时间必须使用一条命令

  1. Redis 的单条命令执行具有原子性,当在执行 incr 命令时,其他客户端也刚好在执行 incr 操作,此时的执行顺序是串行化的
  2. Redis 的多条命令执行不具有原子性,在 Redis 事务中,当一条命令执行成功,另一条命令执行失败之后,此时并不会回滚。
set key value expire key seconds

所以当使用上面两条命令设置锁成功,指定锁过期时间失败之后,此时还是导致锁不能被释放,其他服务器始终无法获取到锁

引入校验机制:

在上面场景中,买票服务器 1 加上的锁,可能会被其他买票服务器释放

因此我们可以引入一个校验 ID,例如买票服务器编号是 001,将一开始加锁中的 value 设置成 001,代表是 001 号服务器加上的锁,在后面释放锁的时候,先通过 value 值来判断是不是当前服务器加上的锁,如果是则可以释放锁,如果不是则不能释放锁

可以用一段伪代码来描述上面的逻辑:

1Stringkey=[要加锁的资源 id];2StringserverId=[服务器的编号];34// 加锁,设置过期时间为 10s5redis.set(key,serverId,"NX","EX","10s");67// 执行各种业务逻辑,比如修改数据库数据。8doSomething();910// 解锁,删除 key。但是删除前要检验下 serverId 是否匹配。11if(redis.get(key)==serverId){12redis.del(key);13}

从这段伪代码中可以看出,get 和 del 这两个操作不是原子的

引入 Lua:

为了使锁解锁操作是原子的,我们使用 Redis 的 Lua 脚本功能

对 Lua 的介绍:

🎁 Lua 也是一个编程语言。读作 “撸啊”。是葡萄牙语中的 “月亮” 的意思。(出自于 Lua 官方文档 https://www.lua.org/about.html)

Lua 的语法类似于 JS,是一个动态弱类型的语言。Lua 的解释器一般使用 C 语言实现。Lua 语法简单精炼,执行速度快,解释器也比较轻量(Lua 解释器的可执行程序体积只有 200KB 左右)。

因此 Lua 经常作为其他程序内部嵌入的脚本语言。Redis 本身就支持 Lua 作为内嵌脚本。

使用 Lua 脚本完成上述解锁功能:

1ifredis.call('get',KEYS[1])==ARGV[1]then2returnredis.call('del',KEYS[1])3else4return05end;

上述代码可以编写成一个.lua后缀的文件,由redis-cli或者redis-plus-plus或者jedis等客户端加载,并发送给 Redis 服务器,由 Redis 服务器来执行这段逻辑。

一个 lua 脚本会被 Redis 服务器以原子方式来执行。

引入看门狗 (watch dog):

两个时间概念:

lockWatchdogTimeout:锁每次被续约之后的有效期(也就是还有多长时间才到期 )

续约触发间隔:看门狗多长时间进行一次检查

在上面买票场景中,有可能还存在一个问题:当这个服务器已经加上锁之后,可能还没有执行完具体的业务逻辑之后,锁便过期了,也就是我们要动态控制锁的过期时间.

  1. 不能让锁的过期时间太长,不然当一个服务器发生宕机之后,不能完成解锁,其他服务器也就不能获取到锁
  2. 不能让锁的过期时间太短,不然当这个服务器还没有执行完业务逻辑之后,锁就过期了,这把锁便失效了,其他买票服务器也可以获取到这把锁,不能实现资源的互斥访问,也就会导致超卖问题(一张票卖给两个人)

所谓 watch dog,本质上是加锁的服务器(业务服务器)上的一个单独线程,负责对锁过期时间完成续约操作

具体例子:

初始情况下设置锁过期时间为 10s**(****lockWatchdogTimeout)**.同时设定看门狗线程每隔 3s 检测一次

那么当时间间隔达到 3s 的时候,看门狗就会判定当前任务是否完成.

  • 如果当前任务已经完成,则会使用 Lua 脚本完成原子解锁
  • 如果当前任务还没有完成,看门狗线程则会对锁过期时间进行续约操作,重新设置这个锁的过期时间为10s
注意点:(对锁的续约时间理解)

当进行续约操作时,此时锁的有效时间不是累计增加,而是重新设置为一开始指定的有效期

如一开始指定有效期为 10s,看门狗每隔 3s 进行一次检查

  1. 锁设置成功,有效时间为 10s
  2. 第一次续约,锁的有效时间为 10s
  3. 第二次续约,锁的有效时间为 10s
  4. 第三次续约,锁的有效时间为 10s

引入 Redlock 算法:

实践中的 Redis 一般是以集群的方式部署的(至少是主从的形式,而不是单机),有可能会出现以下极端情况:

服务器1 向 master 节点进行加锁操作. 这个写入 key 的过程刚刚完成,master 挂了; slave 节点升级成了新的 master 节点. 但是由于刚才写入的这个 key尚未来得及同步给 slave 呢, 此时就相当于 服务器1 的加锁操作形同虚设了,服务器2 仍然可以进行加锁(即给新的 master 写入 key. 因为新的 master 不包含刚才的 key).

解决方案:

我们引入一组 Redis 节点. 其中每一组 Redis 节点都包含一个主节点和若干从节点. 并且组和组之间存储的数据都是一致的, 相互之间是 “备份” 关系(而并非是数据集合的一部分, 这点有别于 Redis cluster).

加锁的时候, 按照一定的顺序, 写多个 master 节点. 在写锁的时候需要设定操作的 “超时时间”. 比如 50ms. 即如果 setnx 操作超过了 50ms 还没有成功, 就视为加锁失败。

如果给某个节点加锁失败, 就立即再尝试下一个节点.

当加锁成功的节点数超过总节点数的一半, 才视为加锁成功.

这样的话, 即使有某些节点挂了, 也不影响锁的正确性.

同理, 释放锁的时候, 也需要把所有节点都进行解锁操作. (即使是之前超时的节点, 也要尝试解锁, 尽量保证逻辑严密).

简而言之, Redlock 算法的核心就是,加锁操作不能只写给一个 Redis 节点, 而要写个多个!! 分布式系统中任何一个节点都是不可靠的. 最终的加锁成功结论是“少数服从多数的”.

由于一个分布式系统不至于大部分节点都同时出现故障, 因此这样的可靠性要比单个节点来说靠谱不少.

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 21:15:05

新材料制造工业数据中台解决方案

某新材料制造型企业拥有多个生产厂房及自动化生产设备,涵盖配料、成型、切割、中涂、分色、包装等工艺流程。为全面推进数字化转型,企业计划构建数字化车间管理机系统,重点在于全面采集各工艺及产线设备数据,并实现对设备监控、故…

作者头像 李华
网站建设 2026/7/15 21:13:47

构建高性能存储网络:NVMe-oF、SPDK与RDMA的融合实践

1. 为什么需要高性能存储网络?现代数据中心对存储性能的要求越来越高。传统存储架构在应对AI训练、高频交易、实时分析等场景时,往往面临延迟高、吞吐低的瓶颈。想象一下,当你在玩在线游戏时,如果每次加载地图都要等待几秒钟&…

作者头像 李华
网站建设 2026/7/15 21:13:03

2026免费数字人平台怎么选:基于功能、成本、体验的实测选型框架,新手、老板IP和商用团队分别看什么

2026免费数字人平台怎么选:基于功能、成本、体验的实测选型框架,新手、老板IP和商用团队分别看什么 如果你正在搜“免费数字人平台怎么选”,很容易被两类内容带偏。 一类是单纯的排行榜:把平台从第一名排到第十名; 另一…

作者头像 李华
网站建设 2026/7/15 21:12:05

HTTP状态码404:从“死链接”到“软404”的SEO陷阱与实战修复

1. 404错误的本质:从"死链接"到"软404"的认知升级当你在浏览器地址栏输入一个网址,却看到"404 Not Found"的提示时,这意味着服务器无法找到你请求的资源。这种标准404错误就像图书馆里找不到的书——系统明确告…

作者头像 李华
网站建设 2026/7/15 21:10:32

小白程序员转行AI必看:Agent开发vs大模型开发,谁更适合你?

本文详细对比了大模型开发与Agent开发的区别,指出大模型开发薪资高但门槛极高,适合顶尖人才;而Agent开发则更注重AI落地,学习曲线平缓,适合广泛人群。文章建议初学者关注Agent开发,抓住行业发展红利期&…

作者头像 李华
网站建设 2026/7/15 21:09:58

Gemini 3.5 到底适合做什么?2026最新大模型实用边界与选型攻略

在大模型百花齐放的今天,Google 的 Gemini 系列凭借原生多模态和超长上下文在行业中独树一帜。许多开发者和内容从业者在面对视频分析、多模态检索等复杂任务时,开始通过 AI 模型聚合平台 yingcaiai.com 切换到 Gemini 3.5 进行实测对比。那么&#xff0…

作者头像 李华