当前位置: 首页 > news >正文

Redisson分布式锁分析,可重入、可续锁(看门狗)

前言

  在此说明,本文章不只是讲一些抽象的概念,而是可落地的,在日常工作中基本上进行修改一下便可以使用。书接上回,上篇自研分布式锁的文章使用是一个自己手写的一个分布式锁,按照JUC里面java.util.concurrent.locks.Lock接口规范编写的。其主要逻辑为:
lock()加锁关键逻辑
  1.加锁:加锁实际上就是在redis中,给key设置一个值,为了避免死锁,并给定一个过期时间;
  2.可重入:加锁的LUA脚本,通过redis里面的hash数据模型,加锁和可重入性都要保证;
  3.自旋:加锁不成,需要while进行重试并自旋,AQS;
  4.续期:在过期时间内,一定时间内业务还未完成,自动给锁续期;
  unlock()解锁关键逻辑
  将redis的key删除,但是也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉了,只能自己删自己的锁;
  考虑可重入性的递减,加锁几次就需要删除几次,最后到零了,直接del删除;上面自研的redis锁对于一般中小公司,不是特别高并发场景足够用了,单机redis小业务也撑得住。

Redis分布式锁-Redlock红锁算法

  英文名称为: Distributed locks with Redis,官方说明文档:点击此处

为什么基于故障转移的实施是不够的

  上篇文章中自己手动写的分布式锁存在的问题是单点故障时会出现数据不安全。看看官方文档怎么说?
为了理解我们想要改进的地方,让我们分析一下基于多数Redis额分布式锁库的现状。使用Redis锁定资源的最简单方法是在实例中创建一个键。使用Redis的过期功能,秘钥通常是在有限的生存时间内创建的,因此最终它会被释放。当客户需要释放资源时,它会删除秘钥。
有个问题:如果Master宕机了怎么办?主从架构条件下,添加一个副本,这种方式是不可行的。这样做我们无法实现显示互斥的安全属性,因为Redis复制是异步的。
官方文档中的一段说明进行解释,此模型存在竞争条件:
  1.Client A获取master中的锁;
  2.在对秘钥的写入传输到副本之前,主服务器崩了;
  3.副本被提升为主节点;
  4.客户端B获取对同一资源A持有锁的锁;(违反安全规定)
  有时在特殊情况下,例如在故障期间,多个客户端可以同时持有锁是完全没有问题的。如果是这种情况,您可以使用基于复制的方案。否则,我们建议实施本文档中描述 解决方案。

  简单的描述就是,当线程1首先获取锁成功,将键值写入Redis的master节点中,在Redis将该键值对同步到slave节点之前,master发生了故障;Redis触发故障转移,其中一个slave升级为新的master,此时master并不包含线程1写入的键值对,因此线程2尝试获取锁也是可以成功拿到锁的,此时相当于有两个线程获取了锁,可能会导致各种预期之外的情况发生,例如最常见的脏数据。
在这里插入图片描述
  我们加的是排它独占锁,同一时间只能有一个建Redis锁成功并持有锁,严禁出现2个以上的请求线程拿到锁。

RedLock算法设计理念

  Redis之父提出了RedLock算法解决上面这个一锁被多建的问题
  Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。 锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。

设计理念

  该方案也是基于(set加锁、Lua脚本解锁)进行改良的,所以redis之父antirez只描述了差异的地方,大致方案如下:假设我们有N个Redis主节点,例如N= 5这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统,为了取到锁客户端执行以下操作:
  1.获取当前时间,以毫秒为单位;
  2.依次尝试从5个实例,使用相同的 key和随机值(例如 UUID)获取锁。当向Redis 请求获取锁时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以防止客户端在试图与一个宕机的 Redis 节点对话时长时间处于阻塞状态。如果一个实例不可用,客户端应该尽快尝试去另外一个Redis实例请求获取锁;
  3.客户端通过当前时间减去步骤1记录的时间来计算获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且获取锁使用的时间小于锁失效时间时,锁才算获取成功;
  4.如果取到了锁,其真正有效时间等于初始有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  5.如果由于某些原因未能获得锁(无法在至少N/2+1个Redis实例获取锁、或获取锁的时间超过了有效时间),客户端应该在所有的Redis 实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用master节点,同时由于舍弃了slave,为了保证可用性,引入了N个节点。客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
  条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;
  条件2:客户端获取锁的总耗时没有超过锁的有效时间。
该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用master节点,同时由于舍弃了从节点(slave),为了保证可用性,引入了N个节点,官方建议是5。

redi只支持 AP,即高可用,为了解决CP的风险,采用N个节点,N为奇数,上面的3个master个独立,不是主从复制。为什么是奇数?N=2X+1,其中N是最终部署主机数,X是容错主机数。

什么是容错

  失败了多少个机器实例后我还是可以容忍的,所谓容就是数据的一致性还是可以的,CP数据一致性还是可以满足,加入在集群环境中,redis失败1台,可以接受。2X + 1 = 2* 1 +1 = 3, 部署3台,死了1个剩下2个可以正常工作,那就部署3台。加入在集群环境中,redis失败1台,可以接受。2X + 1 = 2* 2 +1 = 5, 部署5台,死了2个剩下3个可以正常工作,那就部署5台。

为什么是奇数

  最少的机器,最多的效果。加入集群环境中,redis失败1台,可接受。2N + 2 = 2 * 1 + 4,部署4台。

使用Redisson进行编码改造

  可重入锁,基于Redis实现的Redisson分布式可重入锁RLock java实现了java.util.concurrent.lock.Lock接口。同时还提供异步、反射和RxJava2标准额接口。
  Spring集成Redission环境开发。bom文件中引入如下代码:

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.4</version>
</dependency>

  配置类:RedisConfig

@Bean
public Redisson redisson() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0).setPassword("123456");return (Redisson) Redisson.create(config);
}

  修改服务方法:InventoryService(在日常工作中的业务成层类中)

@Autowired
private Redisson redisson;
public String saleByRedisson() {String resMessgae = "";RLock redissonLock = redisson.getLock("luojiaRedisLock");redissonLock.lock();try {// 1 抢锁成功,查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory01");// 2 判断库存书否足够Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);// 3 扣减库存,每次减少一个库存if (inventoryNum > 0) {stringRedisTemplate.opsForValue().set("inventory01", String.valueOf(--inventoryNum));resMessgae = "成功卖出一个商品,库存剩余:" + inventoryNum + "\t" + ",服务端口号:" + port;log.info(resMessgae);} else {resMessgae = "商品已售罄。" + "\t" + ",服务端口号:" + port;log.info(resMessgae);}} finally {redissonLock.unlock();}return resMessgae;
}

  仔细看上面代码依然存在释放锁的问题,可能存在删除其他线程的锁,所以在finally中添加如下方法。

finally {// 改进点,只能删除属于自己的key,不能删除别人的if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {redissonLock.unlock();}
}

watch dog(看门狗)自动延期机制

  源码中初始化了一个定时器,dely的时间是 internalLockLeaseTime / 3。在Redisson中,internalLockLeaseTime 是获取配置的看门狗的时间,默认是30s,也就是每隔10s续期一次,每次重新设置过期时间为30s。总而言之,看门狗的本质在于开启一个监听线程,定期检查锁是否持有,有则延长过期时间。
Redisson看门狗续期源码:

private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return;}Long threadId = ent.getFirstThreadId();if (threadId == null) {return;}RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getName() + " expiration", e);return;}if (res) {// reschedule itselfrenewExpiration();}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);
}

结语

  本文章到此结束,麻烦路过点赞后续还会补充内容。

相关文章:

Redisson分布式锁分析,可重入、可续锁(看门狗)

前言 在此说明&#xff0c;本文章不只是讲一些抽象的概念&#xff0c;而是可落地的&#xff0c;在日常工作中基本上进行修改一下便可以使用。书接上回&#xff0c;上篇自研分布式锁的文章使用是一个自己手写的一个分布式锁&#xff0c;按照JUC里面java.util.concurrent.locks.L…...

C++掉血迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #include <iostream> #include <string> #include <cstring> using namespace std; enum RBYG {R 1,B 2,Y 4,G 7, }; struct heal {int ix…...

Spring Boot- 数据库相关问题

Spring Boot 与数据库相关问题及其解决方案 1. 引言 Spring Boot简化了Java企业级应用的开发&#xff0c;尤其在与数据库交互方面提供了诸多便利。Spring Boot提供了多种数据库集成方案&#xff0c;涵盖关系型数据库&#xff08;如MySQL、PostgreSQL等&#xff09;与非关系型…...

秒懂C++之特殊类设计

目录 设计一个类&#xff0c;不能被拷贝 设计一个类&#xff0c;只能在堆上创建对象 设计一个类&#xff0c;只能在栈上创建对象 设计一个类&#xff0c;无法被继承 设计一个类&#xff0c;只能创建一个对象(单例模式) 饿汉模式 懒汉模式 设计一个类&#xff0c;不能被拷…...

人工智能学习

&#x1f310;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 &#x1f449;【点击跳转到网站&#xff1a;人工智能教程】 什么是人工智能&#xff1f;通俗来讲&#xff0c;就是让机器能像人一样思考。这…...

WINDOWS AGENTARENA:EVALUATING MULTI-MODAL OS AGENTS AT SCALE论文学习

文章开头说现有的agent都是局限于特定领域&#xff08;什么网络问答啊&#xff0c;仅限文字啊&#xff0c;仅限于某一个app啊&#xff09;这样的&#xff0c;本文的工作主打一个贴近用户使用场景&#xff0c;用户用什么软件&#xff0c;看什么网页&#xff0c;本文的模型就用什…...

3步轻松定制报价方案,亿发商城报价神器你用过了吗?

如果您正寻求突破传统业务模式的束缚&#xff0c;希望拥抱数字化转型带来的无限可能&#xff0c;我们诚邀您体验亿发软件。亿发专业团队将为您提供个性化的咨询和定制服务&#xff0c;帮助您的企业快速适应市场变化&#xff0c;实现业务模式和商业模式的创新。...

CISP备考题库(五)

在当今这个飞速发展的数字化时代&#xff0c;信息安全已跃居至前所未有的战略地位&#xff0c;其重要性伴随着技术的日新月异而持续攀升&#xff0c;成为了一个不容小觑的关键领域。为了激发并引领广大青年才俊积极投身于网络安全专家的崇高事业&#xff0c;我们精心策划并编纂…...

【Kubernetes】常见面试题汇总(二十三)

目录 69.考虑一家拥有分布式系统的跨国公司&#xff0c;拥有大量数据中心&#xff0c;虚拟机和许多从事各种任务的员工。您认为这样公司如何以与 Kubernetes 一致的方式管理所有任务&#xff1f; 70.考虑一种情况&#xff0c;即公司希望通过维持最低成本来提高其效率和技术运营…...

linux-Shell 编程-Shell 脚本基础

Linux Shell 编程&#xff1a;Shell 脚本基础 在Linux系统中&#xff0c;Shell脚本是一种强大的自动化工具。通过编写Shell脚本&#xff0c;用户可以自动化重复性任务、系统管理操作和程序控制流程&#xff0c;极大提高工作效率。 1. 什么是Shell脚本&#xff1f; Shell脚本是…...

Linux运维篇-tigervnc工具的使用

目录 简介下载使用clientserver配置文件服务管理 设定密码&#xff08;先切换成对应的用户&#xff09;&#xff1a;配置多用户的VNC tigervnc连接排错一、vnc密码错误二、vncserver端口忘记了三、连接很卡&#xff0c;或者画面没有反应四、服务报错 简介 TigerVNC是VNC的一种…...

基于Spark的电影推荐系统设计与实现(论文+源码)_kaic

摘 要 在云计算、物联网等技术的带动下&#xff0c;我国已步入大数据时代。电影是人们日常生活中重要的一种娱乐方式&#xff0c;身处大数据时代&#xff0c;各种类型、题材的电影层出不穷&#xff0c;面对琳琅满目的影片&#xff0c;人们常感到眼花缭乱。因此&#xff0c;如…...

基于python+django+vue的医院预约挂号系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤pythondjangovue…...

镀金引线---

一、沉金和镀金 沉金和镀金都是常见的PCB金手指处理方式&#xff0c;它们各有优劣势&#xff0c;选择哪种方式取决于具体的应用需求和预算。 沉金&#xff08;ENIG&#xff09;是一种常用的金手指处理方式&#xff0c;它通过在金手指表面沉积一层金层来提高接触性能和耐腐蚀性…...

『功能项目』窗口可拖拽脚本【59】

本章项目成果展示 我们打开上一篇58第三职业弓弩的平A的项目&#xff0c; 本章要做的事情是给坐骑界面挂载一个脚本让其显示出来的时候可以进行拖拽 创建脚本&#xff1a;DraggableWindow.cs using UnityEngine; using UnityEngine.EventSystems; public class DraggableWindo…...

Map--08--CurrentHashMap 与 Hashtable的异同?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Map方法computeIfAbsent1.computeIfAbsent 方法的简介2.案例computeIfAbsent() Map方法computeIfAbsent computeIfAbsent方法是Java 8中引入的一种简化操作Map的方…...

Docker学习笔记(三)存储与卷

挂载机制介绍 我们都知道&#xff0c;默认下&#xff0c;Docker容器与宿主机是完全隔离的&#xff0c;这种特性使得我们创建与删除容器都变得更方便&#xff0c;不需要再去删除宿主机上容器遗留下来的痕迹。   但是&#xff0c;当我们使用数据库一类需要持久化数据、共享数据…...

硬件工程师笔试面试——滤波器

目录 12、滤波器 12.1 基础 滤波器原理图 滤波器实物图 12.1.1 概念 12.1.2 滤波器的分类 12.1.3 滤波器的工作原理 12.1.4 滤波器的应用 12.1.5 滤波器设计的关键参数 12.2 相关问题 12.2.1 不同类型的滤波器在实际应用中的具体作用是什么? 12.2.2 如何设计一个简…...

【SpringBoot3】面向切面 AspectJ AOP 使用详解

文章目录 一、AspectJ介绍二、简单使用步骤1、引入依赖2、定义一个Aspect3、开启AOP支持 三、AOP 核心概念四、切点&#xff08;Pointcut&#xff09;1. execution2. within3. this & target4. args & args5. within & target & annotation 五、通知&#xff0…...

wav怎么转mp3格式?给你推荐几种音频格式转换方法

wav怎么转mp3格式&#xff1f;将wav文件转换为MP3格式是一个常见的操作&#xff0c;尤其适用于需要节省存储空间或确保文件兼容性的场景。wav文件保存了音频的所有原始数据&#xff0c;这使得它们的文件体积往往非常庞大。相比之下&#xff0c;MP3格式通过有损压缩技术显著减小…...

Redis的AOF持久化、重写机制、RDB持久化、混合持久化

1、AOF持久化 1.1.AOF持久化大致过程 概括&#xff1a;命令追加&#xff08;append&#xff09;、文件写入、文件同步&#xff08;sync&#xff09; Redis 每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到一个文件里&#xff0c;然后重启 Redis 的时候&#…...

Dom4j使用xpath查询xml文

Dom4j使用xpath查询带有命名空间的xml文件 方式1 忽略命名空间 DocumentFactory factory DocumentFactory.getInstance(); SAXReader reader new SAXReader(factory); Document document reader.read(xmlFilePath); Element rootElement document.getRootElement(); Nod…...

国家专精特新小巨人企业指标解析与扶持领域

一、什么是国家专精特新小巨人 &#xff08;一&#xff09;概念与定义 专精特新“小巨人”企业是指那些在细分市场中具有专业化、精细化、特色化和新颖化特征的中小企业中的佼佼者。这些企业在创新能力强、市场占有率高、掌握关键核心技术以及质量效益方面表现突出&#xff0…...

进程的属性

tips&#xff1a; task_struct就是linux下的PCB 操作系统不相信任何外部用户&#xff0c;而是只提供窗口&#xff0c;不可能直接与用户打交道&#xff0c;而是通过操作系统 tast_struct用来描述所有进程&#xff0c;用来管理 &#xff1b; 和 && 可以同时跑两个命令 进…...

Git 中的refs

在 Git 中&#xff0c;refs 是用来存储 Git 对象&#xff08;如提交、树、标签等&#xff09;的引用。每个 ref 都是一个指针&#xff0c;指向一个特定的 Git 对象。以下是 Git 中几种常见的 refs 及其含义&#xff1a; 1. refs/heads/ 表示&#xff1a;本地分支。 用途&…...

408算法题leetcode--第六天

58. 最后一个单词的长度 58. 最后一个单词的长度思路&#xff1a;反向遍历时间&#xff1a;O(n)&#xff1b;空间&#xff1a;O(1) class Solution { public:int lengthOfLastWord(string s) {int id s.size() - 1;while(s[id] ){--id;}int ret 0;while(id > 0 &&…...

ubuntu64位系统无法运行32位程序的解决办法

在 64 位的 Ubuntu 系统上运行 32 位程序时&#xff0c;如果出现问题&#xff0c;可能是由于缺少 32 位库支持。以下步骤可以帮助你解决这一问题&#xff1a; 1. 启用 32 位架构 首先&#xff0c;确保系统支持 32 位架构。你可以通过以下命令添加 32 位架构支持&#xff1a; …...

深入理解Go语言中的并发封闭与for-select循环模式

在现代编程中,并发已经成为提高程序性能和响应能力的关键手段。然而,在并发环境下,如何安全地访问和操作共享数据却是一大挑战。本文将深入探讨Go语言中的**封闭(confinement)**技术,以及常见的for-select循环模式,帮助您编写更高效、更安全的并发代码。 并发编程中的安…...

Java学习Day42:骑龙救!(springMVC)

springMVC与sevlet都是对应表现层web的&#xff0c;但是越复杂的项目使用SpringMVC越方便 基于Java实现MVC模型的轻量级web框架 目标&#xff1a; 小案例&#xff1a; 1.导入依赖 spring-context: 提供 Spring 框架的核心功能&#xff0c;如依赖注入、事件发布和其他应用上…...

原型模式详细介绍和代码实现

&#x1f3af; 设计模式专栏&#xff0c;持续更新中&#xff0c; 欢迎订阅&#xff1a;JAVA实现设计模式 &#x1f6e0;️ 希望小伙伴们一键三连&#xff0c;有问题私信都会回复&#xff0c;或者在评论区直接发言 Java实现原型模式 介绍: 原型模式&#xff08;Prototype Patte…...