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

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

因为在集群下,相当于多个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.hqwc.cn/news/295257.html

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

相关文章

ubuntu 18.04 共享屏幕

用于windows远程ubuntu 1. sudo apt install xrdp 2. 配置 sudo vim /etc/xrdp/startwm.sh 把最下面的test和exec两行注释掉&#xff0c;添加一行 gnome-session 3.安装dconf-editor : sudo apt-get install dconf-editor 关闭require encrytion org->gnome->desktop…

【数据结构入门精讲 | 第十六篇】并查集知识点及考研408、企业面试练习

上一篇中我们进行了散列表的相关练习&#xff0c;在这一篇中我们要学习的是并查集。 目录 概念伪代码选择题填空题编程题7-1 朋友圈R7-1 笛卡尔树R7-2 部落R7-3 秀恩爱分得快 在许多实际应用场景中&#xff0c;我们需要对元素进行分组&#xff0c;并且在这些分组中进行查询和修…

Ethercat“配置从站地址”报文分析

涉及的从站寄存器&#xff1a;Configured Station Address (0x0010:0x0010)。 使用场景举例&#xff1a; IgH启动后&#xff0c;通过“配置从站地址”报文将所有从站地址清零&#xff0c;然后通过APWR指令“配置从站地址”报文&#xff0c;设置各个从站的地址。 一、发送报文…

PyTorch加载数据以及Tensorboard的使用

一、PyTorch加载数据初认识 Dataset:提供一种方式去获取数据及其label 如何获取每一个数据及其label 总共有多少的数据 Dataloader:为后面的网络提供不同的数据形式 数据集 在编译器中导入Dataset from torch.utils.data import Dataset 可以在jupyter中查看Dataset官方文档&…

JoySSL诚招SSL证书代理

不久前&#xff0c;阿里云宣布了一个让人稍感唏嘘的消息——它们的一年期免费SSL证书服务将停步&#xff0c;转而提供三个月期限的证书。这一变化&#xff0c;无疑会使得网站开发的公司在维持用户信任和网站安全上多出心思。然而&#xff0c;免费的午餐并没有彻底消失&#xff…

基于springboot的日记本系统源码+数据库+安装使用说明

之前写的SpringBoot日记本系统备受好评&#xff0c;考虑到还是有很多小伙伴不会部署&#xff0c;所以这一篇文章就单独来讲一下部署步骤吧。 需要资源 idea&#xff08;破不破解都行&#xff09; MySQL&#xff08;最好5.7以上版本&#xff0c;最好8.0&#xff09; Navicat…

帆软报表 - 数据显示为列表,但是数据仍全部显示在同一行上?

文章目录 1 问题截图2 解决办法3 原因分析3.1 数据设置&#xff1a;全是列表 1 问题截图 想要的效果&#xff1a;每行显示一组数据得到的效果&#xff1a;数据全部显示在一行&#xff0c;以逗号隔开 2 解决办法 修改扩展方向。将 “不扩展” 修改为 “纵向” 3 原因分析 3.1…

【IDEA】try-catch自动生成中修改catch的内容

编辑器 --> 文件和代码模板 --> 代码 --> Catch Statement Body

mysql自增序列 关于mysql线程安全 独享内存 溢出 分析

1 MySQL锁概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁冲突也是影响数据库并发访问性能的一个重要因素。 相对其他数据库而言&#xff0c;MySQL的锁机制比较简单&#xff0c…

【SVN】centos7搭建svn--亲测能通

centos7.6搭建svn 1 知识小课堂1.1 CentOS1.2 SVN 2 搭建过程2.1 前期准备2.2 通过yum命令安装svnserve2.3 创建版本库目录2.4 创建svn版本库2.5 配置修改2.5 防火墙配置2.6 启动或关闭svn服务器2.6.1 进程守护2.6.2 检测svn端口3690是否已经监听&#xff1a;2.6.3 关闭SVN 2.7…

PyCharm控制台异常堆栈乱码问题解决

目录 1、问题描述2、问题原因3、问题解决 1、问题描述 PyCharm环境都已经配置成了UTF-8编码&#xff0c;控制台打印中文也不会出现乱码&#xff0c;但异常堆栈信息中如果有中文会出现中文乱码&#xff1a; 这种该怎么解决呢&#xff1f; 2、问题原因 未将PyCharm编码环境与项目…

初学链表(分析建立学生信息链表)

本题要求实现一个将输入的学生成绩组织成单向链表的简单函数。 #include <stdio.h> #include <stdlib.h> #include <string.h> struct stud_node { int num; char name[20]; int score; struct stud_node *next; }; struct stu…