redis分布式锁过期的优化方案

问题场景:假设redis分布式锁(key)过期时间是5s,任务的执行时间是10s,那么就意味着A线程获取锁之后尚未执行完毕,B线程就可以获取到锁,很明显此时锁无法保证线程安全,应该如何优化处理?

思路:线程获取到锁之后,开启一个守护线程,专门用来维护key的过期时间。

代码如下:
 

@Autowired
RedisTemplate<String,String> redisTemplate;
//删除key的脚本
private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";


//释放锁
private Boolean unlock(String key,String value) {
   return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
      Jedis jedis = (Jedis) connection.getNativeConnection();
      Object eval = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
      if (eval.equals(1L)) {
         return Boolean.TRUE;
      }
      return Boolean.FALSE;
   });
}

//获取锁
private Boolean lock(String key, String value, long expireTime) {
   Boolean lock =  redisTemplate.execute((RedisCallback<Boolean>) connection -> {
      Jedis jedis = (Jedis) connection.getNativeConnection();
      String set = jedis.set(key, value, "NX", "EX", expireTime);
      if ("OK".equals(set)) {
         return true;
      }
      return false;
   });
       //如果获取锁成功,则开启守护线程
   if(lock){
      DaemonTask daemonTask = new DaemonTask(key,value,expireTime);
      Thread thread = new Thread(daemonTask);
      thread.setDaemon(true);
      thread.start();
   }
   return lock;
}

//守护线程

pulic class DaemonTask implements Runnable{
   private static final String POSTPONE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return '0' end";

   private String key;
   private String value;
   private long expireTime;
   private boolean isRunning;
   public DaemonTask(String key, String value, long expireTime) {
      this.key = key;
      this.value = value;
      this.expireTime = expireTime;
      this.isRunning = Boolean.TRUE;
   }
   @Override
   public void run() {
      long waitTime = expireTime * 1000 * 2 / 3;// 线程等待多长时间后执行
      while (isRunning){
         try {
            //守护线程等待 锁过期时间的2/3 后,
            Thread.sleep(waitTime);
            if(postpone(key,value,expireTime)){
               System.out.println("延时成功...........................................................");
            }else{
               this.isRunning = false;
            }
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
      }
   }
   
   //key过期时间维护
   public Boolean postpone(String key, String value, long expireTime) {
      return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
         Jedis jedis = (Jedis) redisConnection.getNativeConnection();
         Object result = jedis.eval(POSTPONE_LOCK_SCRIPT, Lists.newArrayList(key), Lists.newArrayList(value, String.valueOf(expireTime)));
         if (result.equals(1L)) {
            return Boolean.TRUE;
         }
         return Boolean.FALSE;
      });
   }

}

//业务代码模拟

@RequestMapping("/lock")
	public Result<?> expire(){
		String key = "liuchs";
		String value = UUID.randomUUID().toString();
		long expireTime = 5L;
		String threadName = Thread.currentThread().getName();
		Boolean lock = lock(key,value,expireTime);
		if(lock){
			System.out.println(threadName+"---------业务线程获取锁-------------------------开始执行业务代码---"+ DateUtil.getDateStr(new Date(),"yyyy-MM-dd HH:mm:ss"));
			try {
				Thread.sleep(8000);
				Boolean unlock = unlock(key, value);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
			System.out.println(threadName+"-----------业务线程执行完毕------------------------------");
		}else{
			System.out.println(threadName+"---------------业务线程获取锁失败---------------");
			return Result.renderError();
		}
		return Result.renderSuccess();
	}

//运行结果分析

 

线程http-nio-8085-exec-1获取锁的时间为2023-06-28 17:26:05,任务的运行时间为8s,此时key的失效时间为 5s,正常情况下5s之后——2023-06-28 17:26:10之后,其他线程可以获取到锁,但是由于守护线程重新维护了key的过期时间,所以其他线程获取锁需要等待任务执行完毕——8s之后——2023-06-28 17:26:13之后,如下图:

由此可以保证任务线程未执行完毕的情况下,key不会过期。 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值