Redis分布式锁(三种实现方式:setnx+expire,set,Redission)

Redis分布式锁


分布式锁的由来

在传统单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。

但是在分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁的由来。

当多个进程不在同一个系统中,就需要用分布式锁控制多个进程对资源的访问。最实际的应用场景应该是控制库存,比如说电商平台防止商品超卖这类场景。

分布式锁需要满足的条件
  • 互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。

  • 安全性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。

  • 不会死锁:获取锁的客户端因为某些原因(如down机等)而未能释放锁,也能保证后续其他客户端能加锁。

  • 容错:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

Redis分布式锁实现一(setnx+expire)

实现命令如下

seynx key value
expire key time

setnx其实就是set if not exists的意思,仅当key不存在的时候才能进行插入,执行完操作后可以将锁删除
expire命令为锁设置过期时间

del key

这样就能重新对key的值进行操作了

Redis分布式锁实现二(set)

上面那种实现方式存在一种问题,如果在执行完 setnx 之后,执行expire之前,服务器 crash 或重启了导致加的这个锁没有设置过期时间,就会导致死锁的情况(别的线程就永远获取不到锁了)

代码如下:

jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)

第一个为key,我们使用key来当锁,因为key是唯一的。

第二个为value,我们传的是requestId,很多人可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到分布式锁要满足的条件时,提到的安全性这一条,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。

第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

第五个为time,与第四个参数相呼应,代表key的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

我们的加锁代码满足我们可靠性里描述的三个条件:

  • 首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性
  • 其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁
  • 最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。这样就避免了锁被别的线程删除,保证了安全性
Redis分布式锁实现三(Redission框架)

方案二还是可能存在锁过期释放,业务没执行完的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实问题的关键就在于我们不确定要设置多长时间,时间太短就会导致锁过期释放,业务没执行完,时间太长就会使系统运行效率下降. 我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。Redission框架帮我们实现了此功能,名为看门狗

avatar

只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了锁过期释放,业务没执行完问题。

avatar

如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。

这个时候我们就可以使用RedLock,它的思路是,在多个Redis服务器上保存锁,只需要超过半数的Redis服务器获取到锁,那么就真的获取到锁了,这样就算挂掉一部分节点,也能保证正常运行,保证了容错性

References

欢迎访问我的个人博客网站Levitate Gu

  • 21
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis分布式锁实现可以使用RedisSETNX命令(SET if Not eXists)来完成。SETNX命令在键不存在时设置键的值,如果键已经存在则不执行任何操作。 以下是使用SETNX命令实现Redis分布式锁的示例代码(使用Python语言): ```python import redis def acquire_lock(redis_conn, lock_key, expire_time): # 使用SETNX命令尝试获取锁 lock_acquired = redis_conn.setnx(lock_key, 1) if lock_acquired: # 设置锁的过期时间 redis_conn.expire(lock_key, expire_time) return True else: return False def release_lock(redis_conn, lock_key): # 释放锁,删除键 redis_conn.delete(lock_key) # 创建Redis连接 redis_conn = redis.Redis(host='localhost', port=6379, db=0) # 获取锁 if acquire_lock(redis_conn, 'mylock', 10): try: # 执行业务逻辑 print("Lock acquired. Do something here...") finally: # 释放锁 release_lock(redis_conn, 'mylock') else: print("Failed to acquire lock. Another process holds the lock.") ``` 以上代码中,`acquire_lock`函数尝试获取锁并设置过期时间,如果成功获取到锁则返回True,否则返回False。`release_lock`函数用于释放锁,即删除键。 在使用分布式锁时,可以根据具体业务需求设置合适的锁键(`lock_key`)和过期时间(`expire_time`)。确保在获取到锁之后,执行业务逻辑的代码在适当的位置调用`release_lock`函数释放锁,避免锁一直被占用而无法释放。 需要注意的是,RedisSETNX命令是原子操作,可以确保在并发情况下只有一个客户端能够成功获取到锁。同时,设置合适的过期时间能够避免因为异常情况导致锁一直被占用而无法释放。 这只是一个简单的示例,实际使用中还需要考虑异常处理、加锁失败的重试策略以及其他线程安全的因素,以确保分布式锁的可靠性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值