Redis使用setnx实现分布式锁及其问题、优化

redis setnx解决定时任务多节点部署并发问题(分布式锁) 在一些大的互联网平台,通常都会使用定时任务处理一些周期性的业务,而为了保障系统的高可用性,定时任务也会多节点部署,而解决多节点并发问题(分布式锁),大家通常会想到使用缓存,如redis,但是如果使用set / get是无法解决问题的,同样会出现并发问题,redis有专门的解决分布式并发问题的方法,就是setnx命令,很好用,下面介绍一下java实现redis分布式锁的方法。 1. Redis S... 阅读详情

最近在工作中用到了分布式锁,然后查了很多分布式锁的实现方式。比较熟悉redis或者说,redis的用法比较简单,所以查了一下redis使用setnx实现分布式锁的方式。其中有一篇文章搜索到的次数最多,多到我不知道哪个是原创文章,就贴一下看到的链接吧https://blog.youkuaiyun.com/lihao21/article/details/49104695。

reids > setnx(key,value)  //设置key.

redis > delete(key)  //将key删除

当key存在的时候,设置会失败,返回-1。当key不存在的时候,设置成功,返回0。

实现方式一:

lock_key = "distribute_lock"
def get_lock():
      while True:
          lock = redis_client.setnx(lock_key,1)
          if lock: # 设置成功
            	break
          else:
           	 time.sleep(0.5)  #如果没有获取成功,等待0.5继续获取,当然这个地方你也可以设置重试次数。
	return True
if get_lock():
    # do your work,处理临界资源
    redis_client.delete(lock_key) //为防止死锁,处理完临界资源要及时释放锁。

可以看一下这个代码有什么问题?

问题出在了,最后锁的释放,假如在处理临界资源的过程中,进程挂掉了或者在执行删除操作的时候redis链接断掉了,那么这个分布式锁将永远得不到释放,进而产生死锁。所以接下来的优化是如果进程挂掉了,能够及时的释放锁,你想到了什么?超时机制。

实现方式二:

为了避免出现方式一的问题,所以加入了超时机制。

setnx(key, <current Unix time + lock timeout + 1>)

本质就是将key的value值设置成一个超时时间,按照方式一流程:

lock_key = "distribute_lock"
def get_lock():
    while True:
      lock = redis_client.setnx(lock_key,<now_time+ lock timeout + 1>) # 直接设置成功
      if lock: # 设置成功
      		break
      now_time = <current Unix time + lock timeout + 1>
      lock_time_out = redis.client.get(lock_key)
      if now_time > lock_time_out: #检查是否超时
      	  # 如果已经超时
          redis_client.delete(lock_key) # 将这个key删除,进行重新设置。
          lock = redis_client.setnx(lock_key,<now_time+ lock timeout + 1>)
      if lock: # 设置成功
      		break
      else:
      	time.sleep(0.5)  #如果没有获取成功,等待0.5继续获取,当然这个地方你也可以设置重试次数。
	return True
if get_lock():
    # do your work,处理临界资源
    redis_client.delete(lock_key) //为防止死锁,处理完临界资源要及时释放锁。

继续思考一下,这又会出现什么问题?

  1. 假如进程p1获取到了锁,同时进程p2和p3在不断的检测锁是否已经超时。
  2. 然后p1进程挂掉了,没有及时删除锁。
  3. 过了一段时间,p2和p3同时检测到了这个锁已经超时,即程序中now_time > lock_time_out都成立。
  4. 首先p2将锁删除了,然后将锁设置了超时时间,致此p2获取到了锁。
  5. p3也检测到了锁超时了,只不过它执行速度比慢,直接将锁删除了,实际上p3删除的是p2设置的锁,这个时候问题就出现了,p3和p2都获取到了锁。

实现方式三

实现方式二的关键问题是什么?是p3在删除锁的时候,没有检查是否又有新的进程获取到了该锁。
为了避免这种情况,p3在执行set操作的时候,用这个命令:

getset(lock_key,now_time+lock timeout + 1)

这个命令会返回旧,然后设置成新的值。在set之前判断一下当前时间是否大于lock_key的旧值。如果大于,说明已经超时,获取到了锁。假如再次出现上面的情况,p2和p3同时检测到锁的超时,然后p2删除、并获取到了锁。p3执行getset操作,然后当前时间和lock_key的旧值(p2设置的)比较,当前时间小于旧值。获取锁失败,继续下一轮的等待。

其次最后删除的时候,也不能像前几次一样直接删除。要先判断一下,当前时间小于锁的超时时间的话在删除。避免删除其他进程设置的锁。程序如下:

def get_lock():
    LOCK_TIMEOUT = 3
    lock = 0
    lock_timeout = 0
    lock_key = 'distribute_lock'

    # 获取锁
    while lock != 1:
        now = int(time.time())
        lock_timeout = now + LOCK_TIMEOUT + 1
        lock = redis_client.setnx(lock_key, lock_timeout)
        if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout)):
            break
        else:
            time.sleep(0.001)
if get_lock():
    # dou your lock.处理临界资源()

    # 释放锁
    now = int(time.time())
    if now < lock_timeout:
        redis_client.delete(lock_key)

继续看,有什么问题?

  1. 为什么要+1 current Unix time + lock timeout + 1 ?
    +1是因为在 Redis 2.4 版本中,过期时间的延迟在 1 秒钟之内 —— 也即是,就算 key 已经过期,但它还是可能在过期之后一秒钟之内被访问到,而在新的 Redis 2.6 版本中,延迟被降低到 1 毫秒之内。

  2. 这段代码问题在哪里?1) 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。 2)当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。3) 锁不具备拥有者标识,即任何客户端都可以解锁。

(1)首先第一个问题是存在的,但是一般对于同一个分布群的项目,时间肯定是同步的,而且你见过哪台机器时间是用本地时间的?一般都是联网同步互联网全球设定的时区时间。 (2)Redis是单线程的,所以不存在你说的这种情况。是不可能同时执行的。即使客户端A,B,C 在同一时刻(精确到纳秒)发送了getset给redis。redis也会按照队列顺序依次执行。因此绝对保证只有一个客户端获得锁并进行业务处理最后释放锁,其他客户端一定会返回一个大于当前时间的结果,从而导致或许锁失败,也就不可能出现其他任何客户端能够解锁。你可能会说,“同时”。但是你说“同时”的时候,我就知道,兄弟你对redis的底层没有了解到这一层面,redis是强制单线程的。除非你改人家源码,就算你改人家源码,如果你改了人家的单线程设计理念,就等于说你不赞同redis作者的理念。然而,redis的单线程,却没有影响它的高性能。

  1. 实践中我发现的问题.
if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout))

会出现bug,因为当锁被删除之后,如果正好处于redis_client.get(lock_key)检查的时候,这个时候锁被删除了,所以redis_client.get(lock_key)为None,int(None)会出现类型转换错误。
然后这个时候如果为None的话,可以直接赋值为0,这样也不会出现问题,因为本身这个锁就被删除了,和时间超时是一样的,所以提出了优化。

if lock == 1 or (now > int(redis_client.get(lock_key) or 0)) and now > int(redis_client.getset(lock_key, lock_timeout) or 0)

其次为了便于使用,我还优化成了一个类:

class DistributeLock(object):

    def __init__(self, lock_key=None, lock_timeout=2):
        """
        :param lock_key: 分布式锁的key
        :param lock_timeout: 锁的超时时间
        """
        self.LOCK_TIMEOUT = lock_timeout
        self.lock = 0
        self.lock_timeout = 0
        self.lock_key = lock_key  #  # 分布式锁的key
        self.redis_client = #初始化redis客户端。

    def __enter__(self):
        # 获取锁
        while self.lock != 1:
            now = int(time.time())
            self.lock_timeout = now + self.LOCK_TIMEOUT + 1
            lock = self.redis_client.setnx(self.lock_key, self.lock_timeout)
            if lock == 1 or (now > int(self.redis_client.get(self.lock_key) or 0)) and now > int(
                    self.redis_client.getset(self.lock_key, self.lock_timeout) or 0):
                break
            else:
                time.sleep(0.001)

    def __exit__(self, ex_type, ex_value, traceback):
        # 释放锁
        now = int(time.time())
        if now < self.lock_timeout:
            self.redis_client.delete(self.lock_key)
# 使用方式:
	with DistributeLock(lock_key="your_distribute_lock",lock_timeout=3):
    	# do your work.
        do_work() # 不用在关心锁的获取或者释放。
其实以上,还有有些问题:就是没有考虑redis挂掉或者主从切换的情况,后续再更新。
关注我,让我们一起成长。

在这里插入图片描述

Redis实战—分布式锁(setnx、redisson) 本文详细介绍了Redis分布式锁的实现,同时对 “分布式锁误删” 与 “分布式锁原子性” 问题进行了讨论和解决。 阅读详情

相关推荐

Redis实现分布式锁

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

m0_46574072的博客 1223

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

SETNX命令简介 SETNX key value 将key的值设为value,并且仅当key不存在。 若给定的key已经存在,则SETNX不做任何操作。 SETNX 是SET if Not eXists的简写。 返回整数,具体为 1,当 key 的值被设置 0,当 key 的值没被设置 使用SETNX实现分布式锁 多个进程执行以下Redis命令: SETNX lock.foo <current Unix time + lock timeout + 1> 如果 SETNX 返回1,说

笔记 1923

Cgroup对进程所占用的CPU与内存资源同时进行限制的测试

测试环境:Ubuntu。处理器数量:1CPU。内存大小:2048MB。 以下是测试程序mem_cpu_test.c的代码。 将mem_cpu_test.c编译为可以运行的程序mem_cpu_test。 运行程序,并用top命令查看mem_cpu_test所占用的系统资源情况,可以看到,mem_cpu_test使用了50%左右的内存资源,也就是1000MB左右,并占用了接近全部的CPU资源。 关闭以上的程序后,在Cgroup子系统memory和cpu下分别创建进程组test1和test2。 并设置tes

qq_44998067的博客 1154

讲一讲Redis分布式锁的实现?

这样做可以确保在申请锁后的一段时间内,如果锁的持有者在该时间内没有更新锁的过期时间,锁将会自动过期,从而防止锁被永久占用。看上面这张图,加入key的失效时间是10s,但是客户端C在拿到分布式锁之后,然后业务逻辑执行超过10s,那么问题来了,在客户端C释放锁之前,其实这把锁已经失效了,那么客户端A和客户端B都可以去拿锁,这样就已经失去了分布式锁的功能了!上面的命令执行时,每个客户端在释放锁时,并没有进行严格的验证,存在释放别人锁的潜在风险。在这种情况下,客户端将永远占用该锁,其他客户端将无法获取该锁。

weixin_39827856的博客 248

DisplayPort 1.4数据扰码实战:16bit LFSR并行优化技巧(附Verilog代码)

本文深入探讨了DisplayPort 1.4协议中16bit数据扰码的并行化实现。针对高带宽视频传输需求,文章详细推导了16位LFSR(线性反馈移位寄存器)的单周期16步并行计算公式,并提供了可直接使用的Verilog代码,解决了传统8bit串行处理吞吐量不足的瓶颈,为高速接口设计提供了关键优化技巧。

y4z5a6b7的博客 259

Ubuntu 22.04 “软件与更新”装了NVIDIA驱动,WiFi等设备不见了

在Win10装了Ubuntu22.04的双系统,台式机里面有NVIDIA 3080Ti的独立显卡,Ubuntu默认使用的nouveau驱动,需要更换成NVIDIA相应的驱动,在软件和更新里面一键安装了NVIDIA-driver-535-server之后,发现wifi、蓝牙等设备消失了。这个问题的根源据说跟这个nouveau驱动有关,希望linux社区可以解决这个问题。命令安装后 sudo reboot就可以重新看到WiFi以及蓝牙等设备图标了。然后用U盘拷贝到不能上网的Ubuntu22.04系统上,利用。

me_ysd的博客 5855

redis的setNx方法

使用redis解决多线程问题

weixin_42855525的博客 4万+

RK3588 开启多窗口

这样就支持多窗口了。

tzj1058756809的博客 268

Redis 中 SETNX 与 SET NX 的区别及应用

SETNX 和 SET NX 在实现 “当键不存在时设置” 这一基本功能上有相似之处,但在命令形式、灵活性和返回值等方面存在明显区别。在实际应用中,需要根据具体场景选择合适的方式来使用 Redis。如果仅需要简单的分布式锁功能,SETNX 可能已经足够;如果需要更多的功能,如设置键值对的同时设置有效时间等,SET 命令结合 NX 选项会更加合适。理解它们的区别有助于更好地发挥 Redis 在不同场景下的优势,提高系统的性能和可靠性。

m0_57836225的博客 3450

目标检测YOLO实战应用案例100讲-基于YOLOv7的番茄采摘机械手场景感知及试验(下)

根据软件系统的设计需求,制定系统的可行性方案分析,编写系统的相关程序和 调试运行环境,对系统进行测试,以实现对番茄数据的采集及监测。登陆界面登陆后,进入选择界面,菜单包括了对番茄种植棚室的固定相机数据的监测、 对番茄采摘机械手的双目相机采集的番茄的静态、动态数据的获取、对番茄静态、动态数 据处理的功能菜单等选项。与利用人工去进行对于番茄果实的识别和数目的统计相比,番茄视觉系统自动记录番 茄数量以及红色已经成熟的番茄、青色未成熟番茄的数量,减轻人工记录的工作量,减少 人工记录的误差,从经济方面是可行的。

qq_36130719的博客 812

彻底解决Failed to configure a DataSource: ‘url‘ attribute is not specified and no embedded datasource

Failed to configure a DataSource: ‘url‘ attribute is not specified and no embedded datasource

renkai721的专栏 24万+

redis getset()方法_Redis使用setnx实现分布式锁及问题、优化

最近在工作中用到了分布式锁,然后查了很多分布式锁的实现方式。比较熟悉redis或者说,redis的用法比较简单,所以查了一下redis使用setnx实现分布式锁的方式。其中有一篇文章搜索到的次数最多,多到我不知道哪个是原创文章,就贴一下看到的链接吧https://blog.youkuaiyun.com/lihao21/article/details/49104695。reids>setnx...

weixin_42394054的博客 656

外骨骼机器人(二):自适应振荡器

振荡器是一种数学工具,可以用于实时跟踪、预测类周期信号的频率、相位、幅值等,而助行外骨骼机器人需要自动适应人体的行走频率、幅度,两者相结合,就能使助行外骨骼机器人拥有更舒适、更自然的行走步态。 ......

行者无疆的博客 4264

分布式锁_Redis_Setnx_getSet

使用Redis-setnx-getset实现分布式锁实现原理: Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。 SETNX SETNX key value 将 key 的值设为 value,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动

detiantian的博客 4566

一步步教你使用 NVIDIA 推出的全新GPU加速求解器 cuOpt:解锁超高效率的求解体验

未完成

Linshaodan520的博客 869

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Redis 高并发分布式锁实战

Redis,Spring boot, nginx,jmeter,Synchronized锁,redis的setnx 实现分布式锁,Lua脚本原子删除锁,使用Redission 实现分布式锁

gorgor在码农 2418

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

分布式锁(Redis)

分布式锁

李青水的博客 1746

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

使用Redis实现分布式锁的技术详解

然而,这两个命令并不是原子的,如果SETNX成功但EXPIRE失败,就可能导致死锁。Redis作为一个高性能的内存数据库,提供了多种机制来实现分布式锁,本文将详细介绍如何使用Redis实现分布式锁。释放锁操作需要确保只释放自己持有的锁,以防止误删其他客户端的锁。为了确保操作的原子性,可以使用Lua脚本来实现。为了确保加锁和释放锁的原子性,可以使用Lua脚本将多个Redis命令打包成一个原子操作。为了避免这种情况,可以设置锁的过期时间,当锁过期时自动释放。在加锁时,需要设置锁的过期时间,以防止死锁的发生。

my1121716951的博客 1072

redis分布式锁原理与实现

分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。 使用setnx、getset、expire、del这4个redis命令实现: setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写。命令格式:SETNX key value;使用:只在键 key.

bobozai86的博客 557

redis setnx java_基于redis的分布式锁(Java实现)

Github源码:实现原理:1.setnxRedis的setnx指令(文档参考),setnx意为SET if Not eXists,命令格式:setnx $key $value如果此key不存在,则设置值为value,返回值为1;如果此key存在,则不设置,返回值为0。如下图:127.0.0.1:6379[1]>setnx key v(integer)1127.0.0.1:6379[1]&g...

weixin_32048835的博客 2483

Redis基础面试题:Redis中的分布式锁实现原理及其在高并发场景下的性能优化

通过以上讨论,Victor系统地介绍了Redis分布式锁的实现原理、性能优化策略以及与其他方案的对比。面试官对Victor的技术深度和系统性思维表示了高度认可。

qq_61302385的博客 1120

面试题 - Redis分布式锁原理,Redisson分布式锁原理,Redis分布式锁缺点

Redis分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。 使用setnx、getset、expire、del这4个redis命令实现 setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写。 命令格式:SETNX key value;使用:只在键 k

坚定目标,超越自我 3517
上一篇: LeetCode:236. Lowest Common Ancestor of a Binary Tree( 二叉树的最近公共祖先)
Raven_csdn
博客等级 码龄8年 26粉丝 69原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值