Redisson分布式锁的概念和使用

Redisson分布式锁的概念和使用

    • [一 简介](#一 简介)
      • [1.1 什么是分布式锁?](#1.1 什么是分布式锁?)
      • [1.2 Redisson分布式锁的原理](#1.2 Redisson分布式锁的原理)
      • [1.3 Redisson分布式锁的优势](#1.3 Redisson分布式锁的优势)
      • [1.4 Redisson分布式锁的应用场景](#1.4 Redisson分布式锁的应用场景)
    • [二 案例](#二 案例)
      • [2.1 锁竞争案例](#2.1 锁竞争案例)
      • [2.2 看门狗案例](#2.2 看门狗案例)
      • [2.3 参考文章](#2.3 参考文章)

前言

这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。

作者:神的孩子都在歌唱

一 简介

1.1 什么是分布式锁?

在分布式系统中,多个服务实例或进程可能会同时访问共享资源(例如数据库、文件等)。为了防止数据竞争或一致性问题,我们需要一种机制来确保在同一时间,只有一个进程能够访问这些资源。这种机制就是分布式锁。

Redisson 是一个支持 Redis 的 Java 客户端,它不仅能提供简单的 Redis 连接,还包括了许多高级功能,如分布式锁、异步任务执行、限流等。Redisson 基于 Redis 来实现分布式锁,具备高效、可靠的特性。

1.2 Redisson分布式锁的原理

Redisson 的分布式锁主要依赖 Redis 的 SETNXEXPIRE 命令来实现。流程如下:

  1. 获取锁 :客户端通过 SETNX(SET if Not eXists)命令尝试在 Redis 中设置一个键。如果该键不存在,表示没有其他进程持有锁,当前进程即可成功获取锁。

  2. 锁过期时间:为了避免某个持有锁的进程崩溃而导致锁无法释放,通常会为锁设置一个过期时间(如10秒)。如果超过这个时间锁还没有被释放,Redis 将自动删除该锁,允许其他进程获取。

  3. 释放锁 :当任务完成后,进程会通过 DEL 命令来释放锁,其他进程就可以重新获取锁。

  4. 可重入锁:Redisson 提供了可重入锁,即同一个线程可以多次获取锁,而不会被锁定阻塞。只有当线程完全释放锁后,其他线程才能获取该锁。

1.3 Redisson分布式锁的优势

  • 高效:Redis 基于内存操作,具有极快的响应速度。Redisson 使用 Lua 脚本将获取和释放锁的操作进行原子化操作,避免并发问题。
  • 可靠性:Redisson 通过 Redis 的过期机制和 Watchdog(看门狗)机制,确保锁可以自动释放,防止因进程异常退出导致的死锁问题。
  • 可扩展性:Redisson 支持 Redis 集群、哨兵模式等多种模式,适用于不同规模的分布式系统。

1.4 Redisson分布式锁的应用场景

  1. 库存扣减:在电商系统中,当用户发起购买请求时,需要确保在多个并发请求中,库存只能被扣减一次,避免超卖现象。

  2. 任务调度:在分布式任务调度系统中,需要保证同一时间只有一个服务实例执行某个任务,防止重复执行。

  3. 分布式事务:在分布式事务中,确保在多个服务之间的一致性操作,分布式锁能够确保只有一个服务能修改某个共享资源。

二 案例

2.1 锁竞争案例

假设我有两个节点可以执行同一种业务,我需求是节点1执行的时候,节点2不能执行。那么就可以使用分布式锁实现,代码如下

引入redisson的依赖

xml 复制代码
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.35.0</version>
        </dependency>

编一个redisson客户端

java 复制代码
public class RedissonConfig {

    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.1.47:6379").setPassword("xxxx");
        return Redisson.create(config);
    }
}

节点1

java 复制代码
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RKeys;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
 * @Author chenyunzhi
 * @DATE 2024/9/3 10:01
 * @Description:
 */
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        RedissonConfig redissonConfig = new RedissonConfig();
        RedissonClient redissonClient = redissonConfig.redissonClient();
        int i = 0;
        Thread.sleep(2000);
        while (i < 5) {
            try {
                Date currentDate = new Date();
                System.out.println("当前时间: " + currentDate);
                RLock testLock = redissonClient.getLock("testLock");
                //尝试获取锁   waitTime(重试等待时间),leaseTime(过期时间),TimeUnit(时间单位)
                boolean b = testLock.tryLock(1, 5, TimeUnit.SECONDS);
                i++;
                if (b) {
                    try {
                        Thread.sleep(2000);
                        System.out.println("执行业务" +  i + "次 节点1");
                    } finally {
                        testLock.unlock();
                    }
                } else {
                    System.out.println("获取锁失败  节点1");
                }
            } catch (Exception e) {
                System.out.println("锁中断 节点1" + e.getMessage());
            }
        }
    }
}

节点1完成业务的速度大约2秒钟,循环执行5次任务,如果获取锁失败就不执行,进入下一次执行

节点2

java 复制代码
package org.example.demo;
import org.example.tool.RedissonConfig;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
 * @Author chenyunzhi
 * @DATE 2024/9/3 10:01
 * @Description:
 */
public class demo2 {
    public static void main(String[] args) throws InterruptedException {
        RedissonConfig redissonConfig = new RedissonConfig();
        RedissonClient redissonClient = redissonConfig.redissonClient();
        int i = 0;
        while (i < 5) {
            try {
                Date currentDate = new Date();
                System.out.println("当前时间: " + currentDate);
                RLock testLock = redissonClient.getLock("testLock");
                //尝试获取锁   waitTime(重试等待时间),leaseTime(过期时间),TimeUnit(时间单位)
                boolean b = testLock.tryLock(1, 10, TimeUnit.SECONDS);
                i++;
                if (b) {
                    try {
                        Thread.sleep(1000);
                        System.out.println("执行业务" +  i + "次 节点2");
                    } finally {
                        testLock.unlock();
                    }
                } else {
                    System.out.println("获取锁失败  节点2");
                }
            } catch (Exception e) {
                System.out.println("锁中断 节点2" + e.getMessage());
            }
        }
    }
}

节点2完成业务的速度大约1秒钟,循环执行5次任务,如果获取锁失败就不执行,进入下一次执行

然后我们同时允许两个节点行测试

通过上面测试可以发现,在节点1执行业务的时候,节点2获取锁失败了,然后无法执行业务,反之也是如此

2.2 看门狗案例

上面的案例我们为了防止任务死锁,都会给锁都设定了有效时间,可是我们不确定这个任务要执行多久,就会导致任务还没执行完成,锁就先过期了。watchDog(看门狗)的作用是可以确保等待某个节点任务完全执行完成后才去释放锁。

redisson的看门狗底层使用的是setnx加lua脚本 实现的,会定期给锁续约,默认是每隔10s续期一次,一次续约30s ,其他线程在最大等待时间内自旋,不断尝试获取锁,超过最大等待时间则获取锁失败,设置默认加锁时间的参数是 lockWatchdogTimeout ,会和主线程一起销毁。。

注意

  1. 看门狗只会在锁没设定过期时间的时候才有效
  2. 如果任务一直阻塞,那么锁就会一直续期,得不到释放
java 复制代码
package org.example.demo;

import org.example.tool.RedissonConfig;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

/**
 * @Author chenyunzhi
 * @DATE 2024/9/21 11:26
 * @Description:
 */
public class demo3 {
    public static void main(String[] args) {
        RedissonConfig redissonConfig = new RedissonConfig();
        RedissonClient redissonClient = redissonConfig.redissonClient();
        RLock myLock = redissonClient.getLock("myLock");
        if (myLock.tryLock()) {
            try {
                // 模拟任务执行
                System.out.println("执行任务");
                Thread.sleep(50000); // 模拟50秒的任务
                System.out.println("任务完成");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //判断当前线程是否持有锁  isHeldByCurrentThread
                if (myLock.isHeldByCurrentThread()) {
                    myLock.unlock();
                    System.out.println("释放锁");
                }
            }
        }
        redissonClient.shutdown();
    }
}

使用myLock.tryLock()不设置过期时间,那么看门狗会默认启动,然后会默认设置30秒的过期时间,每10秒刷新一次过期时间。

2.3 参考文章

github: https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers

redisson中的看门狗机制总结

Redis的分布式锁

redisson watchdog 使用和原理

作者:神的孩子都在歌唱

本人博客:https://blog.csdn.net/weixin_46654114

转载说明:务必注明来源,附带本人博客连接。

相关推荐
iReachers11 小时前
为什么HTML打包安卓APP安装时会覆盖或者报错?
android·java·html·html打包apk·网页打包
纟 冬11 小时前
Flutter & OpenHarmony 运动App运动模式选择组件开发
android·java·flutter
毕设源码-赖学姐11 小时前
【开题答辩全过程】以 基于Springboot的智慧养老系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
jamesge201011 小时前
限流之漏桶算法
java·开发语言·算法
jvstar11 小时前
JAVA面试题和答案
java
冷雨夜中漫步11 小时前
OpenAPITools使用——FAQ
android·java·缓存
9坐会得自创12 小时前
使用marked将markdown渲染成HTML的基本操作
java·前端·html
Hello.Reader12 小时前
Flink ML 线性 SVM(Linear SVC)入门输入输出列、训练参数与 Java 示例解读
java·支持向量机·flink
oioihoii12 小时前
C++数据竞争与无锁编程
java·开发语言·c++