Redisson学习教程(B站诸葛)

在这里插入图片描述


  • 弱智级别
package org.example.controller;

public class IndexController {
    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_storck")
    public String deductStock() {
        String lockKey = "product:1001";


        //再redis里排队的只有第一个请求能执行成功
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
        //相当于jedis.setnx(k,v)  如果这个值不存在,我就设置这个key
        //result代表执行成功,原来没有这个key,如果这个值是false 代表这个key存在
        if (!result) {
            return "error";
        }
        if (stock > 0) {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("扣减成功 ");
        } else {
            System.out.println("扣减失败,库存不足 ");
        }
        stringRedisTemplate.delete("lockKey");
    }
}

在这里插入图片描述
如果扣减库存的失效,那么就出问题了,下面就无法执行,锁无法释放,导致报错,这个商品id就一直被锁着 了

  stringRedisTemplate.delete("lockKey");

因为服务器扣减库存抛异常,改进一版

package org.example.controller;

public class IndexController {
    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_storck")
    public String deductStock() {
        String lockKey = "product:1001";


        //再redis里排队的只有第一个请求能执行成功
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
        //相当于jedis.setnx(k,v)  如果这个值不存在,我就设置这个key
        //result代表执行成功,原来没有这个key,如果这个值是false 代表这个key存在
        if (!result) {
            return "error";
        }
        
        try{     
		    int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            System.out.println("扣减成功 ");
            } else {
                System.out.println("扣减失败,库存不足 ");
            }
        }finally {
            stringRedisTemplate.delete("lockKey");
        }
    }
}

在关键的代码块加了

try{} finally {}

  • 改进二版:
  • 如果这个服务器宕机了,在扣减库存的时候,这个时候你应该怎么办?????finally 无法执行,是不是又导致死锁了。
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
        if (!result) {
            return "error";
        }

解决方案: 加过期的时间


第三版:如果枷锁之后,系统宕机, 还是导致死锁,所以要保证原子性:

//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
//        stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge", 10, TimeUnit.SECONDS);

原子性:保证了同时执行,一行代码,注意方法的参数;

  • 高并发下出现的问题:假如执行这个请求执行完成1500ms , 过期时间1000ms, 也就是说第一个请求还没执行完成,就已经过期了,然而此时第二个请求过来了,加入执行时间800ms 扣减库存占用500ms, 第二个请求本来是加锁了,但是第一个因为提前过期而没有手动释放锁,导致你现在释放了第二个请求过来的锁。这样加的锁就无效了。然后导致超卖问题,就是你库存100卖了1000的货。

问题的根本原因:我加的锁被别人删除了, 怎么解决???

package org.example.controller;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class IndexController {
    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_storck")
    public String deductStock() {
        String lockKey = "product:1001";
        String clientId = UUID.randomUUID().toString();
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientId, 10, TimeUnit.SECONDS);

        if (!result) {
            return "error";
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功 ");
            } else {
                System.out.println("扣减失败,库存不足 ");
            }
        } finally {
            if (clientId.equals(stringRedisTemplate.opsForValue().get("lockKey"))) {
                stringRedisTemplate.delete("lockKey");
            }
        }
    }
}

在这里插入图片描述
他在加锁哪里,对锁进行了一个id的匹配


关于锁续命:
天空一声雷响,主角登场,文章属于纸上谈兵,目前没有实战

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>

1、单机模式useSingleServer,可以变

@Configuration
public class RedissonConfig {
 
    @Bean
    public RedissonClient redissonClient(){
        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.150.101:6379")
            .setPassword("123321");
        // 创建RedissonClient对象
        return Redisson.create(config);
    }
}

也可以

package org.example;

import jdk.nashorn.internal.runtime.regexp.joni.Config;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
        System.out.println("程序启动");
    }

    @Bean
    public RedissonClient redissonClient() {
        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.150.101:6379")
                .setPassword("123321");
        // 创建RedissonClient对象
        return Redisson.create(config);
    }
}

改进版

    public String deductStock() {
//        String lockKey = "product:1001";
//        String clientId = UUID.randomUUID().toString();
//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientId, 10, TimeUnit.SECONDS);
//
//        if (!result) {
//            return "error";
//        }
        RLock redissonLock = redisson.getLock("lockKey");
        redissonLock.lock();

        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功 ");
            } else {
                System.out.println("扣减失败,库存不足 ");
            }
        } finally {
            redissonLock.unlock();
/*            if (clientId.equals(stringRedisTemplate.opsForValue().get("lockKey"))) {
                stringRedisTemplate.delete("lockKey");
            }*/
        }
    }

视频最后的效果

package com.example.demo.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;

public class IndexController {
    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_storck")
    public String deductStock() {
        RLock redissonLock = redisson.getLock("lockKey");
        redissonLock.lock();
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功 ");
            } else {
                System.out.println("扣减失败,库存不足 ");
            }
        } finally {
            redissonLock.unlock();
        }
        
        return "deductStock";
    }
}


        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.31.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

分别对应依赖

    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
  • 为什么要用分布式锁,用一般的锁不行吗,分布式锁的应用场景是什么?使用 分布式锁 的主要原因是要在多个不同的 机器进程 之间进行资源的共享和同步。在分布式系统中,应用可能在多个节点上运行,而普通的 本地锁(如 Java 中的 synchronizedReentrantLock)只能在同一个 JVM(Java Virtual Machine)进程内有效,无法跨进程、跨机器共享和同步。

1. 为什么普通锁不行

普通锁(如 synchronizedReentrantLock)是 局部锁,只能在同一个 进程线程 内部工作。当应用部署在多个机器或容器上时,普通锁就无法保证跨节点的同步与协调。比如:

  • 如果多个服务实例或容器在不同的机器上运行,它们之间无法共享本地的锁。
  • 如果不同服务实例同时访问某个共享资源(如数据库、缓存、文件等),就可能导致资源竞争,产生 并发冲突数据不一致

这时就需要 分布式锁 来保证多个分布式系统节点在操作共享资源时,能够遵循某种约定进行同步,避免数据竞争。

2. 分布式锁的应用场景

分布式锁主要用于以下几个典型的场景:

1. 多个服务实例竞争共享资源

在微服务架构或分布式系统中,通常会有多个服务实例运行在不同的机器上。当这些服务需要对一个共享资源(如数据库中的某个数据、缓存、文件等)进行操作时,需要避免同时进行修改,防止数据不一致或者产生竞态条件。这时分布式锁就可以确保在同一时刻只有一个服务实例在访问这个共享资源。

场景示例

  • 订单系统:多个微服务实例可能同时收到用户支付请求,如果没有分布式锁,可能会导致重复创建订单或者扣款。
  • 限流:多个服务实例需要共同控制某个资源的访问量,比如访问次数、流量限制等,可以通过分布式锁确保同一时刻只有一个服务实例在执行限流操作。
2. 防止重复任务的执行

在分布式系统中,如果没有协调机制,可能会出现多个节点同时执行同一个任务的情况。通过分布式锁,可以确保在同一时刻只有一个节点能够执行某个任务,避免任务重复执行。

场景示例

  • 定时任务:比如数据清理任务、缓存刷新任务等,如果有多个节点同时执行相同的定时任务,可能会导致资源浪费或者数据重复处理。使用分布式锁可以确保任务只在一个节点上执行。
  • 唯一性任务:比如生成唯一的 ID 或令牌时,通过分布式锁可以保证 ID 生成过程的唯一性,防止多个节点生成相同的 ID。
3. 协调跨节点的操作

在一些需要跨节点协同的场景中,多个节点可能需要顺序地进行某个操作。分布式锁可以确保这些操作按照特定的顺序执行,而不会发生并发冲突。

场景示例

  • 分布式事务:在分布式数据库或多服务之间的事务处理中,如果不同的节点需要修改同一份资源,分布式锁可以帮助确保事务的一致性和顺序性。
  • 唯一任务处理:比如某个任务只应该被一个节点处理,其他节点需要等待。
4. 资源的有限访问控制

在某些情况下,某些资源(如 API 请求、数据库连接池等)是有限的,多个节点可能会争抢访问这些资源。使用分布式锁可以控制资源的访问顺序,确保资源不会被过度占用。

场景示例

  • 限制高频访问:如果你有一个高频率的 API 或数据库接口,分布式锁可以确保不会有多个服务实例同时发送请求。
  • 限制并发请求:例如,某些操作需要保证最大并发数,分布式锁可以用于控制并发请求的数量,确保系统不会过载。

3. 分布式锁的实现方式

分布式锁的实现方式有多种,常见的包括:

  • Redis:通过 Redis 提供的 SETNX 命令或者 Redis 的 RedLock 算法实现分布式锁。
  • ZooKeeper:利用 ZooKeeper 的临时节点来实现分布式锁,ZooKeeper 本身就是为了协调分布式系统而设计的。
  • Etcd:类似于 ZooKeeper,Etcd 也可以用来实现分布式锁,通常用于 Kubernetes 等分布式系统中。
  • 数据库:通过数据库的行锁或其他机制实现,但效率相对较低,适合不频繁需要获取锁的场景。

4. 分布式锁的挑战

分布式锁虽然可以解决跨节点并发问题,但在实现和使用时也有一些挑战:

  • 死锁:分布式系统中的锁可能因网络延迟或节点故障等原因导致死锁,需要特别小心管理锁的获取和释放。
  • 性能问题:频繁获取和释放分布式锁会增加系统的延迟,可能成为瓶颈。
  • 过期时间管理:如果分布式锁没有合理的过期时间控制,可能会导致锁无法及时释放,影响系统的可用性。
  • 容错性:分布式锁需要具备容错机制,防止由于单点故障或网络问题导致锁失效。

总结

  • 分布式锁 是解决跨进程、跨机器资源同步与竞争的一个重要工具,适用于分布式系统、微服务架构中需要对共享资源进行安全访问的场景。
  • 普通的 本地锁 只能在同一进程内使用,无法满足跨机器同步的需求,而分布式锁则能有效地协调多个节点之间的资源访问。
  • 常见的应用场景包括 共享资源竞争、任务去重、跨节点协同工作、资源访问控制等,但也需要注意合理管理锁的获取和释放,避免性能问题和死锁。

是不是最好用分布式锁,尽量避免使用Synchronized,Reentrantlock?????????????
并不是所有场景下都需要使用 分布式锁,也不能说一定要避免使用 synchronizedReentrantLock。实际上,这两者(synchronizedReentrantLock)和 分布式锁 各自有其适用的场景,使用时要根据具体的需求和系统架构来选择。

1. synchronizedReentrantLock 的适用场景

1.1 本地锁的优势
  • 简单高效synchronizedReentrantLock 是 Java 内建的本地锁机制,它们的实现非常简单,开销低,并且效率高。它们适用于 单机应用,即只有一个 JVM 实例运行的情况。这种情况下,不需要复杂的分布式协调,使用本地锁完全可以满足并发控制的需求。

  • 易于使用

    • synchronized 是 Java 的语言级别的关键字,使用起来简洁直接,不需要额外的依赖。
    • ReentrantLock 提供了比 synchronized 更加灵活的功能,比如手动加锁、解锁、可重入、可中断锁等特性。
1.2 使用场景:
  • 多线程环境下的同步:当多个线程需要对同一个对象的资源进行并发操作时(如多个线程修改同一内存中的数据结构),synchronizedReentrantLock 是常用的本地锁,可以确保数据的一致性和完整性。
  • 在单台机器上运行:如果你的应用只部署在单台服务器上,且所有线程都运行在同一 JVM 进程内,那么本地锁是最合适的选择。

2. 分布式锁的必要性

分布式锁解决的主要问题是 跨进程、跨服务器、跨机器的同步问题。当你的应用是分布式的,或者在多个节点上运行时,就需要引入 分布式锁 来确保资源的共享和同步。

2.1 分布式锁的应用场景:
  • 多实例共享资源:当应用分布在多台服务器上,多个实例之间可能同时需要访问或修改某个共享的资源(如数据库记录、缓存、文件等),此时需要使用分布式锁来避免资源竞争和数据不一致。
  • 跨进程协调:分布式锁通常被用来解决不同进程或服务实例之间的资源竞争问题。
  • 确保唯一性和顺序性:分布式锁可以保证某些任务或操作只在一个节点上执行,避免重复执行任务或保证某些操作的顺序性(如定时任务、消息消费、数据库记录生成等)。
2.2 典型的分布式锁技术:
  • Redis:Redis 提供了强大的分布式锁功能,如基于 SETNXRedLock 算法等实现的锁,适合需要高性能和高可用性的分布式环境。
  • ZooKeeper:通过 ZooKeeper 的临时节点来实现分布式锁,适合需要强一致性和可靠性的分布式系统。
  • Etcd:类似 ZooKeeper,提供了强一致性保障,可以用来实现分布式锁。

3. 何时使用本地锁?

  • 应用只在单一 JVM 运行时:如果你的应用只运行在一台机器上,即使是多线程环境,也不需要考虑跨进程或跨机器的同步问题,那么使用本地锁(synchronizedReentrantLock)会更加简单和高效。
  • 性能要求较高:本地锁没有分布式锁的网络通信开销,通常会比分布式锁更高效。如果在单台机器上的并发控制足够满足需求,使用本地锁可以避免额外的分布式协调。

4. 何时使用分布式锁?

  • 应用部署在多台机器/多个服务实例中:当你的应用是分布式的,可能有多个服务实例或多个 JVM 节点在运行时,需要使用分布式锁来确保跨节点的资源访问同步。
  • 需要协调多个进程/服务实例之间的操作:如多个服务实例同时访问一个共享数据库表,或者多个应用实例处理相同的任务。
  • 避免重复执行任务:比如在分布式系统中有一些定时任务(例如,定时清理数据、批量任务等),分布式锁可以确保这些任务不会被多个服务实例同时执行。

5. 本地锁和分布式锁的权衡

本地锁(synchronized / ReentrantLock):
  • 优点
    • 使用简单。
    • 无网络延迟,性能高效。
    • 避免了分布式系统中的复杂性。
  • 缺点
    • 只能在同一 JVM 中有效,无法跨多个进程或机器同步。
分布式锁:
  • 优点
    • 可以在多个进程或服务实例间同步共享资源,保证数据一致性。
    • 适用于分布式环境中需要跨进程或跨服务进行同步的场景。
  • 缺点
    • 相对复杂,需要维护分布式系统的可靠性和一致性。
    • 存在性能开销(如网络延迟、锁争用等问题)。
    • 容易受到网络故障和服务宕机的影响。

6. 结论:两者的选择

  • 单机应用或单一 JVM 环境:如果你只在一个 JVM 进程中运行应用,synchronizedReentrantLock 是最好的选择,简洁高效,性能高。
  • 分布式应用或多节点环境:如果你的应用部署在多台机器、多个服务实例上,必须保证跨进程、跨机器的同步操作,那么就需要使用 分布式锁

总之,不同场景下使用不同的锁机制,不是越复杂的锁机制越好,而是要根据系统架构和业务需求来选择合适的同步方案。在 分布式系统 中, 分布式锁 是必不可少的,而在单一 JVM 中, 本地锁 是更为合适和高效的选择。

以下实现原理可以不看,B站连接 分布式锁教程



源码分析

   <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return this.evalWriteSyncedAsync(this.getRawName(), LongCodec.INSTANCE, command,
         "if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", 
         Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)});
    }

这个方法 tryLockInnerAsync 是用来异步地尝试获取分布式锁的。它的作用是在 Redis 中执行一个 Lua 脚本来尝试获取一个锁,并返回一个 RFuture<T> 对象,代表一个异步操作的结果。具体来说,方法执行了一个 Redis 脚本来检查锁的状态,并设置过期时间和线程标识符。

方法解析

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command)
  • waitTime: 等待锁的最大时间(即最大尝试获取锁的时间)。
  • leaseTime: 锁的持有时间(即锁的过期时间)。
  • unit: 时间单位(如秒、毫秒等)。
  • threadId: 当前线程的标识符,用于标识锁的持有者。
  • command: 执行 Redis 命令的对象。

代码解析

return this.evalWriteSyncedAsync(
    this.getRawName(), 
    LongCodec.INSTANCE, 
    command, 
    "if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
    "redis.call('pexpire', KEYS[1], ARGV[1]); return nil; " +
    "end; return redis.call('pttl', KEYS[1]);", 
    Collections.singletonList(this.getRawName()), 
    new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)}
);

关键部分分析:

  1. evalWriteSyncedAsync:

    • evalWriteSyncedAsync 是一个异步执行 Redis Lua 脚本的方法。它会执行一个 Lua 脚本来进行 Redis 数据的操作。
    • this.getRawName() 获取 Redis 锁的键名。
    • LongCodec.INSTANCE 是用于处理返回结果的编解码器,表示返回类型是一个 Long 类型。
    • command 是 Redis 命令,用于进一步操作。
  2. Lua 脚本

    if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then
        redis.call('hincrby', KEYS[1], ARGV[2], 1);
        redis.call('pexpire', KEYS[1], ARGV[1]);
        return nil;
    end;
    return redis.call('pttl', KEYS[1]);
    

    这段 Lua 脚本执行以下操作:

    • 检查锁是否存在
      • redis.call('exists', KEYS[1]) == 0: 检查锁是否存在(通过 Redis 键检查)。
      • redis.call('hexists', KEYS[1], ARGV[2]) == 1: 检查是否已经存在一个由 threadId 标识的锁。
    • 如果锁不存在或线程ID已经持有锁
      • redis.call('hincrby', KEYS[1], ARGV[2], 1): 增加该锁的计数,表示线程成功获得了锁。
      • redis.call('pexpire', KEYS[1], ARGV[1]): 设置锁的过期时间,ARGV[1] 是通过 unit.toMillis(leaseTime) 计算出的过期时间,单位是毫秒。
      • return nil: 如果获取锁成功,返回 nil
    • 如果锁存在且未获得,返回当前锁的剩余存活时间:
      • return redis.call('pttl', KEYS[1]): 返回当前锁的剩余过期时间,单位是毫秒。
  3. Collections.singletonList(this.getRawName()):

    • 传递锁的键名,这里 this.getRawName() 是锁的 Redis 键。
  4. new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)}:

    • ARGV[1] 是锁的过期时间(以毫秒为单位)。
    • ARGV[2] 是线程的标识符,用来区分不同线程的锁。

总结

这个方法的目的是在 Redis 中异步地尝试获取一个分布式锁。它通过执行一个 Lua 脚本来检查锁是否已经存在,如果锁不存在或者当前线程已经持有锁,它会将锁的计数加 1,并设置过期时间。如果锁已经被其他线程持有,脚本将返回锁的剩余存活时间。

RFuture<T> 是一种异步结果的返回类型,表示锁的获取过程会异步执行,调用者可以通过它来获取操作结果。

希望这解释能够帮助你更好地理解代码的功能和实现方式!

    private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture acquiredFuture;
        if (leaseTime > 0L) {
            acquiredFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        } else {
            acquiredFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        }

        CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);
        CompletionStage<Boolean> f = acquiredFuture.thenApply((acquired) -> {
            if (acquired) {
                if (leaseTime > 0L) {
                    this.internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    this.scheduleExpirationRenewal(threadId);
                }
            }

            return acquired;
        });
        return new CompletableFutureWrapper(f);
    }

解释
这个方法 tryAcquireOnceAsync 是用来异步地尝试获取分布式锁的,并返回一个 RFuture<Boolean>,表示是否成功获取到锁。这个方法的核心功能是调用 tryLockInnerAsync 来请求锁,并根据是否成功获取锁来做进一步的处理。

代码解析

private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture acquiredFuture;
    if (leaseTime > 0L) {
        acquiredFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    } else {
        acquiredFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    }

    CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);
    CompletionStage<Boolean> f = acquiredFuture.thenApply((acquired) -> {
        if (acquired) {
            if (leaseTime > 0L) {
                this.internalLockLeaseTime = unit.toMillis(leaseTime);
            } else {
                this.scheduleExpirationRenewal(threadId);
            }
        }

        return acquired;
    });
    return new CompletableFutureWrapper(f);
}

关键部分分析:

1. tryLockInnerAsync 调用
if (leaseTime > 0L) {
    acquiredFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
} else {
    acquiredFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
  • tryLockInnerAsync: 这个方法是执行 Redis Lua 脚本来尝试获取锁的异步方法。这里根据 leaseTime 的值选择不同的超时设置:
    • 如果 leaseTime > 0L,则使用提供的 leaseTimeunit 来设置锁的持有时间。
    • 如果 leaseTime <= 0L,则使用 internalLockLeaseTime(可能是类的一个字段,代表默认的锁持有时间)。
2. handleNoSync 处理
CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);
  • handleNoSync 方法通常用来处理异步操作的后续逻辑,确保它不会同步阻塞线程。
  • 它可能是为了解决某些同步问题,比如锁在获取时可能需要做一些额外的处理。
3. thenApply 对锁获取结果的处理
CompletionStage<Boolean> f = acquiredFuture.thenApply((acquired) -> {
    if (acquired) {
        if (leaseTime > 0L) {
            this.internalLockLeaseTime = unit.toMillis(leaseTime);
        } else {
            this.scheduleExpirationRenewal(threadId);
        }
    }

    return acquired;
});
  • thenApply: 对 acquiredFuture 结果进行进一步处理。acquired 表示是否成功获取到锁。
    • 如果获取到锁:
      • 如果传入了正的 leaseTime,更新类中的 internalLockLeaseTime 字段。
      • 如果没有传入 leaseTime(即 leaseTime <= 0L),则调用 scheduleExpirationRenewal(threadId) 来安排续期操作,这意味着锁会在过期之前自动续期,保持锁的有效性。
4. 返回 CompletableFutureWrapper
return new CompletableFutureWrapper(f);
  • 最后,CompletableFutureWrapper 是对 CompletionStage<Boolean> 的包装,返回一个 RFuture<Boolean>。这确保了调用者能够异步地获取锁是否成功的结果。

总结:

  1. 获取锁的逻辑

    • 通过调用 tryLockInnerAsync 异步地尝试获取分布式锁。如果传入了 leaseTime,则使用提供的时间;否则,使用默认的锁持有时间(internalLockLeaseTime)。
  2. 异步处理结果

    • handleNoSync 处理异步结果,确保后续的操作不阻塞当前线程。
    • 使用 thenApply 处理锁获取成功后的逻辑,例如更新锁持有时间或安排锁续期。
  3. 返回结果

    • 最终通过 CompletableFutureWrapper 包装一个 CompletionStage<Boolean> 返回给调用者。

这个方法的设计是为了支持异步地获取分布式锁,并在获取锁后进行一些额外的处理,如设置锁的过期时间或安排续期。

可能的改进或问题:

  1. 变量重名问题
    在代码中,有一行声明:

    CompletionStage<Boolean> acquiredFuture = this.handleNoSync(threadId, acquiredFuture);
    

    这里的 acquiredFuture 是重复定义的,实际上应该避免在同一作用域中重名。你可以修改成:

    CompletionStage<Boolean> future = this.handleNoSync(threadId, acquiredFuture);
    
  2. 异常处理
    异常处理在这个方法中没有体现,如果在异步操作过程中发生错误,应该通过 .exceptionally 或其他手段进行捕获和处理,防止异步操作失败时没有任何反馈。

希望这些解释有助于你理解 tryAcquireOnceAsync 方法的实现!如果有更多问题,欢迎继续讨论。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024信创数据库TOP30之蚂蚁集团OceanBase

数据库作为存储、管理和分析这些数据的关键工具&#xff0c;其地位自然不言而喻。随着信息技术的日新月异&#xff0c;数据库技术也在不断演进&#xff0c;以满足日益复杂多变的市场需求。近日&#xff0c;备受瞩目的“2024信创数据库TOP30”榜单由DBC联合CIW/CIS权威发布&…

基于Java后台实现百度、高德和WGS84坐标的转换实战

目录 前言 一、需求的缘由 1、百度坐标拾取 2、高德坐标拾取 3、不同地图的坐标展示 二、后端坐标偏移转换处理 1、相关类库介绍 2、coordtransorm类图介绍 3、后台实际转换 三、总结 前言 在当今数字化时代&#xff0c;地理位置信息的精确性和实时性对于各种应用至…

Wireshark抓取HTTPS流量技巧

一、工具准备 首先安装wireshark工具&#xff0c;官方链接&#xff1a;Wireshark Go Deep 二、环境变量配置 TLS 加密的核心是会话密钥。这些密钥由客户端和服务器协商生成&#xff0c;用于对通信流量进行对称加密。如果能通过 SSL/TLS 日志文件&#xff08;例如包含密钥的…

网络安全,文明上网(6)网安相关法律

列举 1. 《中华人民共和国网络安全法》&#xff1a; - 这是中国网络安全的基本法律&#xff0c;于2017年6月1日开始实施。该法律明确了网络运营者的安全保护义务&#xff0c;包括采取数据分类、重要数据备份和加密等措施。 2. 《中华人民共和国数据安全法》&#xff1a; …

111 - Lecture 10

File I/O and GUI with JavaFX 文件输入/输出与JavaFX图形用户界面 一、Overview 1. File I/O &#xff08;1&#xff09; learning Java File I/O mechanism &#xff08;2&#xff09; writing into and reading from a file 使用文件I/O进行数据读取和…

【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍

文章目录 前言一、Zachman架构二、ToGAF架构三、FEA架构四、DoDAF 前言 企业架构&#xff08;Enterprise Architecture&#xff0c;EA&#xff09;是指企业在信息技术和业务流程方面的整体设计和规划。 最近接触到“企业架构”这个概念&#xff0c;转念一想必定和我们软件架构…

vue3:使用插件递归组件

vue3:使用插件递归组件 首先安装插件 npm i unplugin-vue-define-optionsvite.config.ts 配置插件 // vite.config.ts// 引入 unplugin-vue-define-options import DefineOptions from "unplugin-vue-define-options"; export default defineConfig({// 注册插件 De…

开源远程桌面工具:RustDesk

在远程办公和远程学习日益普及的今天&#xff0c;我们经常需要远程访问办公电脑或帮助他人解决电脑问题。 市面上的远程控制软件要么收费昂贵&#xff0c;要么需要复杂的配置&#xff0c;更让人担心的是数据安全问题。 最近我发现了一款名为 RustDesk 的开源远程桌面工具&…

springboot整合hive

springboot整合hive pom.xml <?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.…

ChatGPT 与其他 AI 技术在短视频营销中的技术应用与协同策略

摘要&#xff1a; 本文深入探讨了 ChatGPT 及其他 AI 技术在短视频营销中的应用。从技术层面剖析了这些技术如何助力短视频内容创作、个性化推荐、用户互动以及营销效果评估等多方面&#xff0c;通过具体方法分析、数据引用与大模型工具介绍&#xff0c;旨在为短视频营销领域提…

国土安全部发布关键基础设施安全人工智能框架

美国国土安全部 (DHS) 发布建议&#xff0c;概述如何在关键基础设施中安全开发和部署人工智能 (AI)。 https://www.dhs.gov/news/2024/11/14/groundbreaking-framework-safe-and-secure-deployment-ai-critical-infrastructure 关键基础设施中人工智能的角色和职责框架 https:/…

xtu oj 前缀和

样例输入 3 3 2 1 2 3 1 2 3 2 1 2 3 -1 -2 3 3 -4 3 1 4 2 1 样例输出 0 3 3 解题思路&#xff1a;排序前缀和二分查找 因为数组b是可以插入到数组a任意位置&#xff0c;要想k最大&#xff0c;就要尽可能把b的小数插到a数组的前面。所以&#xff0c;用qsort方法对数组b进…

Nexus搭建go私有仓库,加速下载go依赖包

一、搭建go私库 本文我们梳理一下go依赖包的私库搭建以及使用。 它只分为proxy和group两种仓库&#xff0c;这一点和maven仓库有所不同。 1、创建Blob Stores 为了区分不同的私库依赖包&#xff0c;存储的位置分隔开。 2、新建go proxy官网 Remote storage&#xff1a;htt…

CPU算法分析LiteAIServer摄像机实时接入分析平台固废检测算法助力环保

随着城市化进程的加速和工业化发展的不断深入&#xff0c;固体废弃物的处理问题逐渐成为了一个全球性的挑战。传统的固废检测方法主要依赖于人工巡查和抽样检测&#xff0c;这种方式不仅效率低下&#xff0c;而且难以实现对固体废弃物的全面覆盖和实时监测。为了解决这一问题&a…

国内镜像android studio

Android SDK在线更新镜像服务器 1.中国科学院协会镜像站: 部分地区无法使用 ◦IPV4/IPV6: mirrors.opencas.cn 端口&#xff1a;80 ◦IPV4/IPV6: mirrors.opencas.org 端口&#xff1a;80 ◦IPV4/IPV6: mirrors.opencas.ac.cn 端口&#xff1a;80 2.上海GDG镜像服务器地址…

Ubuntu上安装MySQL并且实现远程登录

目录 下载网络工具 查看网络连接 更新系统软件包&#xff1b; 安装mysql数据库 查看mysql数据库状态 以数字ip形式显示mysql的监听状态。&#xff08;默认监听端口是3306&#xff09; 查看安装mysql数据库时系统创建的目录信息。 根据查询到的系统用户名以及随机密码&a…

从零开始认识显卡

显卡&#xff08;GPU&#xff0c;全称为Graphics Processing Unit&#xff09;&#xff0c;是电脑中专门负责图形处理的硬件组件。以下是从零开始认识显卡的简单介绍&#xff1a; 1. 显卡的基本组成 显卡通常由以下几个主要部分组成&#xff1a; GPU核心&#xff1a;显卡的“…

mac安装Pytest、Allure、brew

安装环境 安装pytest 命令 pip3 install pytest 安装allure 命令&#xff1a;brew install allure 好吧 那我们在安装allure之前 我们先安装brew 安装brew 去了官网复制了命令 还是无法下载 如果你们也和我一样可以用这个方法哦 使用国内的代码仓库来执行brew的安装脚本…

【Vue】 npm install amap-js-api-loader指南

前言 项目中的地图模块突然打不开了 正文 版本太低了&#xff0c;而且Vue项目就应该正经走项目流程啊喂&#xff01; npm i amap/amap-jsapi-loader --save 官方说这样执行完&#xff0c;就这结束啦&#xff01;它结束了&#xff0c;我还没有&#xff0c;不然不可能记录这篇文…

三极管工作原理,以及小电流,如何驱动大电流

因为研究【自动下载电路实现】&#xff0c;涉及到三极管内容&#xff0c;之前看过&#xff0c;现在回看之前的笔记&#xff0c;一点印象都没了&#xff0c;于是&#xff0c;想了个办法&#xff0c;记住它 个人联想&#xff0c;不喜绕道&#xff0c;只是帮助个人记忆的 标题也是…