基于Redis的分布式锁

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

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

2.实现

1.设置set NX如果键不存在就是存储,EX是10秒钟后过期也可以使用PX毫秒

java 复制代码
SET mykey myvalue NX EX 10

2.java实现

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.看门狗机制

引入开门狗机制主要是防止在线程持有锁的时候,代码没有执行完毕就因为锁到期释放了锁,导致共享资源被修改。

java 复制代码
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);
    }
}
java 复制代码
//底层逻辑 开一个监听线程看执行完毕删除锁,未执行完毕就加过期时间
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[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); 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();
        }

    }
}

4.redLock

redLock主要是为了防止在redis集群中,在一个节点加了锁,但在加锁后节点就挂掉了,导致之前加的锁失效,线程重复加锁的问题。

java 复制代码
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();

但红锁也有很多问题

  1. 资源竞争问题:‌当多个客户端竞争同一资源时,‌如果向多个Redis实例请求获取锁,‌容易出现没有获胜者的情况。‌这种情况下,‌没有获得过半数锁的客户端应及时释放锁,‌以避免长时间占用资源。‌

  2. 安全性问题:‌在某个主节点宕机时,‌可能会出现锁安全性问题。‌例如,‌当Redis的持久化策略为AOF使用appendfsync=everysec即每秒fsync一次时,‌故障时会丢失1秒的数据,‌这可能导致锁的丢失。‌

  3. 实现复杂性:‌虽然Redlock算法提供了一种实现分布式锁的方法,‌但在实际应用中,‌需要确保所有Redis实例的时间是同步的,‌这增加了实现的复杂性。‌

  4. 性能开销:‌由于Redlock需要在多个Redis实例上同时进行操作,‌这可能会增加额外的性能开销,‌尤其是在高并发场景下2。‌

  5. 可靠性问题:‌虽然Redlock算法设计用于避免死锁和确保最终一致性,‌但在实践中,‌如果锁定资源的服务崩溃或分区,‌仍然可能存在释放锁的可靠性问。

5.读写锁

读写锁是一种并发控制机制,用于控制对共享资源的访问。它允许多个读操作同时进行,但写操作是互斥的。这样可以在保证数据一致性的同时,提高系统的并发性能。

在Redis缓存中,我们可以将数据分为热点数据和非热点数据。热点数据是指访问频率较高的数据,而非热点数据访问频率较低。对于热点数据,我们可以采用读写锁机制,以提高并发性能。

java 复制代码
RReadWriteLock readWriteLock =  redissonClient.getReadWriteLock(mobile);
readWriteLock.readLock();
readWriteLock.writeLock();
相关推荐
运维&陈同学1 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
时差9531 小时前
Flink Standalone集群模式安装部署
大数据·分布式·flink·部署
菠萝咕噜肉i1 小时前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁
只因在人海中多看了你一眼5 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
zhixingheyi_tian7 小时前
Spark 之 Aggregate
大数据·分布式·spark
求积分不加C9 小时前
-bash: ./kafka-topics.sh: No such file or directory--解决方案
分布式·kafka
nathan05299 小时前
javaer快速上手kafka
分布式·kafka
谭震鸿12 小时前
Zookeeper集群搭建Centos环境下
分布式·zookeeper·centos
天冬忘忧17 小时前
Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化
大数据·分布式·kafka
IT枫斗者1 天前
如何解决Java EasyExcel 导出报内存溢出
java·服务器·开发语言·网络·分布式·物联网