Redis实现分布式锁

ops/2025/2/13 21:32:22/

一、使用分布式锁的背景是什么

1、如果你公司的业务,各个应用都只部署了一台机器,那么完全用不着分布式锁,直接使用Java的锁即可

2、可是当你们的业务量大,多台机器并发情况下争夺一个资源的时候,就必须要保证业务的原子性了。

举例:

例子1、超卖问题:一共100个商品待抢购,当还有最后一个库存的时候,实例1,2,3.....10都查询到还有库存,用户同时都下单成功;那么就出现了超卖问题:你一共100的库存,哪来的110个商品发货?

怎么解决呢?将100个商品写入redis中,并加上分布式锁,必须一个实例,拿到锁,并完成交易,扣除库存后,释放锁,才能让下一个实例拿到锁;这样就保证了不会超卖

例子2、定时任务问题:多台分布式应用,要执行对账,如果多台机器同时执行,就有可能产生脏数据;如果使用分布式锁,使同时只有一个应用实例去执行一个任务,这样就实现了互斥,不会导致脏数据产生

二、Redis分布式锁,是如何实现的?

Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则set)的简写

获取锁:

#添加锁,lock是锁的名称,value就是值(根据业务命名) NX代表互斥,EX是设置超时时间
SET lock value NX EX 10

释放锁:

# 释放锁,删除即可
DEL key

分布式锁必须加超时时间的原因是,如果应用获取到锁后,宕机了,那么就永远释放不了锁了

三、Redisson实现分布式锁,如何控制锁的有效时长?

如果应用使用了分布式锁,但是在锁的有效时间都超过了(锁失效了),那么应用的其他实例就会拿到锁,也去执行业务代码,就有可能会导致脏数据产生

那么如何合理控制锁的有效时长呢?

1、根据业务预估

根据业务代码评估后,觉得实例拿到锁之后,多久能跑完,就给锁设值多久过期

但是这样很不准确

2、动态地给锁续期

我只要拿到锁的线程还在跑,那我就一直给锁加有效时间(超时时间)即可,每次加10秒钟,诶,非常合理

 有以下两种方式:

1、自己代码里新开一个线程,每隔10秒或者多少秒,延长一下锁的有效时间(过期时间)

或者说,在数据库标记一个标识,新开一个线程记录当前线程id + 状态,根据状态判断是否需要延期

2、使用Redisson的看门狗机制

Redisson自带有一个watch dog(看门狗机制),

如图所示,

1、实例1在获取到锁后,在加锁后,Redisson会另外开一个线程(看门狗线程),监控持有锁的线程,会不断增加持有锁的线程的持有锁的时间(超时时间,默认10秒)

看门狗线程,每次续期(release / 3)的时间,其中release是锁的默认超时时间:30s,也就是说默认10秒钟续期一次,每次重新设置为30秒过期时间

当实例1释放锁的时候,需要通知一次看门狗,不需要再做监听了,因为key已经被删除了

2、在实例2,尝试加锁的过程中,如果加锁失败,会循环,尝试获取锁;

当然实例2也不会无限循环尝试获取,一段时间没有加锁成功就会报错,这就是Redisson新增的重试机制

public void redisLock() throws InterruptedException{// 获取锁(重入锁),执行锁的名称RLok lock = redissonClient.getLock("myLockName");// 尝试获取锁,参数分别是:1、获取锁的最大等待时间(就是未获取到锁循环尝试获取的时间)// 2、锁自动释放时间(过期时间),如果你设置了这个值,那么Watch dog(看门狗)就不会生效,因为他会认为你能控制锁的超时时间,不会定时给你续期// 3、第三个参数为 前两个时间参数的单位// boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);// 判断是否获取锁成功if(isLock) {try {System.out.prientln("执行你的业务逻辑");} finally {// 释放锁lock.unlock();}}
}

所有的加锁解锁,设置过期时间,看门狗续期等,都是根据lua脚本来实现的,保证执行的原子性

四、Redisson实现的分布式锁,可以重入吗?

为什么同一个线程要多次拿到锁呢?

因为如果一个任务,根据不同的传参,可能有不同的判断,要调用不同的方法,调用不同的方法耗时可能不同,那么就可以使用重入机制,调一个方法就延长一下锁的有效时长;

所以问题的答案是:Redisson实现的分布式锁可以重入redission中可以记录线程id,使同一线程的不同方法,都可以重复拿到锁

如图:add1方法获取锁后,Redis会使用hash结果记录锁的key,获取到锁的线程id、以及重入次数

当add1方法调用add2方法后,add2方法也可以获取到锁,这时Redis会把重入次数加一;

当add2释放锁之后,重入次数会减一

当add1释放锁之后,重入次数也会减一,当重入次数value=0时,Redis就会把这个锁删除

五、Redission可以解决Redis主从不一致的问题吗?

什么叫主从不一致的问题?

如果说,你的应用实例1在主节点写入了一个分布式锁,这时候其他实例2,3,4是拿不到分布式锁的;

而如果在你实例1还在执行的时候,Redis主节点挂了(宕机了)!这时候呢,Redis的一个从节点会自动升级为主节点,但是这个新的从节点,里面是没有这个分布式锁的,那么实例2,3,4就又可以获得一个锁,来进行代码的执行,这就有可能导致脏数据问题

所以:Redisson分布式锁,是不能解决主从一致性问题的。

但是,Redis中有另一种解决方案,叫做红锁(redlock)机制,就是把分布式锁的信息保存在X台(X = Redis节点数 / 2  + 1,即超过半数)Redis的节点上;每次加锁,都必须拿到这X台(超过半数)机器的锁,才算真正获得锁

红锁:

但是红锁,一般是不会使用的,因为这样性能太低了,如果硬是要保持数据强一致性,还可以使用zookeeper实现的分布式锁,或者其他机制


http://www.ppmy.cn/ops/158142.html

相关文章

【面试集锦】如何设计SSO方案?和OAuth有什么区别?

如何设计SSO方案?和OAuth有什么区别?--楼兰 带你聊最纯粹的Java ​ 如果面试问你,你会做一个权限系统吗?那你肯定会说做过。不就是各种登录、验证吗。我做的第一个CRUD应用就是注册、登录。简单!但是,如果问你在工作中真的做过权限系统吗?其实很多人都只能默默摇摇头。因…

使用 mkcert 本地部署启动了 TLS/SSL 加密通讯的 MongoDB 副本集和分片集群

MongoDB 是支持客户端与 MongoDB 服务器之间启用 TLS/SSL 进行加密通讯的, 对于 MongoDB 副本集和分片集群内部的通讯, 也可以开启 TLS/SSL 认证. 本文会使用 mkcert 创建 TLS/SSL 证书, 基于创建的证书, 介绍 MongoDB 副本集、分片集群中启动 TLS/SSL 通讯的方法. 我们将会在…

ASP.NET Core DDD

目录 什么是微服务 单体结构项目 微服务架构项目 微服务架构误区 什么是DDD DDD领域与领域模型 领域(Domain) 领域模型(Domain Model) 事务脚本 事务脚本的问题 通用语言与界限上下文 通用语言 界限上下文 实体与值…

DeepSeek时代:百度们亟需“深度求索”

文:互联网江湖 作者:刘致呈 眼看着梁文峰被捧上中国AI神坛,科技巨头们的心情一定是复杂的。 就像大刘笔下的《三体》中,当三百年后的人类太空舰队,面对水滴探测器时是五味杂陈的。 当科技大佬们纷纷断言,…

Flutter 中的生命周期

在 Flutter 中,StatefulWidget 和 StatelessWidget 这两种 Widget 的生命周期不同,主要关注的是 StatefulWidget,因为它涉及到状态的管理和更新。 StatefulWidget 的生命周期: 1. 创建阶段 (Create) createState():…

关于工厂模式和单例模式

工厂模式 工厂模式就是将对象的创建过程封装在一个工厂类中,将创建对象的任务交给工厂完成。外部只能通过工厂类来指定创建或查找一个什么类型的对象,但不能直接创建对象。这样的好处在于实现了创建逻辑和业务逻辑的解耦。让代码变得更好看。 工厂模式又…

Vue全流程--Vue3.0与Vue2.0响应式原理对比

Vue2中数据的响应式 需要使用Vue.set这么一个api,修改数据 需要使用Vue.delete这么一个api,删除数据 数据代理这个当面的理解可以看看我前面文章Vue全流程--数据代理的理解以及在Vue中的应用-CSDN博客 Vue3中数据的响应式 Vue3使用proxy这个api实现…

Render上后端部署Springboot + 前端Vue 问题及解决方案汇总

有一个 Vue 前端 和 Spring Boot 后端的动态网页游戏,当前在本地的 5173 端口和运行。你希望生成一个公开链接,让所有点击链接的人都能访问并玩这个游戏。由于游戏原本需要在本地执行 npm install 后才能启动,你现在想知道在部署时是选择 Ren…