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

本文介绍了分布式锁的概念及其在不同系统间同步获取共享资源的应用。详细解释了分布式锁需要解决的关键问题,包括互斥性、安全性、死锁及容错等问题,并深入探讨了使用Redis实现分布式锁的具体方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

一:什么事分布式锁?

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

二:首先需要知道的是,分布式锁需要解决的问题是什么?
    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出现卡顿的情况,如果你在这里还有更加好的方法来解决,或者可以直接与我讨论

 

### Java 基于 Redis分布式锁实现SETNX EXPIRE) 以下是使用 `SETNX` `EXPIRE` 命令在 Java 中实现基于 Redis分布式锁的示例代码: ```java import redis.clients.jedis.Jedis; public class DistributedLock { // 定义锁的Key private static final String LOCK_KEY = "distributed_lock"; // 定义锁的有效时间(单位:秒) private static final int LOCK_EXPIRE_TIME = 10; public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { // 尝试获取锁,如果不存在则设置并返回true;存在则不覆盖并返回false String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime); return "OK".equals(result); // 如果返回值为"OK",表示成功获取锁 } public void releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { // 使用 Lua 脚本来确保原子性解锁操作 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; jedis.eval(script, java.util.Collections.singletonList(lockKey), java.util.Collections.singletonList(requestId)); } public static void main(String[] args) { Jedis jedis = new Jedis("localhost", 6379); DistributedLock distributedLock = new DistributedLock(); String uniqueRequestId = Thread.currentThread().getName(); try { if (distributedLock.tryGetDistributedLock(jedis, LOCK_KEY, uniqueRequestId, LOCK_EXPIRE_TIME * 1000)) { System.out.println(Thread.currentThread().getName() + ": 获取锁成功!"); // 执行临界区代码 try { Thread.sleep(5000); // 模拟业务逻辑耗时 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": 开始释放锁..."); distributedLock.releaseDistributedLock(jedis, LOCK_KEY, uniqueRequestId); System.out.println(Thread.currentThread().getName() + ": 锁已释放!"); } else { System.out.println(Thread.currentThread().getName() + ": 获取锁失败!"); } } finally { jedis.close(); } } } ``` #### 解析 - **`tryGetDistributedLock` 方法** 此方法尝试通过 `SETNX` 命令来创建一个锁。如果键不存在,则会设置键值对,并指定过期时间为防止死锁发生[^1]。 - **`releaseDistributedLock` 方法** 使用 Lua 脚本确保删除锁的操作具有原子性。只有当当前线程持有的锁与存储中的锁一致时才会删除锁[^2]。 - **`main` 方法** 主函数展示了如何调用上述两个方法以实现完整的加锁解锁流程。它还模拟了一个简单的业务场景,在持有锁期间让线程休眠一段时间[^3]。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值