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>() {@Overridepublic 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 acquiredif (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 = -1if (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 = -1if (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() {@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;}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 itselfrenewExpiration();} else {cancelExpirationRenewal(null);}});}// 默认锁租期internalLockLeaseTime = 30s  默认续期时间为锁租期/3 = 10s}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}

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

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

相关文章

【Golang 接口自动化07】struct转map的三种方式

目录 背景 struct转map 使用json模块 使用reflect模块 使用第三方库 测试 总结 资料获取方法 背景 我们在前面介绍过怎么使用net/http发送json或者map数据&#xff0c;那么它能不能直接发送结构体数据呢&#xff1f;我们今天一起来学习结构体struct转map的三种方法&am…

PHM的设备故障模型如何构建?

预测性维护与健康管理&#xff08;Prognostics Health Management&#xff0c;PHM&#xff09;是现代工业中的一个关键概念&#xff0c;它旨在通过使用数据和先进的分析技术&#xff0c;实现设备故障的早期预测和预防&#xff0c;从而最大限度地提高设备的可用性和可靠性。而在…

如何运行疑难解答程序来查找和修复Windows 10中的常见问题

如果Windows 10中出现问题&#xff0c;运行疑难解答可能会有所帮助。疑难解答人员可以为你找到并解决许多常见问题。 一、在控制面板中运行疑难解答 1、打开控制面板&#xff08;图标视图&#xff09;&#xff0c;然后单击“疑难解答”图标。 2、单击“疑难解答”中左上角的…

大牛练成记:用JavaScript徒手写出一个日期选择插件

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;100个JavaScript的小应用。 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收…

【C语言】初识指针

【C语言】初识指针 一、指针是什么&#xff1f;二、指针和指针类型1. 指针-整数2. 指针的解引用三、野指针1.野指针成因2 .如何规避野指针四、指针运算五、二级指针七、指针数组 &#x1f388;个人主页&#xff1a;库库的里昂&#x1f390;CSDN新晋作者&#x1f389;欢迎 &…

ansible安装及rhel8仓库配置

目录 一、本地仓库 问题&#xff1a; 解决&#xff1a; 1.创建一个仓库&#xff1a; 内容&#xff1a; 2.挂载&#xff1a; 挂载&#xff1a; 测试&#xff1a; 3.或者直接使用阿里云的源 二.配置ansible仓库 1.下载&#xff1a; 2.检查 一、本地仓库 问题&#xff1a; 当…

vue3+uniapp自定义tabbar

首先把tabbar中的元素写在一个list中用v-for进行渲染 用一个interface进行定义接口&#xff0c;这样别人在review你的代码就可以清晰知道你的tabbar包含什么元素。 利用typescript特性进行类型定义&#xff0c;可以省去很多麻烦 import { reactive } from "vue" imp…

docker快速入门

文章目录 简介&#xff1a;组成&#xff1a;安装&#xff1a;运行&#xff1a;原理&#xff1a;常用命令&#xff1a;1.帮助启动类命令2.镜像命令3.容器命令4.命令交互图5.将镜像打包发布到阿里云1.将本地容器制作为镜像2.登录阿里云3.创建个人实例4.创建镜像仓库5.将镜像推送到…

马上解锁 StarRocks 存算分离,降本增效无需等!

StarRocks 于 4 月底正式发布了 3.0 版本&#xff0c;该里程碑版本带来了大家期盼已久的新特性--存算分离。此新功能一推出&#xff0c;立即受到社区热情追捧&#xff0c;用户纷纷开始在自己的业务中评估和测试存算分离效果。从芒果TV、聚水潭、网易邮箱、浪潮、天道金科等数十…

桥接模式——处理多维度变化

1、简介 1.1、概述 桥接模式是一种很实用的结构型设计模式。如果软件系统中某个类存在两个独立变化的维度&#xff0c;通过该模式可以将这两个维度分离出来&#xff0c;使两者可以独立扩展&#xff0c;让系统更加符合单一职责原则。与多层继承方案不同&#xff0c;它将两个独…

游戏APP开发:创新设计的秘诀

在游戏 APP开发中&#xff0c;创新设计是游戏开发公司的一大追求&#xff0c;为了可以为用户带来更好的游戏体验&#xff0c;这就需要对游戏 APP开发进行创新设计。那么&#xff0c;游戏 APP开发中的创新设计是什么呢&#xff1f;接下来&#xff0c;我们就一起来看看吧。 想要…

mongodb docker 及常用命令

MongoDB属于非关系型数据库&#xff0c;它是由C编写的分布式文档数据库。内部使用类似于Json的bson二进制格式。 中文手册 https://www.w3cschool.cn/mongodb/ 安装 https://www.mongodb.com/try/download/community 二进制安装可见另一篇&#xff1a; centos7 mongodb 4.0.28…

SSL原理详解

SSL协议结构&#xff1a; SSL协议分为两层&#xff0c;下层为SSL记录协议&#xff0c;上层为SSL握手协议、SSL密码变化协议和SSL警告协议。 1.下层为SSL记录协议&#xff0c;主要作用是为高层协议提供基本的安全服务 建立在可靠的传输之上&#xff0c;负责对上层的数据进行分块…

Vue3+Vite+TypeScript常用项目模块详解

目录 1.Vue3ViteTypeScript 概述 1.1 vue3 1.1.1 Vue3 概述 1.1.2 vue3的现状与发展趋势 1.2 Vite 1.2.1 现实问题 1.2 搭建vite项目 1.3 TypeScript 1.3.1 TypeScript 定义 1.3.2 TypeScript 基本数据类型 1.3.3 TypeScript语法简单介绍 2. 项目配置简单概述 2.…

小白到运维工程师自学之路 第六十三集 (dockerfile安装sshd、httpd、nginx)

一、概述 Dockerfile的指令根据作用可以分为两种&#xff0c;构建指令和设置指令。构建指令用于构建image&#xff0c;其指定的操作不会在运行image的容器上执行&#xff1b;设置指令用于设置image的属性&#xff0c;其指定的操作将在运行image的容器中执行。 1、FROM 镜像:T…

em3288 linux_4.19 sd卡调试

默认配置&#xff0c;根据实际配置即可。

Maven-学习笔记

文章目录 1. Maven简介2.Maven安装和基础配置3.Maven基本使用4.Maven坐标介绍 1. Maven简介 概念 Maven是专门用于管理和构建Java项目的工具 主要功能有: 提供了一套标准化的项目结构提供了一套标准化的构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;…

语音识别 — 特征提取 MFCC 和 PLP

一、说明 语音识别是一种技术&#xff0c;通过计算机和软件系统&#xff0c;将人们的口头语言转换为计算机可读的文本或命令。它使用语音信号处理算法来识别和理解人类语言&#xff0c;并将其转换为计算机可处理的格式。语音识别技术被广泛应用于许多领域&#xff0c;如语音助手…

什么是图像特征?如何让计算机理解图像特征?

图像的特征 大多数人都玩过拼图游戏。首先拿到完整图像的碎片&#xff0c;然后把这些碎片以正确的方式排列起来从而重建这幅图像。如果把拼图游戏的原理写成计算机程序&#xff0c;那计算机就也会玩拼图游戏了。 在拼图时&#xff0c;我们要寻找一些唯一的特征&#xff0c;这…

企业AD域管理:ADManager Plus助您轻松掌控全局

在现代企业中&#xff0c;Active Directory&#xff08;AD&#xff09;域是一个至关重要的组成部分。它作为一种身份验证和授权机制&#xff0c;管理着企业网络中的用户、计算机、组和其他资源。然而&#xff0c;随着企业规模和复杂性的不断增长&#xff0c;AD域的管理变得越来…