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

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

相关推荐
神仙别闹10 分钟前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭34 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
Data跳动1 小时前
Spark内存都消耗在哪里了?
大数据·分布式·spark
暮湫1 小时前
泛型(2)
java
超爱吃士力架1 小时前
邀请逻辑
java·linux·后端
南宫生1 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石1 小时前
12/21java基础
java
李小白661 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp1 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
Code apprenticeship2 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存