redis 基于SETNX和EXPIRE的用法 实现redis 分布式锁

耐心看完,我相信你会有收获

一:什么事分布式锁?

百度如上,简单回答就是不同系统系统之间同步获取共享资源打的一种方式

二:首先需要知道的是,分布式锁需要解决的问题是什么?
    1.互斥性:任一时刻是有一个客户端获取锁,不能两个客户端获取到锁
    2.安全性:锁只能被持有该客户端的删除,不能由其他客户端删除
    3.死锁:一个客户端获取到锁,导致宕机,而其他客户端无法获取到资源
    4.容错:一些节点宕机,客户端任然能获取锁和释放锁

三.实现原理;
    redis有一个指令:SETNX key value:如果key不存在,则创建并赋值
    指令返回:设置成功,返回1,设置失败 返回0

如上图所示,为了测试方便我在windows 上操作,由于setnx 是原子性的,也就是当我们在操作代码的业务逻辑的时候可以给某key设值,成功返回返回1,上图操作也可以看出,设置成功后不能修改,get 到的还是test,也就是当我们设置成功后,我们线程可以去执行该段业务逻辑

那么问题来了,如何解决SETNX长期有效的问题?

    redis 提供了一个指令:EXPIRE key seconds
          设置key 的生存时间,当key过期(生存时间为0),会自动删除

如下图所示:

设置两秒钟的时间,可以看出再次setnx locknx task ,返回为1,说明执行过期时间生效,如下如简单代码:

RedisService redisService = SpringUtils.getBean(RedisService.class)
long status = redisService,setnx(key,"1")
if(status == 1){
  redisService.expire(key,expire)
  //执行独占资源的业务逻辑
}

上述思路问题是可以解决问题,但是很明显会出现一个问题?
       思考一下就可以想到,当我们设置了SETNX后,如果马上挂掉了,那我们再次    EXPIRE 指令是不是就会无法生效,出现了资源锁死的情况,独占资源所以针对这样问题redis 在2.6.12版本过后,有了一个新的决绝方案

SET key value [EX seconds] [PX millisecounds] [NX|XX]
EX seconds:设置键的过期时间为second秒
PX millisecounds:设置键的过期时间为millisecounds 毫秒
NX:只在键不存在的时候,才对键进行设置操作
XX:只在键已经存在的时候,才对键进行设置操作
SET操作成功后,返回的是OK,失败返回NIL

如图所示,

 

我再redis里面存储了一个 locktarget 的值为 12345,执行成功后我马上修改,返回时间为nil 失败,等过10后再次执行返回ok,这样就可以很轻松决绝当我们执行SETNX的时候服务挂掉,资源独占的情况,伪代码可以参见如下图:

RedisService redisService = springUtils.getBean(RedisService.class)
String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIT,SET_WHIT_EXPIRT_TIME,expireTime);
    if("ok".equals(result)){
          //执行独占资源的业务逻辑
}

如果需要大批量设置过期时间,网上给出的找到答案是,在key上给一个随机值,来避免redis出现卡顿的情况,如果你在这里还有更加好的方法来解决,或者可以直接与我讨论

 

是的,Redis可以使用setnx命令来实现分布式锁setnx命令是Redis中的一个原子性操作,用于设置一个键值对,当键不存在时才会设置成功,如果键已经存在则设置失败。我们可以利用这个特性来实现分布式锁,比如可以将一个键作为锁名称,将当前时间作为锁的值,然后使用setnx命令来尝试获取锁,如果获取成功,则说明获取了锁,否则说明锁已经被其他进程占用。 为了避免死锁,我们还需要给锁设置过期时间,这样即使某个进程在获取锁之后发生了意外导致锁没有及时释放,也不会一直占用锁资源。 下面是使用setnx命令实现分布式锁的示例代码: ``` import redis import time class RedisLock: def __init__(self, redis_client, name, expire=60): self.redis_client = redis_client self.name = name self.expire = expire def acquire(self): while True: now = time.time() expires = now + self.expire if self.redis_client.setnx(self.name, expires): return expires else: value = self.redis_client.get(self.name) if value and now > float(value): old_value = self.redis_client.getset(self.name, expires) if old_value and old_value == value: return expires time.sleep(0.1) def release(self): self.redis_client.delete(self.name) ``` 在上面的代码中,我们定义了一个RedisLock类,它的构造函数接收一个Redis客户端对象、锁名称和过期时间。acquire方法用来获取锁,如果获取成功则返回锁的过期时间,否则阻塞等待。release方法用来释放锁,它会删除锁的键值对。在acquire方法中,我们不断循环尝试获取锁,直到获取成功为止。如果获取失败,则检查锁是否已经过期,如果过期则尝试用getset命令更新锁的值,并检查更新前后的值是否相等,如果相等则说明获取到了锁,否则继续重试。为了避免过多的CPU消耗,我们在尝试获取锁时加上了一个短暂的等待时间。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值