莱莱 发表于 2024-8-14 09:50:42

基于Redis的分布式锁

1.为什么利用分布式锁?

    分布式锁多数用在分布式场景中,假如是单机的话用jvm的锁就行了。分布式锁的原理就是利用redis的set nx多线程的互斥特性,在多线程场景中锁住对共享资源的访问。而且redis是基于内存存储的中间件,加锁解锁的性能都非常快。
2.实现

1.设置set NX假如键不存在就是存储,EX是10秒钟后过期也可以利用PX毫秒
SET mykey myvalue NX EX 10 2.java实现
redisTemplate.opsForValue().setIfAbsent(key,"uid",10,TimeUnit.SECONDS);

//底层是execute直接执行,也可以保证操作的原子性

return (Boolean)this.execute((connection) -> {
            return connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent());
      }, true);
3.看门狗机制

引入开门狗机制重要是防止在线程持有锁的时候,代码没有执行完毕就由于锁到期开释了锁,导致共享资源被修改。
RLock rLock = redissonClient.getLock(name);
try {
    /**
   * waitTime 获取锁的最长等待时间
   * leaseTime 持有锁的时间
   * unit 单位
   * */
    //TODO 开启看门狗lease -1 自我理解看门狗的功能就是在超时后任务没有执行完成就续期,
    // 如果没有看门狗并且设置了leaseTime 就是当前锁的失效时间了
    Boolean bo =rLock.tryLock(5,-1,TimeUnit.MINUTES);
    if (!bo){
      System.out.println("没有拿到锁,遗憾离场:"+name);
       continue;
    }else {
      System.out.println("拿到了锁,并开始耗时:"+name);
      Thread.sleep(600);
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    if (rLock.isHeldByCurrentThread()){
      rLock.unlock();
      System.out.println("释放了锁:"+name);
    } else {
      System.out.println("遗憾离场,并来到了finally:"+name);
    }
} //底层逻辑 开一个监听线程看执行完毕删除锁,未执行完毕就加过期时间
Long ttl = this.tryAcquire(leaseTime, unit, threadId);

//判断是否开启了看门狗机制
private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, final long threadId) {
    if (leaseTime != -1L) {
      return this.tryLockInnerAsync(leaseTime, unit, threadId,
                RedisCommands.EVAL_NULL_BOOLEAN);
    } else {
      //延期时间默认30毫秒this.lockWatchdogTimeout = 30000L;
      RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.
                getConnectionManager().getCfg().getLockWatchdogTimeout(),
                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
      //监听这个线程时间
      ttlRemainingFuture.addListener(new FutureListener<Boolean>() {
            public void operationComplete(Future<Boolean> future) throws Exception {
                if (future.isSuccess()) {
                  Boolean ttlRemaining = (Boolean)future.getNow();
                  if (ttlRemaining) {
                        RedissonLock.this.scheduleExpirationRenewal(threadId);
                  }

                }
            }
      });
      return ttlRemainingFuture;
    }
}

//线程的具体监听方法 锁不存在了就取消监听
private void scheduleExpirationRenewal(final long threadId) {
    if (!expirationRenewalMap.containsKey(this.getEntryName())) {
      Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            public void run(Timeout timeout) throws Exception {
                RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS, ARGV) == 1) then redis.call('pexpire', KEYS, ARGV); return 1; end; return 0;", Collections.singletonList(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});
                future.addListener(new FutureListener<Boolean>() {
                  public void operationComplete(Future<Boolean> future) throws Exception {
                        RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
                        if (!future.isSuccess()) {
                            RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());
                        } else {
                            if ((Boolean)future.getNow()) {
                              RedissonLock.this.scheduleExpirationRenewal(threadId);
                            }

                        }
                  }
                });
            }
      }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
      if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {
            task.cancel();
      }

    }
} https://i-blog.csdnimg.cn/direct/8922ce3394e643f0954ee2e745aa036d.jpeg

4.redLock

    redLock重要是为了防止在redis集群中,在一个节点加了锁,但在加锁后节点就挂掉了,导致之前加的锁失效,线程重复加锁的问题。
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();
  但红锁也有很多问题

[*] 资源竞争问题:‌当多个客户端竞争同一资源时,‌假如向多个Redis实例请求获取锁,‌容易出现没有得胜者的环境。‌这种环境下,‌没有获得过半数锁的客户端应及时开释锁,‌以制止长时间占用资源。‌
[*]  安全性问题:‌在某个主节点宕机时,‌大概会出现锁安全性问题。‌例如,‌当Redis的持久化计谋为AOF利用appendfsync=everysec即每秒fsync一次时,‌故障时会丢失1秒的数据,‌这大概导致锁的丢失。‌
[*]  实现复杂性:‌虽然Redlock算法提供了一种实现分布式锁的方法,‌但在实际应用中,‌需要确保所有Redis实例的时间是同步的,‌这增加了实现的复杂性。‌
[*]  性能开销:‌由于Redlock需要在多个Redis实例上同时举行利用,‌这大概会增加额外的性能开销,‌尤其是在高并发场景下2。‌
[*]  可靠性问题:‌虽然Redlock算法设计用于制止死锁和确保最终一致性,‌但在实践中,‌假如锁定资源的服务崩溃或分区,‌仍然大概存在开释锁的可靠性问。
5.读写锁

  读写锁是一种并发控制机制,用于控制对共享资源的访问。它允很多个读利用同时举行,但写利用是互斥的。这样可以在包管数据一致性的同时,进步体系的并发性能。
  在Redis缓存中,我们可以将数据分为热点数据和非热点数据。热点数据是指访问频率较高的数据,而非热点数据访问频率较低。对于热点数据,我们可以接纳读写锁机制,以进步并发性能。
RReadWriteLock readWriteLock =redissonClient.getReadWriteLock(mobile);
readWriteLock.readLock();
readWriteLock.writeLock();  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 基于Redis的分布式锁