分布式锁2:基于redis的插件redission实现分布式锁

本文介绍了Redisson分布式锁的基本概念,包括其利用setnx实现锁的机制、watchdog防止锁过期、不同类型的锁如重入锁、公平锁和红锁等,以及如何在SpringBoot项目中配置和使用Redisson。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一  redission简介

1.1 redission介绍*

1.1.1 总体流程

1.1.2 详解流程

1.2 存在问题以及解决版本*

1.2.1 死锁问题

1.2.2 业务未结束,锁自动释放问题

1.2.3 互斥问题

1.2.4 可重入和误删除

1.3 redission封装实现特性

1.3.1 redisson封装实现的特性

二 redission常用子锁介绍

2.1 案例

2.1.1  工程结构

2.1.2 pom配置

2.1.3 redis的启动与参数设置

2.2  重入锁

2.2.1 重入锁

​编辑

2.2.2 重入锁操作案例

2.3 公平锁

2.3.1 公平锁

​编辑

2.3.2 公平锁操作案例

2.4 联锁

2.4.1 联锁

2.5 redLock 红锁

2.5.1 redlock 红锁

2.6 信号量

2.6.1 信号量

2.6.2 信号量操作案例

 2.7   闭锁

2.7.1 原理

2.7.2 操作案例

2.8 读写锁 

2.8.1 读写锁

三   Redission源码分析

3.1 关于 getLock("xxx")的作用

3.2 加锁操作

3.3 续锁功能

3.4 解锁功能

四  实操案例

4.1  案例操作

4.1.1 pom依赖配置

4.1.2 redisson配置文件

4.1.3  核心加锁代码

4.1.4  controller调用

4.1.5  验证调试


一  redission简介

1.1 redission介绍*

1.1.1 总体流程

Redisson分布式锁是一种基于redis实现的分布式锁,它利用redis的setnx命令实现分布式锁的互斥访问。同时还支持锁的自动续期功能,可以避免因为某个进程崩溃或者网络故障导致锁无法释放的情况。

只要线程一加锁成功,就会启动一个watchdog看门狗,它是一个后台线程,会每隔10秒检查一下,默认生存时间只有30秒如果线程A还持有锁,那么就会不断的延长锁key的生存时间。可以使用reentracklock,公平锁,读写锁,信号量,闭锁等锁进行加锁操作,完成后然后释放锁。其他线程BCD判断加锁的次数为0,就可以进行加锁操作。

因此,Redisson就是使用watch dog解决了「锁过期释放,业务没执行完问题。

1.1.2 详解流程

1.加锁机制:

一个客户端A要加锁,它首先会根据hash算法选择一台机器,这里注意,仅仅只是选择一台机器。紧接着就会发送一段lua脚本到redis上,比如加锁的那个锁key"mylock",并且设置的时间是30秒,30秒后,mylock锁就会被释放

2.锁互斥机制:

如果这个时候客户端B来加锁,它也会会根据hash算法选择一台机器,然后执行了同样的一段lua脚本。它首先回来判断"exists mylock"这个锁存在吗?如果存在,则B会获得一个数字,这个数字就是mylock这个锁的剩余生存时间,此时客户端B就会进入到一个while循环,不停的尝试加锁

3.watch dog自动延期机制:

客户端A加锁的锁key默认生存时间只有30秒,如果超过了30秒,客户端A还想一直持有这把锁,怎么办?其实只要客户端A一旦加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间

4.可重入加锁机制:

当客户端A获得mylock锁时,里面会有一个hash结构的数据:

mylock:{"8743c9c0-4907-0795-87fd-6c719a6b4586:1":1}

"8743c9c0-4907-0795-87fd-6c719a6b4586:1"就是代表客户端A,数字1就是代表这个客户端加锁了一次如果客户端A还想要持有锁mylock,那么这个1就会变成2,依次累加

5.释放锁机制:

如果发现加锁次数变为0了,那么说明这个客户端不再持有锁了,客户端B就可以加锁了https://huaweicloud.youkuaiyun.com/637eeee1df016f70ae4c9eac.html

redisson最强大的地方就是提供了分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多线程并发系统的能力,降低了程序员在分布式环境下解决分布式问题的难度。

1.2 存在问题以及解决版本*

1.2.1 死锁问题

如果使用这个分布式锁的Redisson节点实例的客户端宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。

解决办法: 加上超时时间约束,超过阈值,自动释放。Redission可以通过加锁方法提供leaseTime的参数来指定加锁的时间,超过这个时间后锁自动解开。从而避免锁一直被占用而导致的死锁问题。

1.2.2 业务未结束,锁自动释放问题

解决办法:为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。确保在业务未完成前,锁不会被其他线程获取。

watch-dog作用: 在获取锁成功后,给锁加一个watchdog,watch dog 会起一个定时任务,在锁没有释放且快要过期的时候进行续期。

1.2.3 互斥问题

在redis集群主从模式下,通过基于故障转移无法真正实现RedLock功能。

因为redis在进行主从复制时是异步完成的,比如在clientA获取锁后,主redis复制数据到从redis过程中崩溃了,导致没有复制到从redis中,然后从redis选举出一个升级为主redis,造成新的主redis中没有clientA上锁的信息,恰好这是clientB尝试获取到了锁,并且能够成功获取锁,导致互斥特性失效。

解决办法:Redission分布式锁支持MultiLock机制,可以将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁。能够保证:

1.互斥,任何时候只能有一个client获取数据。

2.释放死锁:即使锁定资源的服务崩溃或者分区,仍然能释放锁。

3.容错性:只要多说redis节点(一半以上)在使用,client就可以获取和释放锁。

1.2.4 可重入和误删除

Redisson 在为每个锁关联一个线程 ID 和重入次数(递增计数器)作为分布锁 value 的一部分存储在 Redis 中,这样就避免了锁误删和不可重入的问题。

1.3 redission封装实现特性

1.3.1 redisson封装实现的特性

1.Redisson 可以设置分布式锁的过期时间,从而避免锁一直被占用而导致的死锁问题。

2.Redisson 在为每个锁关联一个线程 ID 和重入次数(递增计数器)作为分布锁 value 的一部分存储在 Redis 中,这样就避免了锁误删和不可重入的问题。

3.Redisson 还提供了自动续期的功能,通过定时任务(看门狗)定期延长锁的有效期确保在业务未完成前,锁不会被其他线程获取。

如何使用Redisson实现分布式锁?-腾讯云开发者社区-腾讯云

二 redission常用子锁介绍

2.1 案例

2.1.1  工程结构

2.1.2 pom配置

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atguigu</groupId>
    <artifactId>distributed-lock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>distributed-lock</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--SpringBoot与Redis整合依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- redisson 分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.11.2</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.5</version>
            </plugin>
        </plugins>
    </build>
 
</project>

2.1.3 redis的启动与参数设置

2.2  重入锁

2.2.1 重入锁

基于Redis的Redisson分布式可重入锁RLock, Java对象实现了java.util.concurrent.locks.Lock接口。 Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

2.2.2 重入锁操作案例

1.代码截图

2.核心代码

 @GetMapping("stock/rt")
    public String decuceRt(){
        RLock lock=this.redissonClient.getLock("my-rt");
        lock.lock();
        try {
            //1.查询库存信息
            String stock = redisTemplate.opsForValue().get("stock").toString();
            // 2.判断库存是否充足
            if (stock != null && stock.length() > 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3.减库存操作
                    redisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
        }
        finally {
            lock.unlock();
        }
       return "ok!!!";
    }

 3.测试

4.查看redis

127.0.0.1:6379> get stock
"\"49\""

2.3 公平锁

2.3.1 公平锁

它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒。接口API如下:

2.3.2 公平锁操作案例

1.核心代码

2.代码

@GetMapping("stock/fair")
    public String decuceFairLock(){
        RLock lock=this.redissonClient.getFairLock("myFairLock");
        lock.lock();
        try {
            //1.查询库存信息
            String stock = redisTemplate.opsForValue().get("stockfair").toString();
            // 2.判断库存是否充足
            if (stock != null && stock.length() > 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3.减库存操作
                    redisTemplate.opsForValue().set("stockfair", String.valueOf(--st));
                }
            }
            try {
                TimeUnit.MILLISECONDS.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        finally {
            lock.unlock();
        }
        return "ok123!!!";
    }

3.设置redis
127.0.0.1:6379> get stockfair 
"100"
 
4.访问

 5.查看

127.0.0.1:6379> get stockfair 
"\"99\""
127.0.0.1:6379> 

2.4 联锁

2.4.1 联锁

基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。要求每一个节点都得满足。则获取锁成功

2.5 redLock 红锁

2.5.1 redlock 红锁

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。大部分节点满足则获取锁成功

2.6 信号量

2.6.1 信号量

基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。同时还提供了异步(Async)反射式(Reactive)RxJava2标准的接口。

2.6.2 信号量操作案例

1.代码

@GetMapping("stock/sp")
    public String testsp(){
        RSemaphore lock=this.redissonClient.getSemaphore("sp");
        lock.trySetPermits(3);
        try {
            try {
                lock.acquire();
                this.redisTemplate.opsForList().rightPush("log","10086开始处理业务了"+Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(10+new Random().nextInt(10));
                this.redisTemplate.opsForList().rightPush("log","10086处理完成业务逻辑,释放资源======"+Thread.currentThread().getName());
                   lock.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        finally {
 
        }
        return "信号量!!!";
    }

2.测试

 3.查看redis

 2.7   闭锁

2.7.1 原理

基于 Redisson 的 Redisson 分布式闭锁( CountDownLatch ) Java 对象 RCountDownLatch 采用了与java.util.concurrent.CountDownLatch 相似的接口和用法。

 

2.7.2 操作案例

1.代码截图

2.核心代码

@GetMapping("stock/suo")
    public String testsuo(){
        RCountDownLatch cd1=this.redissonClient.getCountDownLatch("cd1");
        cd1.trySetCount(3);
        System.out.println("进入 lock 之中喽。。。。。。");
        try {
          cd1.await();
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return "cd lock!!!";
    }
    @GetMapping("stock/sf")
    public String testsf(){
        RCountDownLatch cd1=this.redissonClient.getCountDownLatch("cd1");
        try {
               cd1.countDown();
            System.out.println("执行 countdown---。。。。。");
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return "cd unlock!!!";
    }

3.测试

这个接口请求3次,countdown变为0

 这个接口才能请求完成。

2.8 读写锁 

2.8.1 读写锁

分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

2.特点:

同时访问写:一个写完,等待一会,另外一个写开始。

同时访问读:不用等待

先写后读:读要等待写完成后执行。

先读后写:写要等待读完成后执行。

三   Redission源码分析

3.1 关于 getLock("xxx")的作用

1.在getLock方法中设定锁的资源名称rLock,进行初始化锁名称

2.间接在RedissonLock构造函数资源名称赋值给成员变量entryName;同时父类构造函数super(xxx,name);

3.查看父类RedissonExpirable 的构造函数,调用其父类的super函数

4.查看:在RedissonObject类中,通过构造函数赋值给成员变量 name

3.2 加锁操作

1.加锁操作

总流程:通过exits判断,如果锁不存在,则设置值和过期时间,加锁成功。

        通过hexits判断,如果锁已经存在,摈弃锁的是当前线程,则证明是重入锁,加锁成功;

       如果锁已存在,但锁的不是当前线程,则证明其它线程持有锁,返回当前锁的过期时间(代表了锁key的剩余生存时间),加锁失败。

2.加锁操作使用lua脚本保证原子性

 如果锁不存在,则通过hset设置它的值,并设置过期时间。

如果锁存在,并且锁的是当前线程,则通过hincryby给它递增加1;

如果锁存在,并且不是当前线程的,则返回剩余过期时间ttl。

3.3 续锁功能

1.锁续期的方法

2.锁续期逻辑:

这里在初始化RedissionLock的构造函数时,初始化InternalLockLeaseTime的值,默认为30s,也就是每隔10s进行续锁一次。可以理解为delay的值为InternalLockLeaseTime/3;

3.4 解锁功能

1.清除:CHM中缓存数据

2.解锁逻辑

四  实操案例

4.1  案例操作

4.1.1 pom依赖配置

     <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.11.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.7.1</version>
        </dependency>

4.1.2 redisson配置文件

package com.atguigu.distributed.lock.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName: RedissonConfig
 * @Description: TODO
 * @Author: admin
 * @Date: 2024/01/14 23:18:50 
 * @Version: V1.0
 **/
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        // 可以用"rediss://"来启用SSL连接
     //   config.useSingleServer().setAddress("redis://192.168.43.4:6379");
        config.useSingleServer().setAddress("redis://192.168.43.4:6379").setPassword("123456");
        return Redisson.create(config);
    }
}

4.1.3  核心加锁代码

    @Autowired
    private RedissonClient redissonClient;

    public void checkAndLockByRedisson() {
        // 加锁,获取锁失败重试
        RLock lock = this.redissonClient.getLock("rlock");
        lock.lock();
        System.out.println("redisson的锁....");
        // 先查询库存是否充足
        Stock stock = this.stockMapper.selectById(1L);
        // 再减库存
        if (stock != null && stock.getCount() > 0){
            stock.setCount(stock.getCount() - 1);
            this.stockMapper.updateById(stock);
        }
        // 释放锁
        lock.unlock();
    }

4.1.4  controller调用

4.1.5  验证调试

 1.启动服务

2.启动redis

[root@localhost export]# redis-server /myredis/redis.conf
[root@localhost export]# redis-cli -a 123456 -p 6379

3.启动zk

[root@localhost export]# cd apache-zookeeper-3.7.0-bin/
[root@localhost apache-zookeeper-3.7.0-bin]# cd bin
[root@localhost bin]# ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /root/export/apache-zookeeper-3.7.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

4.启动nginx

5.启动jemter

6.查看结果

2.查看结果

7.查看db结果

测试前

测试后

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值