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

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

因为在集群下,相当于多个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";

    @Override
    public 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);
    }
                                    
    @Override
    public 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) + "-";

    @Override
    public 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);
    }

    @Override
    public 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]) then
    return 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);
    }


    @Override
    public 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);
    }

    @Override
    public 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 {

    @Bean
    public RedissonClient redissonClient(){
        // 配置
        Config config = new Config();
        // 配置自己的虚拟机地址和密码   配置密码是setPassword(),我虚拟机没有密码,所以省略
        config.useSingleServer().setAddress("redis://192.168.198.138:6379");
        // 创建RedissonClient对象
        return Redisson.create(config);
    }

}

3.使用Redisson的分布式锁

	@Resource
    private RedissonClient redissonClient;
    
    
   	@Test
   	void 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节点,必须在所有节点都获取重入锁,才算获取锁成功

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

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

相关文章

SpringBoot 3 集成Hive 3

前提条件: 运行环境&#xff1a;Hadoop 3.* Hive 3.* MySQL 8 &#xff0c;如果还未安装相关环境&#xff0c;请参考&#xff1a;Hive 一文读懂 Centos7 安装Hadoop3 单机版本&#xff08;伪分布式版本&#xff09; SpringBoot 2 集成Hive 3 pom.xml <?xml ver…

力扣经典面试题——搜索二维矩阵(两次二分搜索)

https://leetcode.cn/problems/search-a-2d-matrix/description/?envTypestudy-plan-v2&envIdtop-100-liked 思路&#xff1a;先按行二分&#xff0c;再按列进行二分。即先找到对应的行&#xff0c;再找对应的列。 对于这种判断是否存在某个数&#xff0c;记得while(left…

PHP案例代码:PHP如何提供下载功能?

对Web开发人员来说,“下载”功能是一个非常常见的需求。在网站中提供文件下载,通常用于提供用户手册、软件升级、音乐、视频等各种资源文件。本教程将向您介绍如何实现一个PHP下载功能,同时告诉浏览器文件名称、文件大小、文件类型,并统计下载次数。 首先,我们需要了解一些…

Verilog RAM/ROM的数据初始化

文章目录 一、初始化方式二、测试 FPGA设计中RAM和ROM作为存储器用来存储可变或不可变类型的数据。 ROM初始化一般是加载固定数据&#xff0c;RAM声明时默认为不定态数据&#xff0c;初始化时可以让数据为全1或者全0。 一、初始化方式 复位时按地址写入初值always (posedge cl…

nodejs+vue+ElementUi房屋房产销售预约看房系统bqv00

完成房产销售系统&#xff0c;对房源的信息、用户信息及各种资料进行收集和科学的管理&#xff0c;该系统的功能基本可以满足当前市面上的小型房产企业对于房产销售的基本要求&#xff0c;收集各个地区的房源信息并进行分类管理&#xff0c;用户通过注册账号登录网站查询房源信…

MySQL内外连接

目录 内连接外连接左外连接右外连接 内连接 给出一张员工表和一张部门表&#xff0c;员工表数据如下&#xff1a; 部门表信息如下&#xff1a; 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选&#xff0c;我们前面学习的查询都是内连接&#xff0c;也是在开发过…

小程序面试题 | 11.精选小程序面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

推荐算法架构7:特征工程(吊打面试官,史上最全!)

系列文章&#xff0c;请多关注 推荐算法架构1&#xff1a;召回 推荐算法架构2&#xff1a;粗排 推荐算法架构3&#xff1a;精排 推荐算法架构4&#xff1a;重排 推荐算法架构5&#xff1a;全链路专项优化 推荐算法架构6&#xff1a;数据样本 推荐算法架构7&#xff1a;特…

算法:BFS宽度优先遍历

文章目录 BFS与Queue相结合N叉树的层序遍历二叉树的锯齿形层序遍历二叉树的最大宽度 BFS和FLoodFill相结合图像渲染岛屿数量岛屿的最大面积 BFS解决最短路问题最小基因变化单词接龙为高尔夫比赛砍树 本篇总结的是BFS算法&#xff0c;BFS算法相比起DFS算法来说还是比较简单的 B…

基于 Sentry 的前端监控系统搭建(Linux)

一、前言 随着技术这几年的发展与沉淀&#xff0c;线上数据指标监控也变得尤为重要&#xff0c;研发人员和运营人员需要对线上的产品指标有所感知&#xff0c;同时风险也需要及时暴露&#xff0c;很多公司开始自建监控系统&#xff0c;但对于一些定制化要求不是特别高的团队&a…

Spark的核心概念:RDD、DataFrame和Dataset

Apache Spark&#xff0c;其核心概念包括RDD&#xff08;Resilient Distributed Dataset&#xff09;、DataFrame和Dataset。这些概念构成了Spark的基础&#xff0c;可以以不同的方式操作和处理数据&#xff0c;根据需求选择适当的抽象。 RDD&#xff08;Resilient Distribute…

Linux学习教程(第十七章 LAMP环境搭建和LNMP环境搭建)一

第十七章 LAMP环境搭建和LNMP环境搭建&#xff08;一&#xff09; LAMP 环境搭建指的是在 Linux 操作系统中分别安装 Apache 网页服务器、MySQL 数据库服务器和 PHP 开发服务器&#xff0c;以及一些对应的扩展软件。 LAMP 环境是当前极为流行的搭建动态网站的开源软件系统&…

【模式识别】探秘分类奥秘:最近邻算法解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《模式之谜 | 数据奇迹解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1f30c;1 初识模式识…

行为型设计模式(五):访问者模式 观察者模式

访问者模式 Visitor 1、什么是访问者模式 访问者模式允许定义一些不改变数据结构的前提下的操作。通过这种方式&#xff0c;可以在不修改元素类的情况下定义新的操作。访问者模式常用于对复杂对象结构进行操作&#xff0c;而又不希望在这些对象上破坏封装性。 2、为什么使用…

YOLOv8改进 | 主干篇 | 利用SENetV1改进网络结构 (ILSVRC冠军得主)

一、本文介绍 本文给大家带来的改进机制是SENet&#xff08;Squeeze-and-Excitation Networks&#xff09;其是一种通过调整卷积网络中的通道关系来提升性能的网络结构。SENet并不是一个独立的网络模型&#xff0c;而是一个可以和现有的任何一个模型相结合的模块(可以看作是一…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《计及风电不确定性的多场景多时段安全约束机组组合解耦求解方法》

这个标题涉及到一种解决在能源系统中考虑风电不确定性的方法。让我们逐步分解这个标题&#xff0c;以便更好地理解其含义&#xff1a; 计及风电不确定性&#xff1a; 这指的是在能源系统中&#xff0c;风力发电的产出具有不确定性。因为风速是难以预测的&#xff0c;风力发电的…

nodejs+vue+ElementUi大学新生入学系统的设计与实现1hme0

采用B/S模式架构系统&#xff0c;开发简单&#xff0c;只需要连接网络即可登录本系统&#xff0c;不需要安装任何客户端。开发工具采用VSCode&#xff0c;前端采用VueElementUI&#xff0c;后端采用Node.js&#xff0c;数据库采用MySQL。 涉及的技术栈 1&#xff09; 前台页面…

TokenFlow详解

https://github.com/omerbt/TokenFlow/issues/25 https://github.com/omerbt/TokenFlow/issues/31 https://github.com/omerbt/TokenFlow/issues/32 https://github.com/eps696/SDfu register_extended_attention_pnp1. 为所有BasicTransformerBlock layer的attn1重构forward2.…

LeetCode 剑指 Offer II 054. 所有大于等于节点的值之和

给定一个二叉搜索树&#xff0c;请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 提醒一下&#xff0c;二叉搜索树满足下列约束条件&#xff1a; 节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须…

【计数DP】牛客小白月赛19

登录—专业IT笔试面试备考平台_牛客网 题意 思路 首先做法一定是计数 dp 然后状态设计&#xff0c;先设 dp[i] 然后看影响决策的因素&#xff1a;两边的火焰情况&#xff0c;那就 dp[i][0/1][0/1]表示 前 i 个&#xff0c;该位有无火焰&#xff0c;该位右边有无火焰的方案数…