使用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

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

python设置环境变量路径实现过程

《python设置环境变量路径实现过程》本文介绍设置Python路径的多种方法:临时设置(Windows用`set`,Linux/macOS用`export`)、永久设置(系统属性或shell配置文件... 目录设置python路径的方法临时设置环境变量(适用于当前会话)永久设置环境变量(Windows系统