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

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

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

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

??在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中实现锁重入机制来保证同一线程多次获取和释放同一把锁而不会造成阻塞的问题。

相关推荐

redis的八种使用场景

前言:redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。缓存Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。...

基于Redis的3种分布式ID生成策略

在分布式系统设计中,全局唯一ID是一个基础而关键的组件。随着业务规模扩大和系统架构向微服务演进,传统的单机自增ID已无法满足需求。高并发、高可用的分布式ID生成方案成为构建可靠分布式系统的必要条件。R...

基于OpenWrt系统路由器的模式切换与网页设计

摘要:目前商用WiFi路由器已应用到多个领域,商家通过给用户提供一个稳定免费WiFi热点达到吸引客户、提升服务的目标。传统路由器自带的Luci界面提供了工厂模式的Web界面,用户可通过该界面配置路...

这篇文章教你看明白 nginx-ingress 控制器

主机nginx一般nginx做主机反向代理(网关)有以下配置...

如何用redis实现注册中心

一句话总结使用Redis实现注册中心:服务注册...

爱可可老师24小时热门分享(2020.5.10)

No1.看自己以前写的代码是种什么体验?No2.DooM-chip!国外网友SylvainLefebvre自制的无CPU、无操作码、无指令计数器...No3.我认为CS学位可以更好,如...

Apportable:拯救程序员,IOS一秒变安卓

摘要:还在为了跨平台使用cocos2d-x吗,拯救objc程序员的奇葩来了,ApportableSDK:FreeAndroidsupportforcocos2d-iPhone。App...

JAVA实现超买超卖方案汇总,那个最适合你,一篇文章彻底讲透

以下是几种Java实现超买超卖问题的核心解决方案及代码示例,针对高并发场景下的库存扣减问题:方案一:Redis原子操作+Lua脚本(推荐)//使用Redis+Lua保证原子性publicbo...

3月26日更新 快速施法自动施法可独立设置

2016年3月26日DOTA2有一个79.6MB的更新主要是针对自动施法和快速施法的调整本来内容不多不少朋友都有自动施法和快速施法的困扰英文更新日志一些视觉BUG修复就不翻译了主要翻译自动施...

Redis 是如何提供服务的

在刚刚接触Redis的时候,最想要知道的是一个’setnameJhon’命令到达Redis服务器的时候,它是如何返回’OK’的?里面命令处理的流程如何,具体细节怎么样?你一定有问过自己...

lua _G、_VERSION使用

到这里我们已经把lua基础库中的函数介绍完了,除了函数外基础库中还有两个常量,一个是_G,另一个是_VERSION。_G是基础库本身,指向自己,这个变量很有意思,可以无限引用自己,最后得到的还是自己,...

China's top diplomat to chair third China-Pacific Island countries foreign ministers' meeting

BEIJING,May21(Xinhua)--ChineseForeignMinisterWangYi,alsoamemberofthePoliticalBureau...

移动工作交流工具Lua推出Insights数据分析产品

Lua是一个适用于各种职业人士的移动交流平台,它在今天推出了一项叫做Insights的全新功能。Insights是一个数据平台,客户可以在上面实时看到员工之间的交流情况,并分析这些情况对公司发展的影响...

Redis 7新武器:用Redis Stack实现向量搜索的极限压测

当传统关系型数据库还在为向量相似度搜索的性能挣扎时,Redis7的RedisStack...

Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求

重定向与内部子请求Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx...