mysql 排他锁 重入_使用Redis实现分布式环境下的可重入的排他锁

非分布式环境下的可重入排他锁的实现很简单,可以使用的方法很多,如synchronized、ReentrantLock等,但是在分布式环境一下则需要思考一下,一般也可以选型为Redis

、MySQL、Zookeeper等。本文通过Redis实现一个分布式可重入排他锁。

通过Redis的原子特性实现一个分布式环境下的排他锁并不难,默认的 SET

NX

可以很好的帮助我们解决并发下的锁key争抢问题。但是如果持有锁key的线程再一次通过该方式获取该锁则会失败,因为此锁key

已经存在了,即 SET NX

命令无法支持锁的重复获取,需要在代码层做控制,这也带来了实现的复杂性。

SET命令

对SET命令不是很了解的同学可以先看一下使用说明,以下摘自 Redis命令参考

从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:

EX seconds : 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。

PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。

NX : 只在键不存在时, 才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。

XX : 只在键已经存在时, 才对键进行设置操作。

需要注意一个问题就是上面的选项可以同时生效:

127.0.0.1:6379> set mykey haha ex 10 nx

OK

127.0.0.1:6379> ttl mykey

(integer) 6

127.0.0.1:6379> ttl mykey

(integer) 1

127.0.0.1:6379> ttl mykey

(integer) -2

还可以通过lua来实现

127.0.0.1:6379> eval "return redis.call('set', 'mykey', '2_90041', 'ex', '10', 'nx')" 0

OK

127.0.0.1:6379> ttl mykey

(integer) 6

锁超时

任何一个锁在被一个线程持有之后都最好再设置一个失效时间,防止业务代码由于异常原因,如宕机等,无法正常释放对应的锁key,造成其他线程无法获取到锁。

当然正常情况下,线程执行完业务之后需要及时 unlock

让其他线程获取到锁,提高程序的并发性能。

可重入锁

可重入锁是指一个锁在被一个线程持有后,在该线程未释放锁前的任何时间内,只要再次访问被该锁锁住的函数区都可以再次进入对应的锁区域。

可重入锁有一个可重入度的概念,即每次重新进入一次该锁的锁住的区域都会递增可重入度,每次退出一个该锁锁住的区域都会递减可重入度,最终释放全部锁后,可重入度为0。

当然可重入度可能并不是无限大的,具体要看实现,这里不做深入讨论。

如何用Redis实现分布式环境下可重入锁

所以,我们的基于Redis的分布式可重入锁的实现需要考虑:

排他性

这个Redis的SET NX已经帮我们解决了,具体的锁持有者身份标识问题需要业务解决,下面会说

锁超时性

这个需要实现代码在获取锁key成功之后同时设置一个失效时间,防止锁key可能存在的异常情况下长时间不被释放的问题

可重入性

即获取锁的线程在未释放(锁未过期)之前,仍然可以继续获取该锁,这个需要结合排他性里说到的锁持有者身份标识来解决,即A获取了锁,那么锁里一定要有A的信息,对上了就可以继续让A获取了

需要注意的是前两点需要保证原子性,防止上锁成功和锁失效时间设置之间的gap过长导致失效时间设置时锁已释放

可以通过lua来实现上锁和设置ttl的原子性操作

-当key不存在的时候,设置value和失效时间,返回成功

-当key已经存在的时候,如果oldValue == value,新老值相同时,也会返回成功

-都不符合时返回失败

local function setnx_or_value_equals(key, value, ttl)

local old = redis.call('GET', key);

if old == nil or old == false then

redis.call('SET', key, value, 'px', ttl, 'nx') -- 设置value后设置了px失效时间并且要求

return 1 -- 返回成功

end

if value == old then

return 1 -- 对比新老value一致则认为是相同的线程,返回成功

end

-- 大部分请求只进行一次GET后即返回

return 0 -- 返回失败

end

上述锁实现可以令一个已经通过setnx_or_value_equals()获取了key锁的线程在该key未失效前可以多次获取对应的的key锁。

需要注意这里的key的value要特别进行设计,防止简单的时间戳的数值造成可能的冲突问题,导致其他线程通过value==old来窃取到锁。

一般在分布式环境中value可以参考机器、进程和线程,尽可能的多维度化value的值从而避免冲突,如: HOST_IP + Process_ID + Thread_Id

本文首次发布于ElseF’s Blog, 作者 @stuartlau

,

转载请保留原文链接.

注意:本文来自ElseF's Blog。本站无法对本文内容的真实性、完整性、及时性、原创性提供任何保证,请您自行验证核实并承担相关的风险与后果!

CoLaBug.com遵循[CC BY-SA 4.0]分享并保持客观立场,本站不承担此类作品侵权行为的直接责任及连带责任。您有版权、意见、投诉等问题,请通过[eMail]联系我们处理,如需商业授权请联系原作者/原网站。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
重入分布式锁是指一个线程可以多次获取同一把锁而不会造成死锁。在分布式环境下,需要使用分布式锁来保证多个节点之间的数据同步。Redis可以通过SETNX命令实现分布式锁,但是没有原生支持可重入性。下面是一个使用Redis实现重入分布式锁的示例代码: ```python import redis class RedisReentrantLock: def __init__(self, redis_client, lock_key): self.redis_client = redis_client self.lock_key = lock_key self.thread_local = threading.local() self.lock_count = 0 def acquire(self): current_thread = threading.current_thread() if getattr(self.thread_local, 'lock_owner', None) == current_thread: self.lock_count += 1 return True else: if self.redis_client.setnx(self.lock_key, current_thread.ident): self.thread_local.lock_owner = current_thread self.lock_count = 1 return True else: return False def release(self): current_thread = threading.current_thread() if getattr(self.thread_local, 'lock_owner', None) == current_thread: if self.lock_count > 1: self.lock_count -= 1 else: self.redis_client.delete(self.lock_key) del self.thread_local.lock_owner self.lock_count = 0 return True else: return False ``` 这个类实现了acquire和release方法来获取和释放锁。如果一个线程已经获取了锁,再次获取锁时,acquire方法会增加锁计数器。当锁计数器减至0时,会释放锁。这样实现了可重入性。同时,使用了Python的threading.local类来确保每个线程都有自己独立的锁计数器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值