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,一经查实,立即删除!

相关文章

Rust: error: failed to run custom build command for `openssl-sys v0.9.71`

error: failed to run custom build command for openssl-sys v0.9.71 解决 windows &#xff1a; openssl 不要选Light版 设置环境变量 cmd: set OPENSSL_DIR“C:\Program Files\OpenSSL-Win64” OPENSSL_DIR&#xff1a;C:\Program Files\OpenSSL-Win64 linux&#xff1a…

java基础四-String/StringBuffer/StringBuilder区别

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象&#xff0c;每次操作都会生成新的 String 对象&#xff0c;然后将指针指向新的 String 对象&#xff0c;而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作&#xff0c;所以在经常…

【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;从而最大限度地提高设备的可用性和可靠性。而在…

MySQL(一)

mysql简介 1、什么是数据库 &#xff1f; 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库&#xff0c;它产生于距今六十多年前&#xff0c;随着信息技术和市场的发展&#xff0c;特别是二十世纪九十年代以后&#xff0c;数据管理不再仅仅…

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

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

行为型模式之解释器模式

解释器模式&#xff08;Interpreter Pattern&#xff09; 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为设计模式&#xff0c;它用于对语言的文法进行解释和解析&#xff0c;以实现特定的操作。 在解释器模式中&#xff0c;存在以下几个角色&#xff1a; 抽…

如何动态改变background-image

动态改变background-image 最近在开发uni-app小程序时尝试使用栅格布局利用u-grid-item循环遍历元素节点&#xff0c;其中每个元素节点对应的背景图片都不相同&#xff0c;于是就遇到了需要动态改变元素background-image的操作。 错误写法 如果单纯的使用常规的eslint写法。…

uniapp 长时间不操作,自动退出登录页

store 下的inex.js文件 import Vue from vue import Vuex from vuexVue.use(Vuex)const store new Vuex.Store({state: {// 记录最后一次点击时间的元素lastTime: new Date().getTime(),},mutations: {//点击事件调用&#xff0c;刷新最后一次点击时间lastTimeUpdata: (state…

大牛练成记:用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; 当…

R并行计算-parallel例子1

前言&#xff1a; 通常&#xff0c;如果进程运行时间超过3分钟&#xff0c;则会考虑使用并行处理。 这听起来可能很复杂&#xff0c;但是并行计算很简单。 当你有一个重复的任务&#xff0c;它占用了你太多宝贵的时间&#xff0c;为什么不使用并行计算来节省时间呢&#xff…

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、聚水潭、网易邮箱、浪潮、天道金科等数十…

【VScode】Remote-SSH XHR failed无法访问远程服务器

问题概述 当使用VScode连接远程服务器时&#xff0c;往往需要使用Remote-SSH这个插件。而该插件有一个小bug&#xff0c;当远程服务器网络不佳时容易出现。 在控制台会出现下述语句&#xff1a; Resolver error: Error: XHR failed at y.onerror (vscode-file://vscode-app/…

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

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…