基于 SpringBoot + Redis 实现分布式锁

这篇具有很好参考价值的文章主要介绍了基于 SpringBoot + 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 会读写该文件中的内容。

基于 SpringBoot + Redis 实现分布式锁

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 模块实现的,同时该模块还实现了文件读写操作,结构如下:

基于 SpringBoot + Redis 实现分布式锁

  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运行结果:
基于 SpringBoot + Redis 实现分布式锁

服务B运行结果:
基于 SpringBoot + Redis 实现分布式锁

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

那试试不加锁会怎样呢?对比一下看看,将 服务A服务B 中加锁的逻辑注释掉。
基于 SpringBoot + Redis 实现分布式锁
基于 SpringBoot + Redis 实现分布式锁

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

源码地址

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

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

参考资料

如何用Redis实现分布式锁

https://blog.csdn.net/fuzhongmin05/article/details/119251590文章来源地址https://www.toymoban.com/news/detail-451721.html

到了这里,关于基于 SpringBoot + Redis 实现分布式锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 2、基于redis实现分布式锁

    借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setnx命令,只有一个客户端可以成功,返回1(true);其他的客户端返回0(false)。 多个客户端同时获取锁(setnx) 获取成功,执行业务逻辑,执行完成释放锁(del) 其他客户端等

    2024年02月15日
    浏览(66)
  • 基于 Redis 实现分布式限流

    分布式限流是指通过将限流策略嵌入到分布式系统中,以控制流量或保护服务,保证系统在高并发访问情况下不被过载。 分布式限流可以防止系统因大量请求同时到达导致压力过大而崩溃,从而提高系统的稳定性和可靠性。同时,它可以使得业务资源能够更好地分配,提高系

    2024年02月12日
    浏览(41)
  • 基于springboot+Redis的前后端分离项目之分布式锁-redission(五)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 基于setnx实现的分布式锁存在下面的问题: 重入问题 :重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都

    2024年02月11日
    浏览(41)
  • 自定义注解,基于redis实现分布式锁

    1.1、注解的基础知识 实现自定义注解其实很简单,格式基本都差不多。也就参数可能变一变。 @Retention:取值决定了注解在什么时候生效,一般都是取运行时,也就是RetentionPolicy.RUNTIME。 @Target:决定了这个注解可以使用在哪些地方,可以取方法,字段,类等。 注解这就定义

    2024年02月08日
    浏览(36)
  • 【Redis从入门到进阶】第 7 讲:基于 Redis 实现分布式锁

    本文已收录于专栏 🍅《Redis从入门到进阶》🍅    本专栏开启,目的在于帮助大家更好的掌握学习 Redis ,同时也是为了记录我自己学习 Redis 的过程,将会从基础的数据类型开始记录,直到一些更多的应用,如缓存击穿还有分布式锁等。希望大家有问题也可以一起沟通,欢

    2023年04月26日
    浏览(40)
  • SpringBoot基于Zookeeper实现分布式锁

    研究分布式锁,基于ZK实现,需要整合到SpringBoot使用 参考自SpringBoot集成Curator实现Zookeeper基本操作,Zookeeper入门 本篇的代码笔者有自己运行过,需要注意组件的版本号是否兼容,否则会有比较多的坑 采用Docker compose快速搭建ZK容器,很快,几分钟就好了,而且是集群方式搭建

    2024年02月12日
    浏览(47)
  • springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下

    为什么要实现这个功能呢,可能用户在提交一份数据后,可能因为网络的原因、处理数据的速度慢等原因导致页面没有及时将用户刚提交数据的后台处理结果展示给用户,这时用户可能会进行如下操作: 1秒内连续点击提交按钮,导致重复提交表单。 使用浏览器后退按钮重复之

    2024年02月08日
    浏览(40)
  • 基于 Redis + Lua 脚本实现分布式锁,确保操作的原子性

    1.加锁的Lua脚本: lock.lua 2.解锁的Lua脚本: unLock.lua 3.将资源文件放在资源文件夹下 4.Java中调用lua脚本 1)获取文件方式 2)lua字符串方式 5.jedis调用Lua脚本实现分布式重试锁 1)引入jedis依赖 2)jedis调用lua

    2024年02月07日
    浏览(51)
  • Zookeeper 和 Redis 哪种更好? 为什么使用分布式锁? 1. 利用 Redis 提供的 第二种,基于 ZK 实现分布式锁的落地方案 对于 redis 的分布式锁而言,它有以下缺点:

    关于这个问题,我们 可以从 3 个方面来说: 为什么使用分布式锁? 使用分布式锁的目的,是为了保证同一时间只有一个 JVM 进程可以对共享资源进行操作。 根据锁的用途可以细分为以下两类: 允许多个客户端操作共享资源,我们称为共享锁 这种锁的一般是对共享资源具有

    2024年01月16日
    浏览(46)
  • Redis实战案例14-分布式锁的基本原理、不同实现方法对比以及基于Redis进行实现思路

    基于数据库的分布式锁:这种方式使用数据库的特性来实现分布式锁。具体流程如下: 获取锁:当一个节点需要获得锁时,它尝试在数据库中插入一个特定的唯一键值(如唯一约束的主键),如果插入成功,则表示获得了锁。 释放锁:当节点完成任务后,通过删除该唯一键

    2024年02月13日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包