Redis分布式锁学习总结

news2025/2/26 22:17:57

⭐️ 前言

想必大家都有过并发编程的经验,在一个单体应用中,可以通过java提供的各种锁机制来控制多线程对于单体应用中同一资源的并发访问;那么在分布式场景下,想要控制多个应用对于同一外部资源的并发访问,就要用到分布式锁。分布式锁不但要保证单个应用程序内部不会产生并发问题,同时也要保证多个应用程序之间不能产生并发问题。分布式锁有很多实现方式,比如使用redis、zookeeper或关系型数据库的唯一索引,也有现成的分布式锁架构,比如redisson、curator等。本文利用spring-data-redis手动实现一个简易的redis分布式锁,剖析redis分布式锁的原理。
在这里插入图片描述

⭐️ redis分布式锁实现原理浅析

实现redis分布式锁最简单的想法就是各个应用利用setnx命令向redis中争抢设置key的机会,但我们还应该考虑得更加周全。

死锁

如果成功加锁的应用程序在未释放锁之前就异常终止了,那么这个锁永远无法释放,其他应用程序则永远也无法获取到锁,为了解决这个问题,需要给代表锁的redis的key加上过期时间。

原子性

很多时候我们需要保证多个操作具有原子性,
例如,加锁和设置过期时间
若它们无法保证原子性,则应用程序在刚刚成功加锁后就异常终止了,则仍然会出现上面的死锁问题。

原子性可以通过让redis执行Lua脚本来保证,eval命令可以原子性的执行Lua脚本(Lua的多个步骤会被原子性的执行),在redis内置的Lua脚本中有一个redis对象,可以通过redis.call()方法执行各种redis命令。

防误删

根据上面的讨论,我们需要给redis锁加上过期时间,当业务执行完毕之前锁就过期了,这种情况下,其他线程就会成功加锁,那么之前的程序运行到解锁逻辑时,就会造成对后面线程获得锁的误删。
在这里插入图片描述
误删可以通过给每一个线程设置一个id,我们可以叫它 线程标识码

可重入性

另外,在应用程序中免不了方法的彼此调用,若锁无法重入,则业务根本无法执行,比如A方法需要加锁,它在执行过程中会调用B方法,B方法也需要加锁,若锁不具有可重入性,则程序根本无法运行。

可重入性可以通过hash数据结构来实现
key: 代表锁的key
field:线程的唯一标识id,即上文说的 线程标识码
value:重入次数

自动续期

若应用程序执行需要的时间大于锁的过期时间,则锁过期后,应用程序便不再受锁保护,这样就会导致并发问题。所以分布式锁还要具有自动续期的功能,即只要应用程序业务没有执行完毕,则锁需要不断的自动延长过期时间。
自动续期可以通过Timer定时任务配合Lua脚本来实现。

本文参考了上硅谷课程《【尚硅谷】分布式锁全家桶丨一套搞定Redis/Zookeeper/MySQL实现分布式锁》,B站上就有,更详细的内容读者可以去看这门课程。

这里贴出关键代码,完整代码我已经上传到了gitee。欢迎围观啊!!

⭐️ 加锁主要代码

/**
     * 加锁
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1){
            this.expire = unit.toSeconds(time);
        }
        // redis加锁的lua脚本
        String lockStr="if redis.call('exists', KEYS[1])==0 or redis.call('hexists', KEYS[1], ARGV[1])==1 " +
                "then " +
                "redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "redis.call('expire', KEYS[1], ARGV[2]) " +
                "return 1 " +
                "end " +
                "return 0";
        // 加锁
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(lockStr, Boolean.class), Arrays.asList(this.lockName), this.uuid, String.valueOf(this.expire))){
            Thread.sleep(50);
        }
        // 自动续期
        this.autoExpire();
        return true;
    }

⭐️ 解锁主要代码

/**
     * 解锁
     */
    @Override
    public void unlock() {
        // 解锁lua脚本
        String unlockStr = "if redis.call('hexists', KEYS[1], ARGV[1])==0 " +
                "then " +
                "return nil " +
                "end " +
                "if redis.call('hincrby', KEYS[1], ARGV[1], -1)==0 " +
                "then " +
                "return redis.call('del', KEYS[1]) " +
                "end " +
                "return 0";
        // 解锁
        Long del = this.redisTemplate.execute(new DefaultRedisScript<>(unlockStr, Long.class), Arrays.asList(this.lockName), this.uuid);
        if (del == null){
            throw new IllegalMonitorStateException("lock wrong");
        }
        if (del == 1L){
            System.out.println("lock deleted");
        }
    }

⭐️ 自动续期主要代码

/**
     * 自动续期
     */
    private void autoExpire(){
        String expireStr="if redis.call('hexists', KEYS[1], ARGV[1])==1 " +
                         "then return redis.call('expire', KEYS[1], ARGV[2]) " +
                         "end " +
                         "return 0";

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (redisTemplate.execute(new DefaultRedisScript<>(expireStr, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
                    autoExpire();
                }
            }
        }, this.expire * 1000 /3);
    }

⭐️ 运行架构

运行架构比较简单,可以启动两个应用实例,利用nginx做负载均衡。
在这里插入图片描述

⭐️ 压力测试

压力测试可以使用jmeter,其设置如下图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
笔者水平有限,若有不对的地方欢迎评论指正!

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

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

相关文章

npm ERR! notarget No matching version found for @eslint/eslintrc@^2.1.4.

文章目录 Intro解决流程总结前置信息了解npm 镜像源三个要用到的npm命令 官方源确认查看当前镜像源的详情解决&#xff1a; 切换镜像源后重试重新操作 事后感受 Intro 事由是今天我在用 create-react-app 新建一个用于测试的前端项目。 然后就出现以下报错&#xff1a; wuyuj…

聚观早报 |国行PS5轻薄版开售;岚图汽车11月交付7006辆

【聚观365】12月2日消息 国行PS5轻薄版开售 岚图汽车11月交付7006辆 比亚迪推出12月限时优惠 特斯拉正式交付首批Cybertruck 昆仑万维发布「天工 SkyAgents」平台 国行PS5轻薄版开售 索尼最新的PlayStation5主机&#xff08;CFI-2000型号组-轻薄版&#xff09;国行版本正…

算法通关村第五关—队栈和Hash的经典问题(白银)

emsp;emsp;emsp队栈和Hash的经典问题 用栈实现队列 栈是先进后出&#xff0c;队列是先进先出&#xff0c;所以可以使用两个栈来实现队列的功能。 LeetCode232&#xff1a; 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、 empty): …

不可抗力因素包括什么内容

一、《民法典》所称的“不可抗力”&#xff0c;是指不能预见、不能避免并不能克服的客观情况。主要包括以下几种情形&#xff1a; (1)自然灾害、如台风、洪水、冰雹&#xff1b; (2)政府行为&#xff0c;如征收、征用&#xff1b; (3)社会异常事件&#xff0c;如罢工、骚乱。 ​…

【设计模式-4.1】行为型——策略模式

说明&#xff1a;本文介绍设计模式中的行为型设计模式中的&#xff0c;策略模式&#xff1b; 计算器 策略模式属于行为型设计模式&#xff0c;关注对象的行为。例如&#xff0c;目前有一个计算器类&#xff0c;可对两个数进行加减计算&#xff0c;如下&#xff1a; &#xf…

Innodb数据空间占用探索

了解数据存储空间占用&#xff0c;可以更方便我们再企业中对于数据库相关优化做评估。 一、查看当前数据表空间占用信息 首先这里准备一张数据库表约2.3w数据量&#xff1a; CREATE TABLE project (tenantsid bigint(20) NOT NULL DEFAULT 0 COMMENT 租户ID,project_id bigi…

Redis RDB

基于内存的 Redis, 数据都是存储在内存中的。 那么如果重启的话, 数据就会丢失。 为了解决这个问题, Redis 提供了 2 种数据持久化的方案: RDB 和 AOF。 RDB 是 Redis 默认的持久化方案。当满足一定条件的时候, 会把当前内存中的数据写入磁盘, 生成一个快照文件 dump.rdb。Redi…

thinkphp 5.1 对数据库查出来的字段进行预处理

比如数据库的设计是下面这样子&#xff1a; 我想展示的是这个样子&#xff1a; 前端可以处理。 Think PHP的处理方式&#xff1a; 定义属性 &#xff1a; $this->customize 任意值;//这里的之没有作用 <?phpnamespace app\hs\controller\shop;use app\daogou\mo…

C++ list容器

文章目录 C++ list容器list基本概念list构造函数list 赋值和交换list 大小操作list 插入和删除list 数据存取list 反转和排序排序案例C++ list容器 list基本概念 功能:将数据进行链式存储 链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中…

ipvlan介绍

最近使用docker&#xff0c;涉及到需要跨多台物理机部署系统&#xff0c;查了好多资料&#xff0c;最后查到了ipvlan。那什么是vlan&#xff0c;什么又是ipvlan。 交换机层面的vlan&#xff0c;是按802.1Q规范&#xff0c;在链路层中加了4字节的标识vlan的数据&#xff0c;交换…

12.1 二叉树简单题

101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 思路&#xff1a;对称二叉树 有一个特点是以 中左右顺序遍历左子树的结果会等于 中右左顺序遍历右子树的结果…

Linux的基本指令(4)

目录 20.tar指令&#xff08;重要&#xff09;&#xff1a;打包/解包&#xff0c;不打开它&#xff0c;直接看内容 21.bc指令 22.uname –r指令&#xff1a; 23.重要的几个热键[Tab],[ctrl]-c, [ctrl]-d 20.tar指令&#xff08;重要&#xff09;&#xff1a;打包/解包&#…

Hdoop学习笔记(HDP)-Part.08 部署Ambari集群

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

深度学习(四):pytorch搭建GAN(对抗网络)

1.GAN 生成对抗网络&#xff08;GAN&#xff09;是一种深度学习模型&#xff0c;由两个网络组成&#xff1a;生成器&#xff08;Generator&#xff09;和判别器&#xff08;Discriminator&#xff09;。生成器负责生成假数据&#xff0c;而判别器则负责判断数据是真实的还是 f…

cnpm 安装后无法使用怎么办?

问题的原因 cnpm 安装成功&#xff0c;但是却无法使用&#xff0c;一般分为两种情况&#xff0c;一种是提示无法执行命令&#xff0c;另一种是可以执行但是执行时报错&#xff0c;下面分别说明遇到这两种情况的解决方案。 解决方案 问题一&#xff1a;无法执行相关命令 首先…

Docker快速入门(docker加速,镜像,容器,数据卷常见命令操作整理)

Docker本质是将代码所需的环境依赖进行打包运行,而在Docker中最重要的是镜像和容器 镜像:可以简单地理解为每启动一个docker镜像就会占用计算机一个进程,这个进程和另外起的docker镜像的进程是相互独立的,以数据库为例,每个镜像都会copy一份数据库,在他所在的进程中.别的镜像在…

根文件系统构建-对busybox进行配置

一. 简介 本文来学习 根文件系统的制作中&#xff0c;关于 busybox的配置。 本文继上一篇 busybox中文支持的设置&#xff0c;地址如下&#xff1a; 根文件系统构建-busybox中文支持-CSDN博客 二. 根文件系统构建-busybox配置 1. 配置 busybox 与我们编译 Uboot 、 Lin…

DBeaver 社区版(免费版)下载、安装、解决驱动更新出错问题

DBeaver 社区版&#xff08;免费版&#xff09; DBeaver有简洁版&#xff0c;企业版&#xff0c;旗舰版&#xff0c;社区版&#xff08;免费版&#xff09;。除了社区版&#xff0c;其他几个版本都是需要付费的&#xff0c;当然相对来说&#xff0c;功能也要更完善些&#xff…

HashMap源码全面解析

注&#xff1a;本篇文章是在JDK1.8版本源码进行分析。 一、概述 HashMap 是基于哈希表的 Map接口的实现&#xff0c;是以 key-value 存储形式存在&#xff0c;即主要用来存储键值对。 HashMap的类图&#xff1a; HashMap继承抽象类AbstractMap&#xff0c;实现了Map、Clonea…

select选择框里填充图片,下拉选项带图片

遇到一个需求&#xff0c;选择下拉框选取图标&#xff0c;填充到框里 1、效果展示 2、代码 <el-form-item label"工种图标" class"Form_icon Form_label"><el-select ref"select" :value"formLabelAlign.icon" placeholder&…