Redisson分布式锁实现及原理详解

        随着技术快速发展,数据规模增大,分布式系统越来越普及,一个应用往往会部署在多台机器上(多节点),在有些场景中,为了保证数据不重复,要求在同一时刻,同一任务只在一个节点上运行,即保证某一方法同一时刻只能被一个线程执行。在单机环境中,应用是在同一进程下的,只需要保证单进程多线程环境中的线程安全性,通过 JAVA 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。而在多机部署环境中,不同机器不同进程,就需要在多进程下保证线程的安全性了。因此,分布式锁应运而生。

场景分析:

     假如现在有个卫生间,里面只有一个坑位,此时A、B、C三名同学都想上厕所,A拉粑粑需要30s,B拉粑粑需要40s,C拉粑粑需要50s,于是乎A、B、C三名同学来到卫生间后,只有一名同学能够获得坑位的使用权。假设A先到的,进入坑位后将门关住,表示厕所有人,此时B和C只能在外面等待。但是会遇到如下几种情况:

        1、A在进入坑位上厕所时,一不小心掉进坑里了,由于A没有出来,导致门口的锁一直处于被锁住的状态,此时B和C由于锁未释放无法进去,因此只能无限等待,造成了资源的浪费。

        2、A进入厕所后对着B和C说我大概20s就好了(对应设置锁过期时间为20s的操作),20s后A还没有上完厕所,此时B或C看到锁释放了,便进入了厕所,导致A和他人共用厕所的情况发生。

        3、A在进入厕所后在门口加了一个锁,表示此时是A在厕所里,B和C此时看到A在厕所里,只能在外面等待,后来A上完厕所,释放A的锁,此时B进入厕所加了一个锁表示B在厕所里,但A把B的锁给释放掉了,会导致C以为厕所内现在没有人,C进入厕所,也会导致B和C共有厕所的情况。

以上三种情况对应于分布式锁要解决的三个问题:

        1、锁要设置过期时间,不能让某个线程长时间持有锁,会导致资源浪费。

        2、在方法未执行完成时,若锁过期,则需要延长锁的过期时间(看门狗机制),直至方法执行完毕。

        3、每个线程只能释放掉自己加的锁,不能释放掉其他线程获得锁,如果当前线程对应的锁不存在,说明该锁已过期,不做任何操作即可。

1. Redisson分布式锁

        Redisson基于Rediss的Java库,封装了常用功能(如数据缓存、消息队列等)以及分布式系统开发的工具,如分布式锁、分布式集合、分布式信号量、分布式执行器等,同时也封装了 Redis 中的常见数据结构,如 MapSetListQueueDeque 等。

示例代码:

@Scheduled(cron = "0 26 16 * * *")
public void doCacheRecommendUser() {RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock");try {//tryLock(long waitTime, long leaseTime, TimeUnit unit)//waitTime表示 等待获取锁的时间。如果设置为 0,意味着不会等待,会立即尝试获取锁。//leaseTime表示 锁的租约时间,即锁的有效时间。在 Redisson 中,如果设置为 -1,则表示 锁永不过期,除非显式解锁 (unlock()),否则锁会一直存在。if (lock.tryLock(0, -1, TimeUnit.MICROSECONDS)) {System.out.println("getLock: " + Thread.currentThread().getId());Thread.sleep(40000);for (Long userId : mainUserList) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper);String redisKey = String.format("yupao:user:recommend:%s", userId);ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();// 写缓存try {valueOperations.set(redisKey, userPage, 30000, TimeUnit.SECONDS);} catch (Exception e) {log.error("redis set key error", e);}}}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//只能自己释放自己的锁if (lock.isHeldByCurrentThread()) {System.out.println("unlock: " + Thread.currentThread().getId());lock.unlock();}else{System.out.println("当前线程不持有锁,不能释放锁。");}}
}

分析:

        RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock")redissonClient.getLock("yupao:precachejob:docache:lock") 只是通过RedissonClient获取了一个名为"yupao:precachejob:docache:lock"锁对象(RLock但在实际使用之前,Redis中不会存储该锁的任何信息。

        只有当调用lock.lock()lock.tryLock()等方法时,才会在Redis中创建实际的锁

  lock.tryLock(0, -1, TimeUnit.MICROSECONDS)表示当前线程会尝试获取可重入lock锁,0表示立即尝试获取,不进行等待。-1表示锁的过期时间为-1(无限制),表明不手动设置过期时间,系统会为该锁默认设置30s的过期时间,如果方法实际的运行时间大于30s,会根据看门狗机制来为该锁续期,直至方法执行完毕,调用lock.unlock()方法来手动释放锁。

        而在释放锁的过程中需要判断当前线程持有的是哪个锁,每个线程只能释放自己加的锁,不能释放掉其他线程加的锁

        具体实现原理:redissonClient.getLock("yupao:precachejob:docache:lock")会获取一个名为"yupao:precachejob:docache:lock"锁对象(RLock),当调用lock.lock()lock.tryLock()方法时,会创建一个名为lock的map对象,这个map中的key由Redisson客户端id(UUID)和持有锁的线程id构成,value是锁重入计数。这样当释放锁时,先通过lock.isHeldByCurrentThread()来判断当前线程持有的锁是否是自己的,若是则释放,否则无法释放。

原理图:

2. 看门狗机制

        在分布式锁中,看门狗机制通常用于自动续期锁,以确保当任务执行时间超过预期时,锁不会意外过期和被其他进程或线程抢占。

两种锁使用方式对比:
1. 手动设置锁的过期时间(不会自动续期)

当你手动设置锁的过期时间时,例如:

RLock lock = redissonClient.getLock("myLock");
lock.lock(10, TimeUnit.SECONDS);  // 手动设置锁过期时间为10秒
try {// 执行任务Thread.sleep(20000);  // 模拟长时间任务(超过了锁的过期时间)
} finally {lock.unlock();  // 释放锁
}

        在上述例子中,锁的有效期是 10 秒,但任务执行时间是 20 秒。因此,锁会在任务执行期间被 Redis 自动释放,因为它达到了手动设置的过期时间。这时,其他进程或线程可能会获取锁,导致并发冲突。

2. 使用看门狗机制(自动续期锁)

        为了确保锁在任务执行过程中不会过期,可以不设置过期时间,这会启用 Redisson 的看门狗机制。看门狗机制会定期检查任务状态,并在任务未完成时自动续期锁的过期时间:

RLock lock = redissonClient.getLock("myLock");
lock.lock();  // 不设置过期时间,启用看门狗机制
try {// 执行任务Thread.sleep(20000);  // 模拟长时间任务
} finally {lock.unlock();  // 任务完成后显式释放锁
}

        在这种情况下,Redisson 的看门狗机制会在任务执行过程中每隔 10 秒自动续期锁的过期时间(默认是延长 30 秒),直到任务完成并手动释放锁。这样,即使任务执行时间超过了最初的锁的过期时间,锁仍然不会被其他线程抢占。

Redis 分布式锁中的看门狗工作原理

        当执行lock.tryLock(0, -1, TimeUnit.MICROSECONDS)时,当前线程会尝试获取锁,由于锁的过期时间设置为-1,因此会启用看门狗机制,此时该锁的默认过期时间为30000毫秒(30秒),看门狗机制会异步执行一个监听器,每隔internalLockLeaseTime/3之后执行,算下来就是大约10秒钟执行一次,如果当前方法未完成,则会延长锁的过期时间,直至方法完成释放锁。

        lock.tryLock(0, 30000, TimeUnit.MICROSECONDS),此时手动设置锁的过期时间为30000毫秒,当30000毫秒后,该锁会自动释放(无论方法是否执行完成),因此会导致线程不安全,引起并发问题。

  1. 锁的获取
    • 当某个客户端成功获取 Redis 分布式锁时,它会为锁设置一个默认的过期时间,例如 30 秒。这意味着如果客户端在 30 秒内没有释放锁,Redis 会自动释放锁,以避免死锁。
  1. 看门狗的启动
    • Redisson 在客户端成功获取锁后,会启动看门狗线程。
    • 默认情况下,这个看门狗会在锁的到期时间快到时自动续期锁的过期时间,例如每 10 秒检查一次是否需要将锁的过期时间延长 30 秒。
  1. 自动续期
    • 如果客户端仍在持有锁并且任务还没有完成,看门狗会自动续期,防止锁被 Redis 自动释放,从而确保任务可以在锁的保护下继续执行。
    • 如果任务执行时间超出了最初的 30 秒,看门狗会每隔 10 秒续期一次锁的过期时间,确保锁的有效性。
  1. 锁的释放
    • 一旦任务执行完成,客户端会显式调用 unlock() 方法释放锁,锁被释放后,看门狗线程会停止工作,锁的续期也随之停止。
    • 如果任务因为某些异常情况未能完成(如客户端崩溃),看门狗机制会确保锁在没有续期的情况下最终被 Redis 释放,防止锁被永远占用

3 RedLock解决Redis主从不一致性问题

场景:根据前面的分析解决了多个服务器之间的线程安全问题,防止了超卖和超买等问题,但是上述方案的前提都是有多个服务器,单Redis服务器的情况,此时所有的资源都存在一个主Redis服务器上,如果主服务器发生宕机,依然会导致线程不安全。

        针对于上述问题,当Redis是集群架构时,为防止主从服务器的锁不一致性问题,Redisson使用RedLock来实现,核心思想是:有N个Redis实例时,只有当N/2 + 1个Redis实例成功获得锁时,才表示当前锁获取成功,否则重新获取。

RedLock 的基本流程:
  1. 多实例锁获取
    • 系统中假设有 N 个 Redis 实例(推荐使用 3 或 5 个 Redis 实例)。客户端(如应用程序中的某个进程)会尝试依次向所有 Redis 实例获取同一个锁。
    • 客户端使用相同的锁键(例如 my-lock)和相同的过期时间来请求每个 Redis 实例。
  1. 设置锁的唯一标识
    • 每个锁请求会生成一个 唯一标识符(通常是 UUID),以确保不同的客户端或进程不会混淆彼此的锁请求。
  1. 获取多数锁
    • 客户端尝试在 N 个 Redis 实例中获取锁,只有当客户端能在超过 半数的 Redis 实例(即至少 N/2 + 1)中成功获得锁时,才认为锁获取成功。
    • 锁请求必须在设置的超时时间内完成(通常是 Redis 实例的网络延迟和 RTT 时间的几倍),以确保客户端不会因为网络或实例故障无限期等待锁。
  1. 锁的过期时间
    • 每个锁设置时都会有一个过期时间,以避免死锁。当客户端获取锁后,如果客户端崩溃或者没有手动释放锁,Redis 会在锁的过期时间到达时自动释放锁。
  1. 锁的释放
    • 一旦客户端完成了对共享资源的操作,它会向所有持有锁的 Redis 实例发送一个解锁命令来释放锁。
    • 只有持有该锁唯一标识符的客户端才能成功释放锁,防止其他客户端误释放不属于自己的锁。
RedLock 的过程总结为以下五步:
  1. 客户端从 N 个 Redis 实例中请求锁,并设置相同的键、值和过期时间。
  2. 客户端计算从每个 Redis 实例成功获取锁的时间。如果客户端能在大多数(即 N/2 + 1 个)Redis 实例中获取到锁,并且获取锁的总时间小于设定的超时时间,那么客户端成功获得了锁。
  3. 如果客户端在大多数实例中获取了锁,客户端可以进行对共享资源的操作。
  4. 如果锁超时后仍未成功获得足够多的实例锁,或者在操作过程中锁过期,则客户端应放弃该锁,等待下一次重试。
  5. 操作完成后,客户端会向所有 Redis 实例发出解锁命令,以释放锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/421626.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浏览器中的JavaScript核心BOM(浏览器对象模型)重点掌握对象之History对象的属性与方法

History对象是用来把网页浏览历史用类似栈的方式进行表示。 这定义听起来非常的抽象&#xff0c;其实History对象的作用就跟浏览器的前进和后退很像&#xff0c;我们来用几幅图来理解一下。首先我们先回顾一下浏览器的返回上一个页面 和 跳转到下一个页面 这两个功能。 就类似…

JDBC使用

7.2 创建JDBC应用 7.2.1 创建JDBC应用程序的步骤 使用JDBC操作数据库中的数据包括6个基本操作步骤&#xff1a; &#xff08;1&#xff09;载入JDBC驱动程序&#xff1a; 首先要在应用程序中加载驱动程序driver&#xff0c;使用Class.forName()方法加载特定的驱动程序&#xf…

【题解单调队列优化dp】划分

划分 分析&#xff1a; 首先&#xff0c;我们目光着眼于部分分 我们尝试用 O ( n 3 ) O(n^3) O(n3)的朴素dp去解决这个问题 f [ i ] [ j ] 表示划分到第 i 个位置&#xff0c;且上一个位置是 j 的最小运行时间是多少 f[i][j]表示划分到第i个位置&#xff0c;且上一个位置是j的…

erlang学习: Mnesia Erlang数据库3

Mnesia数据库删除实现和事务处理 -module(test_mnesia). -include_lib("stdlib/include/qlc.hrl").-record(shop, {item, quantity, cost}). %% API -export([insert/3, select/0, select/1, delete/1, transaction/1,start/0, do_this_once/0]). start() ->mnes…

自然语言处理系列六十九》搜索引擎项目实战》搜索框架技术选型

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 自然语言处理系列六十九搜索引擎项目实战》搜索框架技术选型搜索…

(k8s)kubernetes 挂载 minio csi 的方式

一、安装Minio&#xff08;Minio分布式集群搭建部署_minio集群最少几台-CSDN博客&#xff09; 生成accessKeyID和secretAccessKey&#xff1a; 二、安装csi-s3插件(在k8s集群上) 首先我们把插件的yaml文件都下载下来&#xff0c;为了保证版本测试的一致性&#xff0c;我们下载…

828华为云征文|基于华为云Flexus云服务器X搭建jumpserver堡垒机软件

文章目录 ❀前言❀jumpserver堡垒机概述❀环境准备❀部署说明❀在线安装❀浏览器访问❀资产添加❀资产授权❀资产登录❀总结 ❀前言 近期华为云推出了最新的华为云Flexus云服务器X&#xff0c;这款云主机在算柔性算力做出了重大变革。华为云Flexus云服务器X基于擎天QingTian架…

QT设置闹钟超时播报

头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTimerEvent> #include<QTime> #include<QTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic…

一个基于Spring Boot 3、Vue 3 和 Element-Plus 的中后台管理框架,流畅、直观且功能强大

前言 当前市面上的中后台管理系统虽然种类繁多&#xff0c;但在实际使用中仍存在不少痛点&#xff0c;比如技术栈陈旧、性能低下、扩展性差等问题。开发者们常常需要花费大量的时间和精力去处理这些问题&#xff0c;而不是专注于业务逻辑本身。 那么&#xff0c;有没有一个框…

计算赎金信

给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1&#xff1a; 输入&#xff…

使用3DUNet训练自己的数据集(pytorch)— 医疗影像分割

代码:lee-zq/3DUNet-Pytorch: 3DUNet implemented with pytorch (github.com) 文章<cicek16miccai.pdf (uni-freiburg.de)3D U-Net: Learning Dense Volumetric Segmentation

HarmonyOS学习(十)——网络编程

文章目录 1、通过HTTP请求网络2、Web组件2.1、加载本地网页2.2、加载在线网页2.3、网页缩放2.4、文本缩放2.5、web组件事件以及状态说明2.6、处理页面导航 1、通过HTTP请求网络 官方API文档地址&#xff1a;HTTP数据请求-Network Kit数据传输能力-Network Kit&#xff08;网络…

Linux 下 C/C++ 程序编译的过程

目录 一、GCC 工具链二、编译过程1、预处理2、编译3、汇编4、链接 本文将介绍如何将 C/C 语言编写的程序转换成为处理器能够执行的二进制代码的过程&#xff0c;包括四个步骤&#xff1a;预处理&#xff08;Preprocessing&#xff09;编译&#xff08;Compilation&#xff09;汇…

Qt_自定义信号

目录 1、自定义信号的规定 2、创建自定义信号 3、带参数的信号与槽 4、一个信号连接多个槽 5、信号与槽的断开 结语 前言&#xff1a; 虽然Qt已经内置了大量的信号&#xff0c;并且这些信号能够满足大部分的开发场景&#xff0c;但是Qt仍然允许开发者自定义信号&#…

ARMxy嵌入式边缘计算控制器支持Linux OS应用于AIOT

人工智能与物联网&#xff08;AIoT&#xff09;的融合正深刻改变着各个行业。而在这一变革中&#xff0c;ARMxy 嵌入式控制器以其卓越的性能和对 Linux OS 的支持&#xff0c;成为了 AIoT 应用的关键推动力量。 一、ARMxy 嵌入式控制器的优势 强大的处理能力 ARMxy 嵌入式控制…

浮毛危害人体健康?希喂、安德迈、有哈宠物空气净化器吸毛测评

养宠之前了解清楚相关的知识&#xff0c;这既是对宠物负责&#xff0c;也是对我们自己负责。宠物最让铲屎官头疼的就是毛发问题&#xff0c;大量脱落的毛发会带来繁重的清理任务&#xff0c;同时飘在空中浮毛还是潜藏在身边的健康”杀手“。浮毛微小、质量轻&#xff0c;容易随…

opencv之图像轮廓(三)--凸包

文章目录 前言获取凸包凸缺陷几何学测试测试轮廓是否是凸形的点到轮廓的距离 形状场景算法比较轮廓轮廓的特征值宽高比ExtentSolidity等效直径&#xff08;Equivalent Diameter&#xff09;方向掩模和像素点使用Numpy函数获取轮廓像素点使用OpenCV函数获取轮廓点 最大值和最小值…

VR 尺寸美学主观评价-解决方案-现场体验研讨会报名

棣拓科技VR创新解决方案助力尺寸美学所见即所得! 诚邀各位行业专家莅临指导交流 请扫描海报二维码踊跃报名&#xff0c;谢谢 中国上海 2024.10.25 亮点介绍 1、通过精湛渲染技术&#xff0c;最真实展现设计效果&#xff0c;并通过VR设备一比一比例进行展现。 2、设置相关设…

ctfshow-PHP反序列化

web254 源码 <?php/* # -*- coding: utf-8 -*- # Author: h1xa # Date: 2020-12-02 17:44:47 # Last Modified by: h1xa # Last Modified time: 2020-12-02 19:29:02 # email: h1xactfer.com # link: https://ctfer.com //mytime 2023-12-4 0:22 */ error_reporting(0)…

谈谈PCIe VID、DID、SSID、SSVID背后的智慧

PCIe Vendor ID 想了半天还是觉得从“ID是什么”这个问题开始比较好。那么ID是什么&#xff1f;ID就是身份。那身份又是什么&#xff1f;身份就是一个合理存在&#xff0c;用于区分不同个体。为什么叫“合理存在”呢&#xff1f;如果国家不给你发身份证&#xff0c;你就是黑户…