Redisson

news2024/12/22 17:24:41

文章目录

  • Redisson
    • 背景
    • 简介
    • 使用
      • 引入依赖
      • 配置类
      • 源代码

Redisson

背景

基于Redis(setnx)实现的分布式锁存在以下几个问题:

  1. 不可重入:同一个线程无法多次获取同一把锁

    image-20230522091500476

  2. 不可重试:获取锁只尝试一次就返回false,没有重试机制

  3. 超时释放:锁超时释放虽然可以避免死锁,但如果业务执行耗时较长,也会导致锁释放,存在一定安全隐患

  4. 主从一致性:如果Redis提供了主从集群,主从同步存在延迟,当主机宕机时,如果从机未同步主机中的锁数据,则会出现锁失效问题,存在一致性问题。

    image-20230522102032025

此时,如果自行编写代码去解决上述问题会显得业务逻辑十分复杂,所以,有没有别人已经封装好的工具,供我们直接使用呢?

此时,Redisson便呼之欲出了

简介

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

使用

既然知道Redisson可以解决一些分布式并发问题,那么该如何使用Redisson去解决生产开发中所遇到的问题呢?

这里提供两个链接进行学习:

  1. https://github.com/redisson/redisson/wiki/目录(Redisson中文文档)
  2. https://github.com/redisson/redisson(Redisson官方github)

引入依赖

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

经历了9年多的版本迭代,最新的版本已经到3.20.0

配置类

在使用前需要为其创建一个配置类(RedisConfig)

@Configuration
public class RedisConfig {
	@Bean
	public RedissonClient redissonClient(){
		// 配置类
		Config config = new Config();
		// 添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址
		config.useSingleServer().setAddress("redis://127.0.0.1:6379");
		// 添加客户端
		return Redisson.create(config);
	}
}

将其通过bean的方式注入进spring,后续使用时直接利用@Autowried或@Resource完成自动装配进行使用。

源代码

通过跟踪查看源代码,我们可以了解到Redisson底层是如何分别解决上面的几个问题的

解决不可重入问题:利用Hash结构记录线程id和重入次数

添加锁源码

image-20230522093106450

释放锁源码

image-20230522093716050

从上面的源码中可以得知,Redisson是通过一个Lua脚本来解决Redis中不可重入问题的,具体代码逻辑如下图

image-20230522092054739

解决不可重试问题:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制

@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    long time = unit.toMillis(waitTime);
    long current = System.currentTimeMillis();
    long threadId = Thread.currentThread().getId();
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        return true;
    }

    time -= System.currentTimeMillis() - current;
    if (time <= 0) {
        acquireFailed(waitTime, unit, threadId);
        return false;
    }

    current = System.currentTimeMillis();
    RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
        if (!subscribeFuture.cancel(false)) {
            subscribeFuture.onComplete((res, e) -> {
                if (e == null) {
                    unsubscribe(subscribeFuture, threadId);
                }
            });
        }
        acquireFailed(waitTime, unit, threadId);
        return false;
    }

    try {
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }

        while (true) {
            long currentTime = System.currentTimeMillis();
            ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
            // lock acquired
            if (ttl == null) {
                return true;
            }

            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }

            // waiting for message
            currentTime = System.currentTimeMillis();
            if (ttl >= 0 && ttl < time) {
                subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {
                subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
            }

            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        }
    } finally {
        unsubscribe(subscribeFuture, threadId);
    }
    //        return get(tryLockAsync(waitTime, leaseTime, unit));
}

通过源码跟踪,Redisson在获取锁的时候利用了消息订阅和信号量的机制(传递了等待重试时间),并不是无休止的盲等机制。

解决超时释放问题:利用watchDog(看门狗),每隔一段时间(releaseTime / 3),重置超时时间

源码

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    if (leaseTime != -1) {
        return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime,
                                                         commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
                                                         TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e != null) {
            return;
        }

        // lock acquired
        if (ttlRemaining == null) {
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

如果在调用trylock方法是传递的释放时间为-1(不传参),则Redisson会默认设置一个“看门狗时间”作为释放时间(默认为30秒),如果传递的释放时间不为-1,则直接返回true,方法结束。

image-20230522095705963

当锁的有效期结束时(每10秒执行一次),会执行Lua脚本刷新锁的有效期(一次刷新为30秒->“看门狗时间”),其中利用的是递归的思路,所以会无限循环。

image-20230522100508315

直到锁被释放,然后会执行取消锁刷新方法

image-20230522100738287

逻辑如下图(重试和超时问题):

image-20230522101242390

解决主从一致问题:使用多个独立的Redis节点,必须在所有节点都获取到重入锁,才算获取锁成功

这里利用的是Redisson中的MultiLock,将多个Redis节点放入一个集合中进行管理,这样做的缺陷就是提高了运维成本,实现的逻辑变的复杂。

image-20230522102323471

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云服务器+minio+PicGo+Typora搭建个人图床实现typora鼠标右击上传图片。

搭建图床 服务器配置docker安装minio安装配置docker安装minio配置minio picgo配置下载picgopicgo配置去插件市场下载&#xff0c;minio配置 typora配置安装破解设置上传服务 服务器配置 本人用的是阿里云的轻量型云服务器&#xff0c;centos7.6&#xff0c;没有服务器的可以自…

让文物“活”起来,火山引擎视频云三维重建技术揭秘

动手点关注 干货不迷路 中国历史悠久&#xff0c;文化底蕴深厚&#xff0c;文物数目众多&#xff0c;文物作为前人智慧的结晶&#xff0c;其文献价值不言而喻。古籍是记录中华文明的重要载体&#xff0c;也是流传至今的宝贵文化遗产&#xff0c;文物保护也是一项长期重要的基础…

OpenAI最新研究Let's verify step-by-step,过程胜于结果!

深度学习自然语言处理 原创作者&#xff1a;Winni OpenAI最新研究 <Let’s verify step-by-step> 于昨天发布&#xff0c;引起了广泛关注。这个想法非常简单&#xff0c;可以用一句话来概括&#xff1a; 对于复杂的逐步推理问题&#xff0c;我们在每个步骤都给予奖励&…

用GANs来做数据增强

适用于只有很少样本的情况。 即使是不完美的合成数据也可以提高分类器的性能。 生成对抗网络(Generative adversarial networks&#xff0c;简称GANs)由Ian Goodfellow于2014年推出&#xff0c;近年来成为机器学习研究中非常活跃的话题。GAN是一种无监督生成模型&#xff0c;它…

光栅尺磁栅尺编码器AB信号输入4倍频脉冲计数器,Modbus RTU模块

IBF153远程I/O模块&#xff0c;可以用来测量1路光栅尺磁栅尺编码器信号。 信号输入 1路光栅尺磁栅尺编码器信号输入&#xff0c;可接NPN和PNP信号&#xff0c;通过命令设置输入类型。 通讯协议 通讯接口&#xff1a; 1路标准的RS-485通讯接口。 通讯协议&#xff1a;支持两…

一篇文章搞定《Android嵌套滑动》

一篇文章搞定《Android嵌套滑动》 前言嵌套滑动冲突种类产生原因1、外部与内部滑动方向不一致2、外部与内部滑动方向一致3、多种情况下的嵌套&#xff08;电商首页&#xff09; 解决嵌套滑动的方法1、外部拦截法2、内部拦截法3、现有API框架 外部与内部滑动方向不一致1、ViewPa…

MySQL第三章、表的增删查改

目录 一、CRUD 二、新增&#xff08;Create&#xff09; 2.1单行数据 全列插入 ​2.2多行数据 指定列插入 ​编辑 三、查询&#xff08;Retrieve&#xff09; ​3.1全列查询 3.2指定列查询 3.3 查询字段为表达式 3.4 别名 3.5 去重&#xff1a;DISTINCT 3.6 排序…

docker-compose安装 rocketmq server、dashboard

目录 目录结构 nameserver安装 broker安装 控制台安装 测试效果 rocket分为3个服务&#xff1a;nameserver、broker、dashboard 这边我计划分开安装&#xff1a; 安装版本为4.5.0 目录结构 规划的结构 命令 mkdir -p /apps/rocketmq/namesrv/{config,data,logs} mkdir…

自动化测试-终章

自动化测试-终章 前沿 如果想做不需要人去点击使用程序做到真正的自动化测试思想,以下是我的思想,需要跟着我的思路来,我们做一个可以测试所有页面的增删改查功能是否好使 思想一 我使用的是Java 做自动化测试,我们现做一个简单的自动化 pom 需要引入 selenium-java 然后需…

Qt中的窗口类及其特点

目录 常用的窗口类 窗口的显示内嵌窗口 QWidget内嵌窗口演示 QWidget不内嵌窗口演示 QDialog类型的窗口特点 QMainWindows窗口的特点 总结 常用的窗口类 常用的窗口类有 3 个 在创建 Qt 窗口的时候&#xff0c;需要让自己的窗口类继承上述三个窗口类的其中一个QWidget 所有…

增量数据抽取技术

写在前面 本文隶属于专栏《大数据从 0 到 1》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见《大数据从 0 到 1》 正文 在数据仓库中要实现增量抽取&#x…

STL-string-1

stoi int stoi (const string& str, size_t* idx 0, int base 10);int stoi (const wstring& str, size_t* idx 0, int base 10); Convert string to integer 解析str&#xff0c;将其内容解释为指定基数的整数&#xff0c;该整数作为int值返回。 如果idx不是空…

QLoRa:在消费级GPU上微调大型语言模型

大多数大型语言模型(LLM)都无法在消费者硬件上进行微调。例如&#xff0c;650亿个参数模型需要超过780 Gb的GPU内存。这相当于10个A100 80gb的gpu。就算我们使用云服务器&#xff0c;花费的开销也不是所有人都能够承担的。 而QLoRa (Dettmers et al.&#xff0c; 2023)&#x…

公司裁员不给赔偿怎么办?

阅读本文大概需要 1.61 分钟。 最近在星球回答球友问题的时候&#xff0c;发现不少人都提到裁员这个话题。 有球友说他们公司在裁员&#xff0c;但不想给赔偿。 领导给他的方案是把年假调休休了&#xff0c;然后再给三周找工作时间&#xff0c;这三周不用打卡&#xff0c;三周后…

茅塞顿开的C#代码——通用型科学计算器

计算器是经常遇到的编程作业。 一般都是实现加、减、乘、除四则运算的普通计算器。 这里介绍用几十行C#代码实现的复杂的《科学计算器》&#xff0c;可以计算各种函数。 不知道其他语言实现同样的功能需要编写多少行代码&#xff1f;20000行&#xff1f; using System; usin…

SpringBoot接口如何正确地接收时间参数

唠嗑部分 在做Java开发时&#xff0c;肯定会碰到传递时间参数的情况吧&#xff0c;比如用户的出生日期、活动的开始&#xff0c;结束日期等等&#xff0c;这些参数往往是由前端传递过来的&#xff0c;那么在SpringBoot项目中&#xff0c;该如何正确的接收日期参数呢&#xff0…

如果不小心上了电信黑名单,应该怎么妥善处理呢?

有些小伙伴们在处理不用的手机卡时&#xff0c;可能会粗心大意&#xff0c;认为不用了就用不了呗&#xff0c;存在欠费停机的情况下也没有及时的去补交欠费&#xff0c;然后销户&#xff0c;导致了自己不小心上了电信黑名单&#xff0c;那遇到这种情况&#xff0c;应该怎么妥善…

论文解读 | 利用图形卷积核在距离图像中实现高效的3D目标检测

原创 | 文 BFT机器人 01 摘要 该论文提出了一种基于范围图像的高效3D物体检测方法&#xff0c;通过利用图卷积核来提取每个像素周围的局部几何信息。 作者设计了一种新颖的2D卷积网络架构&#xff0c;并提出了四种替代内积核心的卷积核&#xff0c;以注入所需的三维信息。该方法…

GPT最常用的应用场景有哪些?

生成式预训练转换器&#xff08;GPT&#xff09;是一种深度学习模型&#xff0c;它能够根据给定的提示生成类似人类的文本&#xff0c;彻底改变了自然语言处理&#xff08;NLP&#xff09;领域。 聊天机器人和虚拟助手 GPT最受欢迎的应用程序之一是开发聊天机器人和虚拟助手。凭…

【Python 自然语言处理(NLP)】零基础也能轻松掌握的学习路线与参考资料

Python 自然语言处理&#xff08;NLP&#xff09;是目前人工智能&#xff08;AI&#xff09;发展中的重要领域。随着科技的不断进步&#xff0c;NLP已经被应用于文本自动摘要、机器翻译、语音识别、情感分析、问答系统等各项实际任务中。 要学习 Python 自然语言处理&#xff…