Redis篇之分布式锁知识点总结---(温故而知新版本)---(加油哦)

分布式锁用于解决多JVM进程间共享资源的互斥访问,基本要求包括互斥、高可用和可重入。Redis的SETNX命令可帮助实现互斥,结合Lua脚本保证原子性解锁。Redisson提供了WatchDog自动续期机制,确保锁在必要时得到续期,避免锁失效。可重入锁允许同一线程多次获取同一把锁,实现检查占有锁的线程是否为当前线程来实现这一功能。
摘要由CSDN通过智能技术生成

分布式锁:
分布式系统下,不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。于是,分布式锁 就诞生了。

一个最基本的分布式锁需要满足:

互斥 :任意一个时刻,锁只能被一个线程持有;
高可用 :锁服务是高可用的。并且,即使客户端的释放锁的代码逻辑出现问题,锁最终一定还是会被释放,不会影响其他线程对共享资源的访问。
可重入:一个节点获取了锁之后,还可以再次获取锁。

基于 Redis 实现分布式锁

在 Redis 中, SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent() 方法),如果 key 不存在的话,才会设置 key 的值。如果 key 已经存在, SETNX 啥也不做。

为了防止误删到其他的锁,我们使用 Lua 脚本通过 key 对应的 value(唯一值)来判断。

选用 Lua 脚本是为了保证解锁操作的原子性。因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,从而保证了锁释放操作的原子性

// 释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

为了避免锁无法被释放,给这个 key(也就是锁) 设置一个过期时间 。

一定要保证设置指定 key 的值和过期时间是一个原子操作

如果操作共享资源的时间大于过期时间,就会出现锁提前过期的问题,进而导致分布式锁直接失效;如果锁的超时时间设置过长,又会影响到性能;
如何解决?

实现锁的优雅续期?

Redisson实现

Redisson 中的分布式锁自带自动续期机制,其提供了一个专门用来监控和续期锁的 Watch Dog( 看门狗),如果操作共享资源的线程还未执行完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。

在这里插入图片描述
renewExpiration() 方法包含了看门狗的主要逻辑:

private void renewExpiration() {
         //......
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                //......
                // 异步续期,基于 Lua 脚本
                CompletionStage<Boolean> future = renewExpirationAsync(threadId);
                future.whenComplete((res, e) -> {
                    if (e != null) {
                        // 无法续期
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }

                    if (res) {
                        // 递归调用实现续期
                        renewExpiration();
                    } else {
                        // 取消续期
                        cancelExpirationRenewal(null);
                    }
                });
            }
         // 延迟 internalLockLeaseTime/3(默认 10s,也就是 30/3) 再调用
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

        ee.setTimeout(task);
    }

Watch Dog 通过调用 renewExpirationAsync() 方法实现锁的异步续期,renewExpirationAsync 方法调用 Lua 脚本实现的续期,这样做主要是为了保证续期操作的原子性

如何使用 Redisson 实现分布式锁:

// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();

只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制:

// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制
lock.lock(10, TimeUnit.SECONDS);

可重入锁?

可重入锁指的是在一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法 ,而无需重新获得锁

如何实现可重入锁?

实现核心思路是线程:在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值