分布式锁常见问题及其解决方案

一、为什么要使用分布式锁?

因为在集群下,相当于多个JVM,就相当于多个锁,集群之间锁是没有关联的,会照成锁失效从而导致线程安全问题

分布式锁可以分别通过MySQL、Redis、Zookeeper来进行实现

在这里插入图片描述

二、redis分布式锁的实现(基于setnx实现的分布式锁)

  • 创建ILock接口
package com.hmdp.utils;public interface ILock {/*** 尝试获取锁* @param timeoutSec* @return*/boolean tryLock(long timeoutSec);/*** 释放锁*/void unLock();
}
  • 创建SimpleRedisLock实现类
package com.hmdp.utils;import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock";@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId = Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unLock() {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + name);}
}

三、以上Redis分布式锁存在的一些问题

1、锁的误删问题

问题:线程1拿到锁产生了业务阻塞,这个时候锁已经超时释放导致线程2可以拿到锁,这时线程1业务执行完会将线程2的锁进行释放

在这里插入图片描述

解决方案:在释放锁的时候进行判断,是否是自己的锁

SimpleRedisLock实现类代码优化:

package com.hmdp.utils;import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock";private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId =ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unLock() {// 获取线程标示String threadId =ID_PREFIX + Thread.currentThread().getId();// 获取锁中的标示String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标示是否一致if (threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + name);}}
}
2、原子性问题

问题:线程1获取锁,执行业务结束,判断锁是否是自己的,判断成功,可能在Jvm垃圾回收的时候阻塞时间过长(这是在判断成功和释放锁之间执行的动作)导致锁超时释放,这个时候线程2可以成功获取到锁,当线程1阻塞结束,因为判断锁是否是自己的已经成功,所以线程1直接删除锁,从而导致误删了线程2的锁

在这里插入图片描述

解决思路:保证判断和释放的原子性

解决方法:

  • 创建lua文件并编写lua脚本(IDEA需要下载插件EmmyLua)

**加粗样式
**

  • lua脚本内容
-- 比较线程标示与锁中的标示是否一致
if(redis.call('get',KEYS[1]) == ARGY[1]) thenreturn redis.call('del',KEYS[1])
end
return 0
  • 调用lua脚本

SimpleRedisLock实现类代码修改

package com.hmdp.utils;import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Collections;
import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock";private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId =ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unLock() {// 调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());}
}
3、还存在一些问题
  • 不可重入:同一个线程无法多次获取同一把锁
  • 不可重试:获取锁只尝试一次就返回false,没有重试机制
  • 超时释放:锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患
  • 主从一致性:如果Redis提供了主从集群,主从同步存在延迟,当主宕机时,如果从并同步主中的锁数据,则会出现锁实现

以上自己设计Redis分布式锁是为了让大家了解分布式锁的基本原理,在企业中直接通过Redisson来实现就可以

四、Redisson

1.什么是Redisson?

Redisson是一个在Redis的基础上实现的Java驻内存数据网络。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现

2.使用方法

1.引入依赖

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

2.配置Redisson

package com.hmdp.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();// 配置自己的虚拟机地址和密码   配置密码是setPassword(),我虚拟机没有密码,所以省略config.useSingleServer().setAddress("redis://192.168.198.138:6379");// 创建RedissonClient对象return Redisson.create(config);}}

3.使用Redisson的分布式锁

	@Resourceprivate RedissonClient redissonClient;@Testvoid testRedisson() throws InterruptedException {// 获取锁(可重入),指定锁的名称RLock lock = redissonClient.getLock("anyLock");// 尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);// 判断释放获取成功if(isLock) {try {System.out.println("执行业务");} finally {// 释放锁lock.unlock();}}}
3.Redisson可重入锁原理

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.Redisson分布式锁原理

在这里插入图片描述

  • 可重入:利用hash结构记录线程id和重入次数

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

  • 超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间

  • 主从一致性:利用Redisson的multiLock。原理:多个独立的Redis节点,必须在所有节点都获取重入锁,才算获取锁成功

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

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

相关文章

华为发布全闪备份一体机旗舰新品,并宣布备份软件开源

[中国&#xff0c;上海&#xff0c;2023年12月20日]在20日举行的OceanProtect数据保护新品发布会上&#xff0c;华为发布全闪备份一体机旗舰新品&#xff0c;并宣布备份软件开源&#xff0c;以应对智慧金融、自动驾驶等场景对数据备份效率及数据安全方面的新诉求&#xff0c;为…

工业信息采集平台的五大核心优势

关键字&#xff1a;工业信息采集平台,蓝鹏数据采集系统,蓝鹏测控系统, 生产管控系统, 生产数据处理平台,MES系统数据采集, 蓝鹏数据采集平台通过实现和构成其他工业数据信息平台的一级设备进行通讯&#xff0c;从而完成平台之间的无缝对接。这里我们采用的最多的方式是和PLC进行…

神经网络:深度学习基础

1.反向传播算法&#xff08;BP&#xff09;的概念及简单推导 反向传播&#xff08;Backpropagation&#xff0c;BP&#xff09;算法是一种与最优化方法&#xff08;如梯度下降法&#xff09;结合使用的&#xff0c;用来训练人工神经网络的常见算法。BP算法对网络中所有权重计算…

Redis取最近10条记录

有时候我们有这样的需求&#xff0c;就是取最近10条数据展示&#xff0c;这些数据不需要存数据库&#xff0c;只用于暂时最近的10条&#xff0c;就没必要在用到Mysql类似的数据库&#xff0c;只需要用redis即可&#xff0c;这样既方便也快&#xff01; 具体取最近10条的方法&a…

Go 代码检查工具 golangci-lint

一、介绍 golangci-lint 是一个代码检查工具的集合&#xff0c;聚集了多种 Go 代码检查工具&#xff0c;如 golint、go vet 等。 优点&#xff1a; 运行速度快可以集成到 vscode、goland 等开发工具中包含了非常多种代码检查器可以集成到 CI 中这是包含的代码检查器列表&…

DBA-MySql面试问题及答案-上

文章目录 1.什么是数据库?2.如何查看某个操作的语法?3.MySql的存储引擎有哪些?4.常用的2种存储引擎&#xff1f;6.可以针对表设置引擎吗&#xff1f;如何设置&#xff1f;6.选择合适的存储引擎&#xff1f;7.选择合适的数据类型8.char & varchar9.Mysql字符集10.如何选择…

第九周算法题(哈希映射,二分,Floyd算法 (含详细讲解) )

第九周算法题 第一题 题目来源&#xff1a;33. 搜索旋转排序数组 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a;整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 <…

全网最全ChatGPT指令大全prompt

全网最全的ChatGPT大全提示词&#xff0c;大家可以进行下载。 AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作 PowerBI 商业智能 68集 数据库Mysql 8.0 54集 数据库Oracle 21C 142集 Office 2021实战应用 Python 数据分析实战&#xff0c; ETL Informatica 数据仓库案例实战 E…

【JAVA面试题】什么是引用传递?什么是值传递?

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 前言 博客的正文部分可以详细介绍Java中参数传递的机制&#xff0c;强调Java是按值传递的&#xff0c;并解释了基本数据类型和对象引用在这种传…

二级分销的魅力:无限裂变创造十八亿的流水

有这么一个团队&#xff0c;仅靠这一个二级分销&#xff0c;六个月就打造了十八亿的流水。听着是不是很恐怖&#xff1f;十八亿确实是一个很大的数字&#xff0c;那么这个团队是怎么做到的呢&#xff1f;我们接着往下看。 这是一个销售减脂产品的团队。不靠网店&#xff0c;不…

【JMeter入门】—— JMeter介绍

1、什么是JMeter Apache JMeter是Apache组织开发的基于Java的压力测试工具&#xff0c;用于对软件做压力测试。它最初被设计用于Web应用测试&#xff0c;但后来扩展到其他测试领域。 &#xff08;Apache JMeter是100%纯JAVA桌面应用程序&#xff09; Apache JMeter可以用于对静…

pycharm git 版本回退

参考 https://blog.csdn.net/qq_38175912/article/details/102860195 yoyoketang 悠悠课堂

电力系统风储联合一次调频MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 简介&#xff1a; 同一电力系统在不同风电渗透率下遭受同一负荷扰动时&#xff0c;其频率变化规律所示&#xff1a; &#xff08;1&#xff09;随着电力系统中风电渗透率的不断提高&#xff0c;风电零惯性响…

若依(ruoyi)管理系统标题和logo修改

1、网页上的logo 2、页面中的logo 进入ruoyi-ui --> src --> assets --> logo --> logo.png&#xff0c;把这个图片换成你自己的logo 3、网页标题 进入ruoyi-ui --> src --> layout --> components --> Sidebar --> Logo.vue&#xff0c;将里面的…

postman几种常见的请求方式

1、get请求直接拼URL形式 对于http接口&#xff0c;有get和post两种请求方式&#xff0c;当接口说明中未明确post中入参必须是json串时&#xff0c;均可用url方式请求 参数既可以写到URL中&#xff0c;也可写到参数列表中&#xff0c;都一样&#xff0c;请求时候都是拼URL 2&am…

伪装目标检测的算术不确定性建模

Modeling Aleatoric Uncertainty for Camouflaged Object Detection 伪装目标检测的算术不确定性建模背景贡献实验方法Camouflaged Object Detection Network&#xff08;伪装目标检测框架&#xff09;Online Confidence Estimation Network&#xff08;在线置信度估计网络&…

Stable Diffusion 基本原理

1 Diffusion Model的运作过程 输入一张和我们所需结果图尺寸一致的噪声图像&#xff0c;通过Denoise模块逐步减少noise&#xff0c;最终生成我们需要的效果图。 图中Denoise模块虽然是同一个&#xff0c;但是它会根据不同step的输入图像和代表noise严重程度的参数选择denoise的…

01背包详解,状态设计,滚动数组优化,通用问题求解

文章目录 0/1背包前言一、0/1背包的状态设计1、状态设计2、状态转移方程3、初始状态4、代码实现5、滚动数组优化二维优化为两个一维二维优化为一个一维&#xff0c;倒序递推 二、0/1背包的通用问题求最大值求最小值求方案数 0/1背包 前言 0/1包问题&#xff0c;作为动态规划问…

Python通过telnet批量管理配置华为交换机

名称&#xff1a;Python通过telnet批量管理配置华为交换机 测试工具&#xff1a;ensp, Visual Studio Code &#xff0c; Python3.8环境 时间&#xff1a;2023.12.23 个人备注&#xff1a;在NB 项目中&#xff0c;可以批量登录修改交换机配置&#xff0c;以此满足甲方爸爸的…

【Linux基础开发工具】gcc/g++使用make/Makefile

目录 前言 gcc/g的使用 1. 语言的发展 1.1 语言和编译器自举的过程 1.2 程序翻译的过程&#xff1a; 2. 动静态库的理解 Linux项目自动化构建工具-make/makefile 1. 快速上手使用 2. makefile/make执行顺序的理解 前言 了解完vim编辑器的使用&#xff0c;接下来就可以尝…