redisson分布式锁学习

什么是分布式锁?

当有多个线程并发访问同一共享数据时,如果多个线程同时都去修改这个共享数据,且修改操作不是原子操作,就很有可能出现线程安全问题,而产生线程安全问题的根本原因是缺乏对共享数据访问的同步和互斥
为了解决这个问题,通常我们的做法是通过加锁来解决该问题,比如ReentrantLock or Synchronized ,但是在分布式系统中,存在多台服务器与客户端,这些节点之间都可能访问相同的共享数据。而Java中的内置锁机制如synchronized和ReentrantLock都是JVM内部的,无法对其他服务器产生效果。因此无法解决真正的分布式多线程访问安全问题。
为了实现分布式环境下的线程安全,需要引入外部的协调组件,实现一个分布式锁常见的分布式锁组件有Redis、Zookeeper等,当然也可以基于数据库的悲观锁或CAS操作实现分布式锁

传统redis工具类实现分布式锁

通过redis工具类基于实现分布式锁。

/**
* 加锁
*
* @param key               - key名称
* @param expireMillisecond - 锁成功后的有效期,毫秒
* @return return null or empty string is lock failed Otherwise return uuid value of lock-key
*/
public String lock(String key, long expireMillisecond) {
    Preconditions.checkArgument(StringUtils.isNotBlank(key));
    Preconditions.checkArgument(expireMillisecond > 0L);

    String lockKey = LOCK_KEY_PREFIX + key;
    String lockValue = UUID.randomUUID().toString();
    boolean keySet = redisTemplateWarpper.vSetIfAbsent(lockKey, lockValue, expireMillisecond);
    if (keySet) {   //锁成功
        return lockValue;
    }
    return null;
}

/**
* 解锁
*
* @param key
*/
public void unlock(String key, String value) {
    if (StringUtils.isBlank(value)) {
        return;
    }

    String lockKey = LOCK_KEY_PREFIX + key;
    String lockValueRedis = redisTemplateWarpper.vGet(lockKey);
    if (StringUtils.equals(lockValueRedis, value)) {
        redisTemplateWarpper.kDelete(lockKey);
    }
}

public Boolean vSetIfAbsent(String key, String value, long timeoutMillisecond) {
        RedisSerializer<String> stringSerializer = stringRedisTemplate.getStringSerializer();
        return stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                Object obj = connection.execute("set",
                        stringSerializer.serialize(checkKey(key)),
                        stringSerializer.serialize(value),
                        stringSerializer.serialize("NX"),
                        stringSerializer.serialize("PX"),
                        stringSerializer.serialize(String.valueOf(timeoutMillisecond)));
                return obj != null;
            }
        });
    }

传统的工具类实现redis分布式锁实现方式简单,虽然可以提供分布式锁的效果,但实际效果其实并不理想,因为在特殊情况下存在种种问题。

死锁问题

业务阻塞死锁: 某个客户端在执行一个长时间的阻塞操作,例如使用 BLPOP 或 BRPOP 命令来阻塞地等待列表中的元素。如果该操作长时间未完成或未释放连接,其他客户端可能无法获取连接,导致死锁。

**客户端宕机死锁:**客户端在解锁前崩溃下线,未设置过期时间,导致锁无法释放。

锁时间不合理问题

在锁资源的时候我们可以给锁设置过期时间,但锁的时间过长或过短都会出现问题。
例如:锁时间过长,其他线程一直无法获取锁资源,从而阻塞业务,不能够正常进行。
锁时间过短,锁资源中的任务还未执行完毕,其他线程已经可以获取到锁资源,从而操作共享数据,出现线程安全问题。

功能单一问题

仅能提供基础的加锁与解锁的功能,高级功能需要自己实现(例如读写锁、公平锁等等)

redisson分布式锁

什么是redisson分布式锁?

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) **

如何解决传统redis工具类问题?

死锁问题

  • 解决业务阻塞死锁:通过tryLock(long waitTime, TimeUnit unit) 尝试获取锁,其他线程一定时间未获取不到锁就返回false,停止获取锁。
  • 解决客户端宕机死锁:不传过期时间时,默认会设置30秒的过期时间,节点没宕机的情况下如果任务未执行完会持续进行锁续期,节点宕机后则不会再进行续期,到了过期时间后就会删掉key

锁时间不合理问题

通过看门狗机制自动进行锁续期,不进行人工干预。

功能单一问题

不仅提供了一系列的分布式的Java常用对象还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法

redisson加锁流程

在这里插入图片描述

看门狗机制


什么是看门狗机制?

Redisson提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。
默认情况下,看门狗的续期时间是30s,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson 还提供了可以指定leaseTime参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。

什么时候会启动看门狗机制?

方法描述Watch Dog 延期机制
lock()拿锁失败时会不停的重试,直到成功获取锁有,续锁时间默认为30秒,每隔30/3=10秒续锁
tryLock(10, TimeUnit.SECONDS)尝试在10秒内获取锁,获取成功返回true,失败返回false有,续锁时间默认为30秒
lock(10, TimeUnit.SECONDS)
(void lock(long leaseTime, TimeUnit unit);)
拿锁失败时会不停的重试,10秒后自动释放锁无,10秒后自动释放锁
tryLock(100, 10, TimeUnit.SECONDS)
(boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)
尝试在100秒内获取锁,每次重试间隔为10秒,获取成功返回true,失败返回false无,10秒后自动释放锁

如果你想让Redisson启动看门狗机制,你就不能自己在获取锁的时候,定义超时释放锁的时间
无论是通过lock() **还是通过tryLock获取锁,只要在参数中,不传入releastime,就会开启看门狗机制。
**就是这两个方法不要用:
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException
void lock(long leaseTime, TimeUnit unit);
因为它俩都传release,但是,你传的leaseTime是-1,也是会开启看门狗机制的

看门狗机制源码分析

 // 直接使用lock无参数方法
public void lock() {
    try {
        lock(-1, null, false);
    } catch (InterruptedException e) {
        throw new IllegalStateException();
    }
}

// 进入该方法 其中leaseTime = -1
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        return;
    }

   //...
}

// 进入 tryAcquire(-1, leaseTime, unit, threadId)
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

// 进入 tryAcquireAsync
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Long> ttlRemainingFuture;
    	//  leaseTime = -1
        if (leaseTime > 0) {
            ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
        CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);
        ttlRemainingFuture = new CompletableFutureWrapper<>(s);

        CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
            // lock acquired leaseTime = -1
            if (ttlRemaining == null) {
                if (leaseTime > 0) {
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
            	// 看门狗续期
                    scheduleExpirationRenewal(threadId);
                }
            }
            return ttlRemaining;
        });
        return new CompletableFutureWrapper<>(f);
    }

 protected void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
        ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            try {
                renewExpiration();
            } finally {
                if (Thread.currentThread().isInterrupted()) {
                    cancelExpirationRenewal(threadId);
                }
            }
        }
    }


   private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        
        Timeout task = commandExecutor.getServiceManager().newTimeout(new TimerTask() {
            @Override
            public 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;
                }
                
                CompletionStage<Boolean> future = renewExpirationAsync(threadId);
                future.whenComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock {} expiration", getRawName(), e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
                    
                    if (res) {
                        // reschedule itself
                        renewExpiration();
                    } else {
                        cancelExpirationRenewal(null);
                    }
                });
            }
        // 默认锁租期internalLockLeaseTime = 30s  默认续期时间为锁租期/3 = 10s
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

P2P网络NAT穿透原理(打洞方案)

1.关于NAT NAT技术&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是一种把内部网络&#xff08;简称为内网&#xff09;私有IP地址转换为外部网络&#xff08;简称为外网&#xff09;公共IP地址的技术&#xff0c;它使得一定范围内的多台主机只…

SpringBoot超级详解

1.父工程的父工程 在父工程的父工程中的核心依赖&#xff0c;专门用来版本管理的 版本管理。 2.父工程 资源过滤问题&#xff0c;都帮解决了&#xff0c;什么配置文件&#xff0c;都已经配置好了&#xff0c;资源过滤问题是帮助&#xff0c;过滤解决让静态资源文件能够过滤到…

别再分库分表了,来试试它吧

什么是NewSQL传统SQL的问题 升级服务器硬件数据分片NoSQL 的问题 优点缺点NewSQL 特性NewSQL 的主要特性三种SQL的对比TiDB怎么来的TiDB社区版和企业版TIDB核心特性 水平弹性扩展分布式事务支持金融级高可用实时 HTAP云原生的分布式数据库高度兼容 MySQLOLTP&OLAP&#xff…

openssl/bn.h: No such file or directory

报错截图 解决方法 ubuntu apt install libssl-dev -y centos yum install openssl-devel -y

第六章 支持向量机

文章目录 支持向量机间隔和支持向量对偶问题问题推导SMO 核函数实验 支持向量机 ⽀持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09; 优点&#xff1a;泛化错误率低&#xff0c;计算开销不⼤&#xff0c;结果易解释。缺点&#xff1a;对参数调节和核…

Python 教程之标准库概览

概要 Python 标准库非常庞大&#xff0c;所提供的组件涉及范围十分广泛&#xff0c;使用标准库我们可以让您轻松地完成各种任务。 以下是一些 Python3 标准库中的模块&#xff1a; 「os 模块」 os 模块提供了许多与操作系统交互的函数&#xff0c;例如创建、移动和删除文件和…

CLIP-GCD: Simple Language Guided Generalized Category Discovery(论文翻译)

CLIP-GCD: Simple Language Guided Generalized Category Discovery 摘要1 介绍2 相关工作2.1 NCD2.2 无监督聚类2.3 自监督和多模态预训练 3 方法3.1 GCD 问题设置3.2 我们的方法3.2.1 使用CLIP 在GCD 4 实验4.1 模型架构细节4.2 数据集和评估4.3 和最先进水平比较4.4 分析4.5…

Linux下 Docker容器引擎基础(1)

简述&#xff1a; Docker的容器技术可以在一台主机上轻松为任何应用创建一个轻量级的、可移植的、自给自足的容器。通过这种容器打包应用程序&#xff0c;意味着简化了重新部署、调试这些琐碎的重复工作&#xff0c;极大的提高了工作效率。例如&#xff1a;项目从腾讯云迁移阿…

尚硅谷大数据项目《在线教育之采集系统》笔记002

视频地址&#xff1a;尚硅谷大数据项目《在线教育之采集系统》_哔哩哔哩_bilibili 目录 P032 P033 P033 P034 P035 P036 P032 P033 # 1、定义组件&#xff0c;为各组件命名 a1.sources r1 a1.channels c1 a1.sinks - k1# 2、配置sources&#xff0c;描述source a1.sour…

ALLEGRO之Route菜单

本文主要介绍了ALLEGRO的Route菜单。 &#xff08;1&#xff09;Connect&#xff1a;走线&#xff1b; &#xff08;2&#xff09;Slide&#xff1a;推挤&#xff1b; &#xff08;3&#xff09;Timing Vision&#xff1a;等长设计时使用&#xff1f;暂不清楚&#xff1b; &…

oracle,获取每日24*60,所有分钟数

前言&#xff1a; 为规范用户的时间录入&#xff0c;因此我们采用下拉的方式&#xff0c;让用户选择需要的时间&#xff0c;因此我们需要将一天24小时的时间拆分为类似00:00,00:01...23:00,23:01,23:59。因此我们需要生成24*601440行的下拉复选值。具体效果如下图所示。 思路 1…

C语言字串函数、内存函数介绍以及模拟实现

目录 前言 本期内容介绍&#xff1a; 一、字符串函数 strlen介绍 strlen 模拟实现&#xff08;三种方式&#xff09; 方法一&#xff1a;计数器法 方法二&#xff1a;递归法&#xff08;不创建临时变量法&#xff09; 方法三&#xff1a;指针-指针 strcpy介绍 strcpy模…

SSIS对SQL Server向Mysql数据转发表数据 (完结)

1、对于根据主键进行更新和插入新的数据&#xff0c;根据前面的文章&#xff0c;对于组件已经很熟悉了&#xff0c;我们直接加入一个 查找 组件 &#xff0c;如下所示 2、右键点击"查找"&#xff0c;然后“编辑” &#xff0c;选择“连接”,选中我们的目标连接器&…

Vue2 第七节 Vue监测数据更新原理

&#xff08;1&#xff09;Vue会监视data中所有层次的数据 &#xff08;2&#xff09;如何监测对象中的数据 通过setter实现监视&#xff0c;且要在new Vue时传入要监测的数据对象中后追加的属性&#xff0c;Vue默认不做响应式处理如果要给后添加的属性做响应式&#xff0c;使…

Docker私有仓库

Docker私有仓库 Docker官方的Docker hub&#xff08;https://hub.docker.com&#xff09;是一个用于管理公共镜像的仓库&#xff0c;我们可以从上面拉取镜像到本地&#xff0c;也可以把我们自己的镜像推送上去。但是&#xff0c;有时候我们的服务器无法访问互联网&#xff0c;…

初阶数据结构——二叉树题目

文章目录 一、单值二叉树二、检查两颗树是否相同三、另一棵树的子树四、二叉树的前序遍历五、对称二叉树 一、单值二叉树 单值二叉树 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff…

Docker学习笔记,包含docker安装、常用命令、dockerfile、docker-compose等等

&#x1f600;&#x1f600;&#x1f600;创作不易&#xff0c;各位看官点赞收藏. 文章目录 Docker 学习笔记1、容器2、Docker 安装3、Docker 常用命令4、Docker 镜像5、自定义镜像5.1、镜像推送到阿里云5.2、镜像私有库 6、数据卷7、Docker 软件安装8、Docker File8.1、常见保…

基于python+Xception算法模型实现一个图像分类识别系统

一、目录 Xception介绍数据集处理模型训练模型评估项目扩展 二、Xception介绍 在计算机视觉领域&#xff0c;图像识别是一个非常重要的任务&#xff0c;其应用涵盖了人脸识别、物体检测、场景理解等众多领域。随着深度学习技术的发展&#xff0c;深度卷积神经网络&#xff0…

哈工大计算机网络课程网络安全基本原理之:身份认证

哈工大计算机网络课程网络安全基本原理之&#xff1a;身份认证 在日常生活中&#xff0c;在很多场景下我们都需要对当前身份做认证&#xff0c;比如使用密码、人脸识别、指纹识别等&#xff0c;这些都是身份认证的常用方式。本节介绍的身份认证&#xff0c;是在计算机网络安全…

android 如何分析应用的内存(十三)——perfetto

android 如何分析应用的内存&#xff08;十三&#xff09; 本篇文章是native内存的最后一篇文章——perfetto perfetto简介 从2018年始&#xff0c;android开发者峰会正式推出perfetto工具。从此perfetto成为安卓最重要的工具之一。在2018年以前&#xff0c;android使用syst…