基于 SpringBoot + Redis 实现分布式锁

news2024/11/6 9:34:09

大家好,我是余数,这两天温习了下分布式锁,然后就顺便整理了这篇文章出来。文末附有源码链接,需要的朋友可以自取。

至于什么是分布式锁,这里不做赘述,不了解的可以自行去查阅资料。

文章目录

    • 实现要点
    • 项目结构
    • Parent Maven依赖
    • 锁的定义
    • 锁的使用
    • 源码地址
    • 参考资料
      • 如何用Redis实现分布式锁

实现要点

1. 使用 Redis 的 Setnx(SET if Not Exists) 命令加锁。

即锁不存在的时候才能加锁成功。如果锁存在了,说明其他服务已经持有该锁了,所以加锁失败。

2. 需要设置过期时间。

防止持有锁的服务意外挂掉后无法释放锁,导致其他服务永远都获取不到锁。

3. 锁的名字是固定的,但是锁的值需要保证线程唯一。

防止误删其他服务(线程)持有的锁。比如 线程A 获取到锁后被挂起了,等到锁自动过期后 线程B 又获得了锁,然后 线程B 开始执行自己的业务逻辑。

这个时候如果 线程A 被唤醒后并执行完所有的业务逻辑需要释放锁了,但这个时锁的持有者其实是 线程B,如果只根据 key 去释放锁的话,那么 线程A 就错误的把 线程B 持有的锁给释放掉了。

所以我们需要让 线程A 释放锁的时候,先判断一下锁是不是自己持有的,是才能释放。

判断锁是不是自己持有的就是通过加锁时给锁设置的 value来确定的。

4. 使用 Lua 脚本保证 “判断是否是自己持有的锁” 和 “释放锁” 的原子性。

因为判断和释放是两个命令,如果不保证原子性,会出现这种情况:刚判断完这个锁是自己的,然后这个锁就过期且被其他服务获取到了,你再释放岂不是把其他服务的锁给释放掉了。

5. 动态刷新锁的过期时间。

如果业务逻辑比较耗时,还没执行完锁就过期了怎么办。因为过期时间是在加锁的时候设置的,根本没有办法准确的预估到业务究竟需要多长时间。所以我们需要在业务逻辑没执行完的时候动态给锁续期,也就是更新锁的过期时间。

项目结构

  1. FileService: 模拟共享资源操作,以及分布式锁的实现。
  2. ServiceA:模拟分布式服务,读写共享资源。
  3. ServiceB:模拟分布式服务,读写共享资源。
  4. CountFile:共享资源,服务A服务B 会读写该文件中的内容。

Parent Maven依赖

基于SpringBoot 3.0.6。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo.iyushu</groupId>
    <artifactId>redis-lock</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>ServiceA</module>
        <module>ServiceB</module>
        <module>FileService</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.6</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>19</maven.compiler.source>
        <maven.compiler.target>19</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

锁的定义

锁的定义是在 FileService 模块实现的,同时该模块还实现了文件读写操作,结构如下:

  1. FileService:读写文件服务。
  2. Lock:定义一个锁。
public class Lock{

    private String key;

    private String value;

    // unit is second
    private int timeout;

    // 看门狗watchDog,用于给锁续期。
    private LockService.WatchDog watchDog;
}
  1. LockService:加锁和释放锁。

加锁:加锁时需要指定加锁的 keyvalue,和超时时间timeout。等待20s没有获取到锁则认为加锁失败。

加锁成功则返回锁,加锁失败返回空。

    public Lock lock(String key, String value, int timeout){
        boolean success = false;
        long start = System.currentTimeMillis();

        // 加锁等待时间 20s
        while(!success && System.currentTimeMillis() - start <= 20000){
            success =  redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
        }

        if(success){
            Lock lock = new Lock(key, value, timeout, new WatchDog());
            lock.getWatchDog().start();
            return lock;
        }

        return null;
    }

释放锁:先判断是自己的锁,然后将锁删掉,需要使用Lua脚本确认这两步的原子性。同时停止给锁的续期。

    public void unlock(Lock lock){
        redisTemplate.execute(unlockScript, Arrays.asList(lock.getKey()), lock.getValue());
        lock.getWatchDog().stop();
    }

Lua 脚本

--- Lua脚本语言,释放锁 比较value是否相等,避免删除别人占有的锁
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

动态给锁续期:加锁成功后会新启一个线程,每隔1s给锁续期一次,直到业务逻辑执行完并释放锁后,续期线程中断。

    public class WatchDog implements Runnable {

        private Lock lock;

        public void setLock(Lock lock) {
            this.lock = lock;
        }

        private Thread thread;

        public void start(){
            thread = new Thread(this);
            thread.start();
        }

        public void stop(){
            thread.interrupt();
        }


        @Override
        public void run() {
            long start = System.currentTimeMillis();
            while(!thread.isInterrupted()){
                long current = System.currentTimeMillis();
                if(current - start >= 1000){
                    System.out.println("续期。。。");
                    redisTemplate.opsForValue().getAndExpire(lock.getKey(), lock.getTimeout(), TimeUnit.SECONDS);
                    start = current;
                }
            }

        }
    }

锁的使用

ServiceAServieB 主要模拟了实际业务中的多个服务,执行有资源共享的业务逻辑前先获取锁,获取成功才继续执行,执行完成后释放锁。

注意加锁的时候,value 值需要是唯一的,这里使用了服务名 + 线程名的方式。

public int getCount() throws InterruptedException {

	Lock lock = null;
    try{
        // 获取锁
        lock = lockService.lock("lock", "服务A" + Thread.currentThread().getName(), 3);
        if(lock == null){
            throw new RuntimeException("lock failed");
        }
        int count = fileService.getCount();
        System.out.println("服务A获取到的计数为" + count);
    
        // 模拟业务逻辑
        Thread.sleep(2000);
    
        fileService.setCount(count + 1);
        return count;
    
    }finally {
        // 释放锁
        if(lock != null){
            lockService.unlock(lock);
        }

	}
}

我们让 服务A服务B 同时各执行10次读写文件的操作,每次写的时候将文件中的数字加一,看看结果如何。

服务A运行结果:

服务B运行结果:

可以看到加锁成功,计数是正常的。两个服务几乎是交替执行的,即一个服务执行完成后,另一个服务才能获取到锁并执行业务逻辑。

那试试不加锁会怎样呢?对比一下看看,将 服务A服务B 中加锁的逻辑注释掉。

毫无悬念的,服务B服务A 的计数完全覆盖了。

源码地址

以下是源码链接,仅用于理解Redis分布式锁的实现原理,代码还有很多不足,切勿用于生产环境哟,欢迎多多交流,嘿嘿~

https://github.com/justyuze/share-redis-lock/tree/main

参考资料

如何用Redis实现分布式锁

https://blog.csdn.net/fuzhongmin05/article/details/119251590

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

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

相关文章

android13 FLAG_BLUR_BEHIND 壁纸高斯模糊,毛玻璃背景方案设计-千里马framework实战

hi,粉丝朋友们! 今天有个学员朋友&#xff0c;问到了一个高斯模糊相关问题&#xff0c;这个高斯模糊相关的需求我相对还是比较熟悉&#xff0c;下面来重点讲解一下新版本高斯模糊相关的实现。 更多framework干货知识手把手教学 Log.i("qq群"&#xff0c;“422901085…

[230528] 托福阅读真题|TPO66 13/30|整卷得分22/30|9:45~10:45|15:40~16:40

The Actor and the Audience P1 rehearsev 排练&#xff1b;排演anticipate v 预期&#xff1b;预料&#xff1b;预见 audiencen 观众brilliantadj 灿烂的&#xff1b;绝妙的rehearsaln 排练&#xff1b;预演&#xff1b;排演crumblev 崩塌stage frightn 怯场&#xff08;演员…

自动化测试框架?这应该是全网最全自动化框架总结了,你要的都有...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

学术加油站|基于LSM-tree存储系统的内存管理,最大限度降低I/O成本

本文系北京理工大学科研助理牛颂登所著&#xff0c;本篇也是 OceanBase 学术系列稿件第 10 篇。欢迎访问 OceanBase 官网获取更多信息&#xff1a;https://www.oceanbase.com/ 「牛颂登&#xff1a;北京理工大学科研助理&#xff0c;硕士期间在电子科技大学网络空间安全研究院从…

资深老鸟总结,Selenium自动化测试实战小技巧,不要再走弯路了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Selenium4自动化测…

数据库小技能:数据报表

文章目录 I 需求1.1 补贴II 实现思路2.1 生成资金调节报表数据III Dto3.1 报表基本查询IV 接口I 需求 代理商调节活动汇总商户调节活动汇总激励金日月汇总数据源:活动流水表(上游回调) 1.1 补贴 调节活动补贴= D0补贴+T1补贴。(比如交易金额满足1000,转T1) 补贴金额 =…

图扑数字孪生智慧灯杆,“多杆合一”降本增效

前言 随着智慧城市建设的不断深入&#xff0c;智慧灯杆作为城市基础设施的重要组成部分&#xff0c;正在成为城市智能化和绿色化的重要手段之一。 效果展示 图扑智慧灯杆系统在城市道路照明领域引入信息化手段&#xff0c;通过构建路灯物联网&#xff0c;实现了现代化的路灯按…

线性代数 --- Gram-Schmidt, 格拉姆-施密特正交化(下)

Gram-Schmidt正交化过程 到目前为止&#xff0c;我们都是在反复强调“对于无解的方程组Axb而言&#xff0c;如果矩阵A是标准正交矩阵的话&#xff0c;就怎么怎么好了。。。。”。因为&#xff0c;不论是求投影还是计算最小二乘的正规方程&#xff0c;他们都包含了。当A为标准正…

yolov4论文解读

数据层面上的数据增强 四张照片拼接成一张进行训练 相当于增大了batch-size&#xff0c;更适合于单GPU。 Mosaic data augmentation 马赛克数据增强 self-adversarial training(SAT) 自我对抗训练 DropBlock Label Smoothing 损失函数 由IOU改进到CIOU 网络结构 CSPNet&…

Win10 WLAN驱动正常但仍然不显示无线网络解决办法

Win10 WLAN驱动正常但仍然不显示无线网络解决办法 写作背景过程解决方案结尾 写作背景 本菜鸡重置了电脑的网络&#xff0c;然后重新启动后 WLAN 不见了&#xff0c;连不了 WIFI 了&#xff0c;很疑惑&#xff0c;后来经过一番搜索找到了问题所在&#xff0c;写下本篇文章以记…

Spark/Flink广播实现作业配置动态更新

前言 在实时计算作业中&#xff0c;往往需要动态改变一些配置&#xff0c;举几个栗子&#xff1a; 实时日志ETL服务&#xff0c;需要在日志的格式、字段发生变化时保证正常解析&#xff1b;实时NLP服务&#xff0c;需要及时识别新添加的领域词与停用词&#xff1b;实时风控服…

访问学者J1签证面签的七个问题

作为访问学者&#xff0c;申请J1签证面签时可能会遇到一些常见问题。下面知识人网小编将介绍七个访问学者面签可能遇到的问题&#xff0c;并提供相应的答案。 问题一&#xff1a;您将在美国进行何种类型的学术研究&#xff1f; 答案&#xff1a;我将在美国从事学术研究&#x…

普冉PY32L020单片机简介,主频最高48MHZ

PY32L020单片机是一颗32 位 ARM Cortex-M0内核&#xff0c;宽电压工作范围的 MCU。这颗MCU的价格跟八位单片机相差不大&#xff0c;性价比可以说是非常的高了。来看看PY32L020的配置吧。 PY32L020单片机产品特性&#xff1a; 内核&#xff1a; — 32 位 ARM Cortex - M0 — 最…

飞浆AI studio人工智能课程学习(2)-Prompt优化思路|十个技巧高效优化Prompt|迭代法|Trick法|通用法|工具辅助

文章目录 优化思路上节课的例子问题分析思路解析 Prompt优化技巧Prompt优化原理 十个技巧高效优化Prompt迭代法Trick法工具法通用技巧│定基础通用技巧│做强调需求强调怎么做&#xff1f; 通用技巧│提预设Trick法│戴高帽原理 Trick法│说好话以基础计算为例: Trick法│给提示…

小红书数据分析:如何用ChatGPT输出爆文笔记

ChatGPT的热度依旧不减&#xff0c;随着技术升级&#xff0c;越来越多更高级的玩法被发掘。今天我们就来聊聊&#xff0c;如何用ChatGPT写出小红书风格的文章。 首先&#xff0c;小红书笔记制作分为两个步骤&#xff1a; 1、找选题 2、写小红书风格的笔记 我们用例子说话&a…

全国自考本科通过率仅7%,为什么还有这么多人报考?

根据教育部官网公布的《2021年全国教育事业发展统计公报》得知&#xff1a;2021年&#xff0c;全国高等教育自学考试学历教育报考625.78万人次&#xff0c;取得毕业证书48.94万人。也就是说2021年我国自考平均通过率大概在7%左右。 自考通过率为什么这么低&#xff1f; ①自考…

Android平台外部编码数据(H264/H265/AAC/PCMA/PCMU)实时预览播放技术实现

开发背景 好多开发者可能疑惑&#xff0c;外部数据实时预览播放&#xff0c;到底有什么用&#xff1f; 是的&#xff0c;一般场景是用不到的&#xff0c;我们在开发这块前几年已经开发了非常稳定的RTMP、RTSP直播播放模块&#xff0c;不过也遇到这样的场景&#xff0c;部分设…

“ChatGPT之父”勇闯币圈!数十亿人的空投计划,只需交出你的虹膜?

最近&#xff0c;Worldcoin&#xff08;世界币&#xff09;热度持续提升&#xff0c;这个由OpenAI创始人SamAltman亲自操持的加密项目&#xff0c;让沉寂已久的币圈开始躁动起来。 虽然Worldcoin并非最新项目&#xff0c;但颇具乌托邦色彩的理念&#xff0c;独特的虹膜识别机制…

10几个国产免费ChatGPT网页版(内附体验网址)

文章目录 前言1. AI文本工具站效率工具自媒体创作工具代码工具 2.道和顺ChatIC3.星期五4.文心一言5.讯飞星火认知大模型6.通义千问7.商汤-日日新8.Moss9.ChatGLM10. 360智脑写在最后 前言 随着ChatGPT迅速走红,国内各大企业纷纷发力认知大模型领域。经过一段时间的酝酿&#x…

剩余参数、扩展运算符、将伪数组转换伪数组

剩余参数 剩余参数语法允许我们将一个不定数量的参数表示为一个数组 语法: array.forEach(function(currentValue,index,arr){}) currentValue:数组当前项的值index:数组当前项的索引arr:数组对象本身 示例 <!DOCTYPE html> <html lang"en"><h…