redis分布式锁

分布式锁

分布式锁的理念

  • 独占性:任何一个时刻有且仅有一个线程持有
  • 高可用:若redis集群环境下,不能因为一个节点挂了而出现获取锁和释放锁失败的情况
  • 防死锁:杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底的终止跳出方案
  • 不乱抢:防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放
  • 重入性:同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁

分布式锁逐步剖析

下面的考虑都是建立在分布式锁的前提下(对于单机的锁来说的话并不是完全适用的)

单机下加锁

单机如果都没有加锁,在单价下就会导致超卖,保证不了数据的一致性

加锁解锁保证同时出现并保证调用

释放锁的代码必须放入到finally块中,保证任何情况下都会被执行

加锁必须考虑时间问题

防止加完锁后,服务可能会挂掉,导致这个锁无期限的锁下去,所以对redis分布式锁的key必须加上一个过期时间的限制

加锁和设置过期时间必须保证原子性

否则照样会出现上面的问题(要特别注意,在高并发的场景下,任何的有锁操作或者资源争用,一定要时时刻刻的警惕原子性,防止自己犯错)

张冠李戴,删除了别人的锁

第一个进来的线程自己执行的方法时间过长了,redis的key到期自动失效,这时候第二个线程进来开始加锁干活,第二个还没走完,第一个线程执行完毕,直接删掉了第二个线程的加锁key,这样的话就导致了混乱.(判断value是自己的才去删除,否则不删除)

判断锁是自己的和删除锁非原子性

这样的话你判断完的一瞬间去删除锁还是有个时间差,在高并发下很有可能你删除的锁还是不是自己的,还是会误删,所以高并发的锁相关操作一定要考虑原子性,每一步都要谨慎(采用lua脚本来保证原子性,redis对lua脚本有原生的支持,专门用来确保一系列的操作的原子性)

确保redis锁的过期时间永远大于业务执行时间

可以用看门狗 watchDog解决

redis异步复制锁丢失

redis集群是AP属性,在master和slave切换的时候有可能就造成锁的丢失(主节点还没同步给从节点,主节点就挂了,从节点升级为主节点,锁就丢失了) zookeeper集群是CP属性(zookeeper保证了一致性,整个节点都通知到才算成功,但是他牺牲了高可用性来保证的一致性)
用RedLock之Redisson落地实现解决

Redisson解决方案

分布式锁解决方案

public static String key = "redisLock";

    public String redisLock() {
        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        RLock lock = redisson.getLock(key);
        lock.lock();
        try {
            //doSomeThing
            return "完成";
        } finally {
            //避免张冠李戴
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

RedLock

容错率公式

N(部署台数) = 2*(宕机数量)+1

设计理念
  • 获取当前时间,以毫秒为单位
  • 依次尝试从5个实例,使用相同的key和随机值获取锁.当向Redis请求获取锁时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间,例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间,这样可以防止客户端在试图与一个宕机的Redis节点对话时长时间处于阻塞状态.如果一个实例不可用,客户端应该尽量尝试去另一个Redis实例请求获取锁
  • 客户端通过当前时间减去步骤1记录的时间来计算获取锁使用的时间,当且仅当从大多数(N/2+1)的Redis节点都获取到锁,并且获取锁使用的时间小于锁失效时间,锁才算获取成功
  • 如果获取到了锁,其真正有效时间等于初始有效时间减去获取锁所用的时间
  • 如果由于某些原因未能获取锁(至少无法在至少N/2+1个Redis实例获取锁,或者获取锁的时间超过了有效时间),客户端应该在所有的Redis实例上解锁(即使某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)

该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用master节点,同时由于舍弃了slave,为了保证可用性,引入了N个主节点,官方建议是5,其实一般3个也行,基本够用了

代码案例
public String getLock(){
        String value = UUID.randomUUID() + Thread.currentThread().getName();
        RLock lock1 = redissonClient1.getLock(key);
        RLock lock2 = redissonClient2.getLock(key);
        RLock lock3 = redissonClient3.getLock(key);
        RedissonRedLock redLock = new RedissonRedLock(lock1,lock2,lock3);
        boolean isLockBoolean;
        try {
            //waitTime,抢锁的等待时间
            //leaseTime就是redis的key的过期时间
            isLockBoolean = redLock.tryLock(3,300, TimeUnit.SECONDS);
            if (isLockBoolean){
                //doSomeThing
                return "true";
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            redLock.unlock();
        }
        return "false";
    }

缓存续命(watchDog)

实现原理

额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间.Redisson里面就实现了这个方案,使用"看门狗"定期检查(每1/3的锁时间检查一次),如果线程还在持有锁,则刷新过期时间

代码跟踪

redisson的lock方法自带了看门狗,直接进行锁续命
默认值设置
1/3检查一次

lua脚本加锁解释

lua脚本解释

参数解释
KEYS[1]代表的是你加锁的那个key就是你自己设置的redis加锁的那个锁的key
ARGV[2]代表的是加锁的客户端ID锁的hash值
ARGV[3]就是锁key的默认生存时间默认30秒
如何加锁你要加锁的那个key不存在的话,你就进行加锁 hincrby 7bcf6a9f-e7f7-49b0-9727…:117 1 接着会执行 pexpire lockzzyy 30000
  • 通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
  • 通过hexists判断,如果锁已经存在,并且锁的是当前的线程,则证明是重入锁,加锁成功(会把hash出来的那个整数值加一)
  • 如果锁已经存在,但是锁的不是当前线程,则证明有其他的线程持有锁,返回当前锁的过期时间,此时加锁失败
lua脚本解锁解释

lua解锁脚本

### IntelliJ IDEA 中通义 AI 功能介绍 IntelliJ IDEA 提供了一系列强大的工具来增强开发体验,其中包括与通义 AI 相关的功能。这些功能可以帮助开发者更高效地编写代并提高生产力。 #### 安装通义插件 为了使用通义的相关特性,在 IntelliJ IDEA 中需要先安装对应的插件: 1. 打开 **Settings/Preferences** 对话框 (Ctrl+Alt+S 或 Cmd+, on macOS)。 2. 导航到 `Plugins` 页面[^1]。 3. 在 Marketplace 中搜索 "通义" 并点击安装按钮。 4. 完成安装后重启 IDE 使更改生效。 #### 配置通义服务 成功安装插件之后,还需要配置通义的服务连接信息以便正常使用其提供的各项能力: - 进入设置中的 `Tools | Qwen Coding Assistant` 菜单项[^2]。 - 填写 API Key 和其他必要的认证参数。 - 测试连接以确认配置无误。 #### 使用通义辅助编程 一旦完成上述准备工作,就可以利用通义来进行智能编支持了。具体操作如下所示: ##### 自动补全代片段 当输入部分语句时,IDE 将自动提示可能的后续逻辑,并允许一键插入完整的实现方案[^3]。 ```java // 输入 while 循环条件前半部分... while (!list.isEmpty()) { // 激活建议列表选择合适的循环体内容 } ``` ##### 解释现有代含义 选中某段复杂的表达式或函数调用,右键菜单里会有选项可以请求通义解析这段代的作用以及优化意见。 ##### 生产测试案例 对于已有的业务逻辑模块,借助于通义能够快速生成单元测试框架及初始断言集,减少手动构建的成本。 ```python def test_addition(): result = add(2, 3) assert result == 5, f"Expected 5 but got {result}" ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的第11小时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值