百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Redis分布式锁如何实现锁重入机制?

wptr33 2025-01-29 18:20 26 浏览

所谓的分布式锁重入是指同一个线程或者是同一个客户端可以多次获取同一个分布式锁而不会导致锁竞争或者死锁问题出现的一种机制,简单的来讲,锁重入就是要让同一个线程在已经持有了锁的情况下还能够再次获取并且能够正确的释放这个锁而不会导致死锁问题的出现,这种实现机制是对于普通的分布式锁的一种扩展,主要就是为了应对在业务操作中同一个线程多次请求同一个锁的情况。

??在Redis实现的分布式锁中,想要实现锁重入机制需要对锁操作进行额外的处理,因为Redis本身是没有对相关的机制进行支持,所以需要通过各种的扩展手段来实现这个功能,一般比较常见的方式就是通过判断锁的唯一标识以及锁计数器的方式来实现锁重入操作。

锁重入的原理

??锁的持有者标识

??在之前的分享中,我们介绍过了如何通过Redis来实现分布式锁的基本原理,一般情况下我们会通过一个唯一标识来标记锁的持有者是谁,例如比较常用的就是通过线程id、通过客户端id等方式,当一个线程获取到锁的时候,在Redis中我们通过记录线程的唯一标识来保证锁的唯一性。

??锁计数器

??在单线程实现锁重入机制的时候,其底层实现就是通过维护了一个锁计数器来记录进入锁的线程的次数,这里也是同样的道理,如果同一个线程再次请求了同样的锁,那么我们还是可以通过同样的机制来记录锁的锁定次数。当每次线程请求同一个锁的时候,Redis可以通过检查锁是否已经被当前线程所持有的方式来判断是否对计数器进行加一操作,如果持有那么计数器加一,如果没有那么就会判断锁是否被其他线程占用,是否可以获取等操作,并且获取成功之后,计数器就会初始化为1。

??锁释放

??既然提到了锁的重入、计数器等操作,那么锁能否被释放的条件就会变成计数器是否为1,如果计数器为零那么表示没有现成使用该锁了,那么该锁就可以被释放,否则就是进行计数器减一操作,表示持有锁的一个线程释放了该锁,其他线程还在锁定中。

??这里博主提出一个问题,分布式锁的释放是否需要遵守加锁顺序?有兴趣的读者可以在评论区讨论一下。

Redis实现分布式重入锁的步骤

??假设,我们通过SET命令的方式来实现分布式锁的创建操作,并且通过线程ID或者是UUID来指定分布式锁的持有者的唯一标识。如下所示。

加锁操作

??通过SET key value NX PX time命令来实现加锁操作,当进行加锁操作的时候,如果判断到锁已经存在了,那么就需要检查当前锁是否被当前线程持有,如果是那么就对计数器加一,如果不是那么就是等待或者是拒绝的操作。

计数器管理

??当通过上面的命令进行加锁的时候,锁的value中存储的就不只是一个锁的唯一标识了,还会存储一个计数器标识,当进行锁重入处理的时候,这个计数器也会随之加一减一。最为简单的存储结构就是通过Redis中Hash来存储,这样方式更加简单高效。

锁释放

??当需要进行锁释放的时候,需要检查当前线程持有的锁的计数器,如果计数器大于1,那么就表示该线程还有其他的层次的调用来持有该锁,这个时候表示持有该锁的一层线程执行逻辑处理完成,如果计数器为1,那么就表示就只有当前这个线程持有该锁了,用玩之后就可以直接对其进行释放了。

具体代码实现

??这里我们通过一个Python代码来简单演示一下Redis实现分布式重入锁的机制,这里我们用到的两个命令分别是SETHINCRBY。其中HINCRBY这个命令是Redis提供的一个用来在Hash结构上执行加法操作的命令,也就是说它可以对Hash结构中的指定的整数进行加一操作,初始化为0,基础语法如下所示。

HINCRBY key field increment
  • key:哈希表的名称(键)。
  • field:哈希表中要更新的字段名。
  • increment:要增加的整数值。这个值可以是正数或负数。

锁机制实现

??如下所示。

import redis
import uuid
import time

class RedisReentrantLock:
    def __init__(self, redis_client, lock_key, expire_time=3000):
        self.redis = redis_client
        self.lock_key = lock_key
        self.expire_time = expire_time
        self.lock_owner = str(uuid.uuid4())  # 用于标识锁的持有者

    def acquire(self):
        while True:
            # 使用 Redis 的 SETNX 原子操作来设置锁
            # 如果锁不存在,则设置成功并返回 True
            if self.redis.setnx(self.lock_key, self.lock_owner):
                # 如果锁不存在,设置成功,设置过期时间
                self.redis.pexpire(self.lock_key, self.expire_time)
                self.redis.hset(self.lock_key, 'counter', 1)  # 初始计数器为 1
                return True
            else:
                # 如果锁已经存在,检查是否是当前持有者
                current_owner = self.redis.get(self.lock_key)
                if current_owner == self.lock_owner.encode('utf-8'):
                    # 如果是当前持有者,增加计数器
                    self.redis.hincrby(self.lock_key, 'counter', 1)
                    return True
                else:
                    # 否则等待一段时间后再试
                    time.sleep(0.1)

    def release(self):
        # 获取锁的计数器
        counter = int(self.redis.hget(self.lock_key, 'counter') or 0)
        if counter > 1:
            # 如果计数器大于 1,减少计数器
            self.redis.hincrby(self.lock_key, 'counter', -1)
        else:
            # 如果计数器为 1,完全释放锁
            self.redis.delete(self.lock_key)

使用该机制

??实现完该分布式锁之后,接下来我们就来看看如何使用这个操作。

# 创建 Redis 客户端
r = redis.StrictRedis(host='localhost', port=6379, db=0)

# 创建一个 RedisReentrantLock 实例
lock = RedisReentrantLock(r, 'my_lock')

# 获取锁
if lock.acquire():
    try:
        # 执行临界区代码
        print("Lock acquired, performing task...")
        time.sleep(2)  # 模拟执行任务
    finally:
        # 释放锁
        lock.release()
else:
    print("Failed to acquire lock")

总结??

通过上面的实现,我们简单总结一下,想要实现锁的重入,首先需要知道锁重入的条件,也就是通过判断获取锁的客户端线程唯一标识与当前持有锁的线程的唯一标识是否一致,其次就是需要再记录锁绑定的同时还要记录一个锁的计数器,然后通过各种的原子性的操作来保证这些操作不会因为并发而导致其他问题出现,解决了这些问题之后,就是需要考虑更加深层次的问题。也就是锁的超时机制、锁的并发、锁的释放等等。这样我们就可以在Redis中实现锁重入机制来保证同一线程多次获取和释放同一把锁而不会造成阻塞的问题。

相关推荐

威信Chronosonic XVX全新旗舰全球首发 设计特点彻底公开

第一眼看到WilsonAudio新推出的ChronosonicXVX音箱,相信大家都会直觉认为它是两年前超级旗舰WAMMMasterChronosonic的缩小版,不过这个推测并不完全正确。C...

C#高精度Timer和Delay以及时间测量

在PCHMI7.0后在工具箱里会多一个MsTimer,以及Delay和Microsecond两个类。...

python教程从基础到精通,第9课—日期与时间

Hello,小伙伴们,祝大家五.一玩得快乐!刚学习完了七大数据类型,今天咱们来学习日期与时间的表示方法。Python标准库中提供了时间和日期的支持:calendar:日历相关;time、datetim...

软件测试|教你轻松玩转Python日期时间

Python基础之日期时间处理...

Go语言中互斥锁与读写锁,你知多少?

简述Golang中的锁机制主要包含互斥锁和读写锁互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段。在Go中主要使用sync.Mutex的结构体表示。一个简单的示例:funcmutex()...

变形金刚动画大电影——经典台词赏析

YOURDAYSARENUMBEREDNOW,DECEPTI-CREEPS你们活不了多久了,霸天虎小子。-{铁皮说的话,体现了铁皮的嫉恶如仇,可是后来铁皮在飞船上遇袭身亡,可谓是出师未捷身先...

Python时间日期模块使用教程(python3日期)

1.时间日期处理概述在日常编程中,时间日期处理是非常常见的需求,比如:记录日志时间...

亚马逊介绍AWS“无服务器”云服务改进:数据库可线上扩充容量等

IT之家11月29日消息,在今天于美国拉斯维加斯展开的亚马逊“AWSre:Invent2023”活动中,亚马逊计算部门资深副总裁PeterDeSantis,介绍了旗下三款云端服务,IT...

2.日期格式 datetime(日期时间显示格式)

fromdatetimeimportdatetime1.获取当前日期和时间now=datetime.now()#2025-05-3110:56:01.4687822.格式化日期...

【科普】时间单位大盘点(时间单位都有哪些?)

时间单位,是7种基本单位之一,长度、时间、质量、物质的量、光照度、电流和(热力学)温度是七种基本单位。本词条中时间单位以时间从大到小列。今天我们来盘点下时间的单位换算...

基于PHP的Laravel框架,盘点Github高星Web管理后台,效率为王!

在Web开发工作中,选择一个高效、稳定的后台管理系统是提高开发效率的关键。虽然PHP在近些年中的热度有所减退,但其上手简单、开源、灵活且被广泛应用的特点,仍然使其在编程语言排行榜中保持前十的位置。这表...

如何使用PHP编写一个简单的留言板?

留言板是一个常见的Web应用程序,允许用户在网站上发布和查看留言。在本文中,我们将使用PHP编写一个简单的留言板,介绍构建过程中的关键步骤和技巧。一、准备工作在开始编写留言板之前,我们需要准备好以下工...

产品经理提需求时要考虑的 15 个隐性需求

虽然世界充满未知的变化,但是有一些大的方向还是可以把握的,本文跟大家谈谈产品经理提需求时要考虑的15个隐性需求,enjoy~俗话说,计划赶不上变化快,无论需求文档做得如何细致,考虑得如何周全,总会...

关于 PHP 启动 MongoDb 找不到指定模块问题

前言:最近有一个小demo,需要通过PHP将用户行为记录储存到MongoDB,再用Spark做协同过滤。由于以前处理跨语言交互是通过消息中间件,这次本地使用MongoDB却弄出了几个问...

PHP程序员老鸟面试经历(php程序员怎么样)

在任何时代找任何工作都有面试这么一说的。特别是高端技术类的工种对技术理论和技术实操能力要求很严格。大部分公司招收技术员工的要求也越来愈高。至于PHP程序员也是如此,我估计大多数PHP老鸟已经不在意所...