使用Redis SETNX 命令实现分布式锁”

2023-10-08 22:58

本文主要是介绍使用Redis SETNX 命令实现分布式锁”,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用Redis的 SETNX 命令可以实现分布式锁,本文介绍其实现方法。

直接进入正题,现在分布式的应用场景很多,为了保持数据的一致性,经常碰到需要对资源加锁的情形。 利用redis来实现分布式锁就是其中的一种实现方案。

SETNX命令简介
命令格式
SETNX key value
1
将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值
设置成功,返回 1 。
设置失败,返回 0 。

示例
redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 设置成功
(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0

redis> GET job                   # 没有被覆盖
"programmer"

SETNX分布式锁实现方案
利用SETNX的特性,很容易的想到,在需要加锁的时候,调用SETNX命令,如果返回了1,表示设置成功,获得了当前锁,之后做一些想要的操作,完成之后调用DEL命令释放锁。

redis> SETNX lock true    # 获得锁成功
(integer) 1
... do thing ...
redis> DEL lock    # 释放锁
(integer) 1


但是这样存在一个问题,如果在执行DEL命令之前,当前程序发生错误,那么这个锁就永远得不到释放,其他程序也永远无法加锁成功。

于是我们可以在加锁之后为这个锁设置一个过期时间,过期时间之后,如果没有释放,就自动删除,防止锁被一直占用。

redis> SETNX lock true    # 获得锁成功
(integer) 1
redis> EXPIRE lock 5    # 设置5秒的过期时间
(integer) 1
... do thing ...
redis> DEL lock    # 释放锁
(integer) 1
但是这样还是有问题,如果在SETNX和EXPIRE之间程序又发生了错误,当前锁又无法释放。所以根本原因还是需要一个原子的操作,在获得锁的同时能够同时设置锁的过期时间。

为了解决这个问题,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行, 这个可以在下一篇介绍。 本文介绍另一种方式。

SETNX设置锁
在设置锁的时候,我们可以利用锁的值来实现过期的特性

SETNX lock  <current Unix time + lock timeout>
1
我们不是设置一个简单的值到lock中,而是将过期的时间写入到lock中。 获得锁的判断条件仍旧是跟之前一样, 如果返回了1的话,表示获得了锁,可以进行下一步的操作。

判断过期条件
正常情况下,操作完成之后,仍旧执行DEL操作将当前锁释放。那么如果当前程序发生了错误退出了,当前锁没有正常释放,其他的进程如何获得锁呢。

假设上一个进程加锁之后异常退出,没有释放锁。当前的进程想要加锁,在调用SETNX的时候发现加锁失败,然后需要调用GET命令获得当前锁的值,即上一个进程写入的过期时间。 如果获得的过期时间未到,那么当前进程继续等待; 如果锁的过期时间已经到了,很大的概率上一个获得锁的进程已经发生了错误,因为我们这个过期时间一般会设置的比正常的运行时间要长。在这种情况下, 当前进程可以重新写入这个锁并进行后续的操作。

解决竞争条件
但是这样又带来一个新的问题: 假设有P1和P2两个进程同时想获得锁,他们都检测到了当前的锁已经过期了, 他们可以写入,他们调用SET命令写入都会成功,那么如果决定到底是哪个进程获得了锁呢。

所以在这边重新写入的时候不能简单的调用SET命令, 还有另一个命令可以考虑: GETSET。GETSET命令在设置值的同时,会将设置之前的值返回。

仍旧考虑刚才的情形, P1和P2同时在竞争锁,发现锁的时间T已经过期了,然后他们同时调用GETSET命令设置新的锁。假设P1先设置成功时间T1,那么调用GETSET得到的值就是T; P2调用GETSET虽然将锁的时间设置成了T2,但是他得到的值是T1。

通过判断GETSET返回的值,就能判断自己是否获得了锁。如果返回的值仍然是一个过期的时间,那么说明正确的加锁了;否则的话,说明正好有别的进程已经设置了锁,当前进程只是更新了一下锁而已,就继续等待。

可能会说这边有一个小问题,P1设置的锁的过期时间被P2更改了。考虑到产生这种竞态条件的时候肯定时间间隔是非常小的, 即使重新设置了过期时间,这种很短的时间修改在大多数情况下都可以忽略不计。

伪代码
所以,我们能够得到最终的一个过程,用伪代码表示

while 1:
    lock = redis.SETNX(key, time.now() + timeout)
    if lock == 1:
        // 获得锁
        break
    lock_ts = redis.GET(key)
    if (lock_ts < time.now()) && (redis.GETSET(key, time.now() + timeout) < time.now()):
        // 锁已经过期,用GETSET重新写锁
        // 返回的原来的时间仍旧过期,说明加锁成功
        break
    else:
        sleep
        
.... do something ...

// 完成之后释放锁
redis.DEL(key)
 

这篇关于使用Redis SETNX 命令实现分布式锁”的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/168562

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本