ZooKeeper 实战(五) Curator实现分布式锁

news2025/2/24 23:50:41

文章目录

  • ZooKeeper 实战(五) Curator实现分布式锁
    • 1.简介
      • 1.1.分布式锁概念
      • 1.2.Curator 分布式锁的实现方式
      • 1.3.分布式锁接口
    • 2.准备工作
    • 3.分布式可重入锁
      • 3.1.锁对象
      • 3.2.非重入式抢占锁
        • 测试代码
        • 输出日志
      • 3.3.重入式抢占锁
        • 测试代码
        • 输出日志
    • 4.分布式非可重入锁
      • 4.1.锁对象
      • 4.2.重入式抢占锁
        • 测试代码
        • 输出日志
    • 5.分布式可重入读写锁
      • 5.1.锁对象
      • 5.2.读锁和写锁的竞争
        • 测试代码
        • 输出日志
    • 6.共享信号量
      • 6.1.锁对象
      • 6.2.信号量抢占
        • 测试代码
        • 输出日志
    • 7.多共享锁
      • 7.1.锁对象
      • 7.2.获取共享锁
        • 测试代码
        • 输出日志

ZooKeeper 实战(五) Curator实现分布式锁

1.简介

1.1.分布式锁概念

分布式锁是一种用于实现分布式系统中的同步机制的技术。它允许在多个进程或线程之间实现互斥访问共享资源,以避免并发访问时的数据不一致问题。分布式锁的主要目的是在分布式系统中提供类似于全局锁的效果,以确保在任何时刻只有一个进程或线程可以访问特定的资源。

zookeeper基于临时有序节点实现分布式锁。每个客户端对某个临界资源加锁时,在zookeeper上的与该临界资源对应的指定节点的目录下,生成一个唯一的临时有序节点。 判断是否获取锁的方式很简单,只需要判断临时有序节点中序号最小的那个是否由自身创建。 当释放锁的时候,只需将这个临时有序节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

1.2.Curator 分布式锁的实现方式

curator-recipes中实现的锁有五种:

  • Shared Reentrant Lock 分布式可重入锁
  • Shared Lock 分布式非可重入锁
  • Shared Reentrant Read Write Lock 可重入读写锁
  • Shared Semaphore 共享信号量
  • Multi Shared Lock 多共享锁

1.3.分布式锁接口

org.apache.curator.framework.recipes.locks.InterProcessLock 该接口为分布式锁的行为规范,定义了获取锁和释放锁方法。

public interface InterProcessLock
{
    /**
     * 阻塞获取锁(如果未获取到锁,则一直阻塞)
     */
    public void acquire() throws Exception;

    /**
     * 非阻塞获取锁
     *
     * @param time 超时时间
     * @param unit 时间单位
     * @return 如果在超时时间内获取到锁则返回true,否则返回false
     * @throws Exception ZK errors, connection interruptions
     */
    public boolean acquire(long time, TimeUnit unit) throws Exception;

    /**
     * 释放锁。每次获取锁之后必须调用该方法释放锁(acquire和release成对出现)
     */
    public void release() throws Exception;

    /**
     * 判断该线程是否获取到锁
     * @return true/false
     */
    boolean isAcquiredInThisProcess();
}

2.准备工作

首先,需要读者掌握 Ideal 同一项目启动多个Service的能力,详细教程可参考博主另一篇博客,博主创建了两个启动实例,一个端口号为8888,另一个9981,此处随意只要不与其他服务的端口号冲突即可。

在这里插入图片描述

另外,在启动项目之前,请根据先前所写的教程启动zookeeper的单机服务器 ,参考ZooKeeper 实战(一) 超详细的单机与集群部署教程,并创建一个存储数据节点:路径/ahao/data,数据内容40。(可参照如下操作,由于博主已经创建过,所以重新设置了一遍)

在这里插入图片描述

3.分布式可重入锁

可重入锁是一种自我保护的锁,允许同一进程或线程多次获得相同的锁,而不会造成死锁。

3.1.锁对象

类路径:org.apache.curator.framework.recipes.locks.InterProcessMutex

公开构造方法如下:

     /**
     * @param client 当前客户端实例
     * @param path   锁节点路径
     */
    public InterProcessMutex(CuratorFramework client, String path)
      
     /**
     * @param client 当前客户端实例
     * @param path   锁节点路径
     * @param driver 锁驱动实例(工具类,只要提供两个方法:#createsTheLock 创建锁节点,#getsTheLock 获取当前锁)
     */
    public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)

3.2.非重入式抢占锁

由于非重入式抢占锁的场景对于5种分布式锁实现方式均适用,并且测试场景均一样,所以本节介绍完非重入式抢占锁的测试场景之后,对应的另4种实现方式的该测试场景将不在赘述。

测试场景:有两台服务实例 C1,C2。C1和C2个有两个线程(线程池调度)并发执行,对同一共享资源(/ahao/data中的数据)进行减1 操作。

测试代码
/**
 * @Name: CuratorDemoApplication
 * @Description:
 * @Author: ahao
 * @Date: 2024/1/10 3:29 PM
 */
@Slf4j
@SpringBootApplication
public class CuratorDemoApplication implements ApplicationRunner{

    @Autowired
    private CuratorFramework client;

    public static void main(String[] args) {
        SpringApplication.run(CuratorDemoApplication.class,args);
    }

  	// 存储数据节点的路径
    final String dataPath = "/ahao/data";

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("。。。。。。。。。。。。。容器初始化完毕。。。。。。。。。。。。。。");
				// 锁节点路径
        String lockPath = "/ahao/lock";
        TimeUnit.SECONDS.sleep(3);

        // 创建可重入锁
        InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
      
      	// 保证并发执行,当前时间的秒针部分为0则结束循环
        int seconds = LocalDateTime.now().getSecond();
        while (seconds != 0){
            seconds = LocalDateTime.now().getSecond();
        }
      
        // 提交两个任务
        for (int i = 0; i < 2; i++) {
            executorService.submit(() -> {
                while (share(mutex)) {
                    try {
                        // 睡眠0.5秒
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }

    }

    /**
     * 用来模拟临界资源的方法
     */
    public boolean share(final InterProcessMutex mutex){
        boolean b = true;
        try {
            // 获取锁
            if (mutex.acquire(3, TimeUnit.SECONDS)) {
                // 获取数据节点中的值
                byte[] bytes = client.getData().forPath(dataPath);
                String s = new String(bytes);
                Integer integer = Integer.valueOf(s);
                if(integer > 0){
                    // 设置新值
                    client.setData().forPath(dataPath,String.valueOf(integer-1).getBytes(StandardCharsets.UTF_8));
                    log.info("当前值:{}",integer);
                    b = true;
                }else {
                    log.info("任务已完成。。。。");
                    b = false;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
              	// 释放锁
                mutex.release();
                return b;
            } catch (Exception e) {
                log.error("释放锁失败");
                throw new RuntimeException(e);
            }
        }
    }
}
输出日志

从下方日志可见,没有出现重复的数值,保证了分布式系统中实现互斥访问共享资源,避免并发访问时的数据不一致问题。

实例c1:

2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:40
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:39
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:36
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:35
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:32
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:31
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:28
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:27
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:24
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:23
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:20
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:19
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:16
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:15
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:12
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:11
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:8
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:7
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:4
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:3
2024-01-15 INFO 65348 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。
2024-01-15 INFO 65348 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。

实例c2:

2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:38
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:37
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:34
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:33
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:30
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:29
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:26
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:25
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:22
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:21
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:18
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:17
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:14
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:13
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:10
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:9
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:6
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:5
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 当前值:2
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 当前值:1
2024-01-15 INFO 65387 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。
2024-01-15 INFO 65387 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。

3.3.重入式抢占锁

测试场景:有两台服务实例 C1,C2。C1和C2个有两个线程(线程池调度)并发执行,对同一共享资源(/ahao/data中的数据)进行连续两次的减1 操作,并且第一次操作成功后才能进行第二次操作。

测试代码

具体的减1操作抽离成方法,以便接下来的测试。

		@Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("。。。。。。。。。。。。。容器初始化完毕。。。。。。。。。。。。。。");

        String lockPath = "/ahao/lock";
        TimeUnit.SECONDS.sleep(3);

        // 创建可重入锁
        InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 保证并发执行,当前时间的秒针部分为0则结束循环
        int seconds = LocalDateTime.now().getSecond();
        while (seconds != 0){
            seconds = LocalDateTime.now().getSecond();
        }

        // 提交两个任务
        for (int i = 0; i < 2; i++) {
            executorService.submit(() -> {
                // 循环执行
                while (share(mutex,1)) {
                    try {
                        // 睡眠0.5秒
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }

    }

    /**
     * 用来模拟临界资源的方法
     */
    public boolean share(final InterProcessLock mutex, int n){
        boolean b = true;
        try {
            // 获取锁
            if (mutex.acquire(3, TimeUnit.SECONDS)) {
                // 减1操作
                b = doLock(n);
                // 最多执行三次
                if (b && n < 3){
                    b = share(mutex,n+1);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                mutex.release();
                return b;
            } catch (Exception e) {
                log.error("释放锁失败");
                throw new RuntimeException(e);
            }
        }
    }

    // 减1操作
    private boolean doLock(int n) throws Exception {
        // 获取数据节点中的值
        byte[] bytes = client.getData().forPath(dataPath);
        String s = new String(bytes);
        Integer integer = Integer.valueOf(s);
        // 判断是否为0
        if(integer > 0){
            // 设置新值
            client.setData().forPath(dataPath,String.valueOf(integer-1).getBytes(StandardCharsets.UTF_8));
            log.info("第{}次加锁当前值:{}",n,integer);
            return true;
        }else {
            log.info("任务已完成。。。。");
            return false;
        }
    }
输出日志

由此可见,通过日志可以发现,每次获取到锁的线程都是连续执行三次并且重复获取锁并释放。

实例c1:

2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:34
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:33
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:32
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:31
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:30
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:29
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:22
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:21
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:20
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:19
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:18
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:17
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:10
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:9
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:8
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:7
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:6
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:5
2024-01-16 INFO 91120 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。
2024-01-16 INFO 91120 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。

实例c2:

2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:40
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:39
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:38
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:37
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:36
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:35
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:28
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:27
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:26
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:25
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:24
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:23
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:16
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:15
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:14
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:13
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:12
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:11
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:4
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第2次加锁当前值:3
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 第3次加锁当前值:2
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 第1次加锁当前值:1
2024-01-16 INFO 91115 --- [pool-3-thread-2] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。
2024-01-16 INFO 91115 --- [pool-3-thread-1] com.ahao.demo.CuratorDemoApplication     : 任务已完成。。。。

4.分布式非可重入锁

org.apache.curator.framework.recipes.locks.InterProcessMutex 类似,只是不可重入。

4.1.锁对象

类路径:InterProcessSemaphoreMutex

公开构造方法如下:

 		/**
     * @param client 当前客户端实例
     * @param path   锁节点路径
     */		
		public InterProcessSemaphoreMutex(CuratorFramework client, String path);

4.2.重入式抢占锁

测试场景:有一台服务实例 C1。C1中的main线程进行连续两次的加锁操作。

测试代码
/**
 * @Name: CuratorDemoApplication
 * @Description:
 * @Author: ahao
 * @Date: 2024/1/10 3:29 PM
 */
@Slf4j
@SpringBootApplication
public class CuratorDemoApplication implements ApplicationRunner{

    @Autowired
    private CuratorFramework client;

    public static void main(String[] args) {
        SpringApplication.run(CuratorDemoApplication.class,args);
    }

    String dataPath = "/ahao/data";

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("。。。。。。。。。。。。。容器初始化完毕。。。。。。。。。。。。。。");

        String lockPath = "/ahao/lock";
        TimeUnit.SECONDS.sleep(3);

        // 创建非可重入锁
        InterProcessSemaphoreMutex mutex = new InterProcessSemaphoreMutex(client, lockPath);
        // 调用测试方法
        share(mutex);

    }


    /**
     * 用来模拟临界资源的方法
     */
    public void share(final InterProcessLock mutex){
        try {
            // 获取锁
            if (mutex.acquire(5, TimeUnit.SECONDS)) {
                log.info("第一次加锁成功");
            }
            // 再次获取锁
            if (!mutex.acquire(5, TimeUnit.SECONDS)) {
                log.info("第二次加锁失败了");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                // 获取多少次锁就要释放多少次
                mutex.release();
                mutex.release();
            } catch (Exception e) {
                log.error("释放锁失败:{}",e.getStackTrace());
                throw new RuntimeException(e);
            }
        }
    }

}
输出日志

本次测试仅需要启动一个实例c1即可,用于测试分布式非可重入锁多次加锁的场景。根据以下输出日志可知,第一次加锁成功,在第二次加锁时超时失败了,导致之后在第二次释放锁时出现异常。

2024-01-16 INFO 45025 --- [           main] com.ahao.demo.CuratorDemoApplication     : 第一次加锁成功
2024-01-16 INFO 45025 --- [           main] com.ahao.demo.CuratorDemoApplication     : 第二次加锁失败了
2024-01-16 ERROR 45025 --- [           main] com.ahao.demo.CuratorDemoApplication     : 释放锁失败:org.apache.curator.shaded.com.google.common.base.Preconditions.checkState(Preconditions.java:444)
2024-01-16 INFO 45025 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2024-01-16 ERROR 45025 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Failed to execute ApplicationRunner
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762)
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:749)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292)
	at com.ahao.demo.CuratorDemoApplication.main(CuratorDemoApplication.java:41)
Caused by: java.lang.RuntimeException: java.lang.IllegalStateException: Not acquired
	at com.ahao.demo.CuratorDemoApplication.share(CuratorDemoApplication.java:83)
	at com.ahao.demo.CuratorDemoApplication.run(CuratorDemoApplication.java:56)
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:759)
	... 5 common frames omitted
Caused by: java.lang.IllegalStateException: Not acquired
	at org.apache.curator.shaded.com.google.common.base.Preconditions.checkState(Preconditions.java:444)
	at org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex.release(InterProcessSemaphoreMutex.java:68)
	at com.ahao.demo.CuratorDemoApplication.share(CuratorDemoApplication.java:80)
	... 7 common frames omitted

5.分布式可重入读写锁

读写锁顾名思义,包含两把锁:读锁和写锁。当写锁未生效(未被获取)时,读锁能够被多个线程获取使用。但是写锁只能被一个线程获取持有。 只有当写锁释放时,读锁才能被持有。可重入表示一个拥有写锁的线程可重入读锁,但是读锁却不能进入写锁,读锁可以重入读锁。 这也意味着写锁可以降级成读锁, 比如请求写锁 —>读锁 —->释放写锁。 从读锁升级成写锁是不行的。可重入读写锁是“公平的”,每个实例将按请求的顺序获取锁。

5.1.锁对象

类路径:org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock

公开构造方法如下:

    
		/**
     * @param client 当前客户端实例
     * @param path   锁节点路径
     */		
		public InterProcessReadWriteLock(CuratorFramework client, String basePath)

  	/**
    *  @param client 当前客户端实例
    * @param path   锁节点路径
    * @param lockData 存储在锁节点的数据内容
    */
    public InterProcessReadWriteLock(CuratorFramework client, String basePath, byte[] lockData)

5.2.读锁和写锁的竞争

测试场景:有两台服务实例 C1,C2。C1和C2个有3个线程(2个读线程和1个写线程)并发执行,其中写线程进行加1操作并重复执行5次,每次都加写锁,而读线程进行查询操作并重复执行10次每次都加读锁。

测试代码
@Slf4j
@SpringBootApplication
public class CuratorDemoApplication implements ApplicationRunner {

    @Autowired
    private CuratorFramework client;

    public static void main(String[] args) {
        SpringApplication.run(CuratorDemoApplication.class, args);
    }

    String dataPath = "/ahao/data";

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("。。。。。。。。。。。。。容器初始化完毕。。。。。。。。。。。。。。");

        String lockPath = "/ahao/lock";
        TimeUnit.SECONDS.sleep(3);

        InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(client, lockPath);
        // 获取读锁
        InterProcessMutex readLock = readWriteLock.readLock();
        // 获取写锁
        InterProcessMutex writeLock = readWriteLock.writeLock();

        // 保证并发执行,当前时间的秒针部分为30的整数倍则结束循环
        int seconds = LocalDateTime.now().getSecond();
        while (seconds/30 != 0){
            seconds = LocalDateTime.now().getSecond();
        }

        for (int j = 0; j < 2; j++) {
            // 读线程
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        // 加锁
                        if (readLock.acquire(3, TimeUnit.SECONDS)) {
                            doLock(false);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    } finally {
                        // 释放锁
                        try {
                            readLock.release();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }, "读线程"+j).start();
        }

        // 写线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    // 加锁
                    if (writeLock.acquire(3, TimeUnit.SECONDS)) {
                        doLock(true);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    // 释放锁
                    try {
                        writeLock.release();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }

            }
        }, "写线程").start();

    }

    /**
     * 加操作
     * @param isAdd true 表示加1,false 表示不加,查询数据
     * @return
     * @throws Exception
     */
    public void doLock(boolean isAdd) throws Exception {
        // 获取数据节点中的值
        byte[] bytes = client.getData().forPath(dataPath);
        Integer integer = Integer.valueOf(new String(bytes));
        if (isAdd) {
            // 设置新值
            client.setData().forPath(dataPath, String.valueOf(integer + 1).getBytes(StandardCharsets.UTF_8));
            log.info("加1操作后:{}", integer);
        } else {
            log.info("查询数据:{}", integer);
        }
    }   
}
输出日志

可以观察到,读线程所查询的数据存在重复数据,说明了在同一时刻可以加多个读锁,而写线程不会出现重复数据,只能有一个线程可以获取到写锁。

实例c1:

2024-01-16  INFO 64462 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:40
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:41
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:42
2024-01-16  INFO 64462 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:42
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:43
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:44
2024-01-16  INFO 64462 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:44
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:45
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:46
2024-01-16  INFO 64462 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:46
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:47
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:48
2024-01-16  INFO 64462 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:48
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:49
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64462 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50

实例c2:

2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:40
2024-01-16  INFO 64453 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:41
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:42
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:42
2024-01-16  INFO 64453 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:43
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:44
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:44
2024-01-16  INFO 64453 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:45
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:46
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:46
2024-01-16  INFO 64453 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:47
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:48
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:48
2024-01-16  INFO 64453 --- [  写线程] com.ahao.demo.CuratorDemoApplication     : 加1操作后:49
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程1] com.ahao.demo.CuratorDemoApplication     : 查询数据:50
2024-01-16  INFO 64453 --- [ 读线程0] com.ahao.demo.CuratorDemoApplication     : 查询数据:50

6.共享信号量

对 JUC 熟悉的读者应该了解Semaphore(信号量)。Semaphore是一种用于实现同步的对象,通常用于限制对共享资源的并发访问。Semaphore可以用来控制进入临界区的线程数量,通过使用Semaphore,可以防止过多线程同时访问共享资源,从而避免出现资源竞争和死锁等问题。

而Curator中的Semaphore和JUC中的Semaphore,如出一辙,只是一个应用于分布式场景,一个应用于进程(服务器)内部。

6.1.锁对象

类路径:org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2

公开构造方法如下:

    /**
     * @param client 当前客户端实例
     * @param path   锁节点路径
     * @param maxLeases 允许线程进入临界区的最大数量(许可证数量)
     */
    public InterProcessSemaphoreV2(CuratorFramework client, String path, int maxLeases);
    /**
     * @param client 当前客户端实例
     * @param path   锁节点路径
     * @param count  用于监听许可证数量
     */
    public InterProcessSemaphoreV2(CuratorFramework client, String path, SharedCountReader count);

6.2.信号量抢占

测试场景:有两台服务实例 C1,C2。C1和C2个有3个线程并发执行。但是有2个许可证,每个线程一次只能获取一个许可证,获取成功则执行4s耗时操作(睡眠4s),然后释放锁。

测试代码
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("。。。。。。。。。。。。。容器初始化完毕。。。。。。。。。。。。。。");
        String lockPath = "/ahao/lock";
        TimeUnit.SECONDS.sleep(3);

        // 创建共享信号量
        InterProcessSemaphoreV2 semaphoreV2 = new InterProcessSemaphoreV2(client,lockPath,2);

        // 保证并发执行,当前时间的秒针部分为30的整数倍则结束循环
        int seconds = LocalDateTime.now().getSecond();
        while (seconds/30 != 0){
            seconds = LocalDateTime.now().getSecond();
        }

        // 创建线程争夺许可证
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                Lease acquire = null;
                try {
                    acquire = semaphoreV2.acquire(5, TimeUnit.SECONDS);
                    if (acquire != null){
                        log.info("抢到许可证,参与竞争的节点:{}",semaphoreV2.getParticipantNodes());
                        // 睡眠4秒
                        TimeUnit.SECONDS.sleep(4);
                    }else {
                        log.info("抢到许可证失败");
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    if (acquire != null){
                        semaphoreV2.returnLease(acquire);
                    }
                }
            }, "线程"+i).start();
        }
        
    }
输出日志

实例c1:

2024-01-16  INFO 4319 --- [ 线程2] com.ahao.demo.CuratorDemoApplication     : 抢到许可证
2024-01-16  INFO 4319 --- [ 线程1] com.ahao.demo.CuratorDemoApplication     : 抢到许可证
2024-01-16  INFO 4319 --- [ 线程0] com.ahao.demo.CuratorDemoApplication     : 抢到许可证失败

实例c2:

2024-01-16  INFO 4307 --- [ 线程0] com.ahao.demo.CuratorDemoApplication     : 抢到许可证
2024-01-16  INFO 4307 --- [ 线程2] com.ahao.demo.CuratorDemoApplication     : 抢到许可证
2024-01-16  INFO 4307 --- [ 线程1] com.ahao.demo.CuratorDemoApplication     : 抢到许可证失败

7.多共享锁

表示将多个锁合并为一个锁。在获取多共享锁时,必须获取其内部所有的锁,才算获取成功,否则释放所有已获取的锁。同样调用释放锁方法时,会释放所有的锁。

7.1.锁对象

类路径:org.apache.curator.framework.recipes.locks.InterProcessMultiLock

公开构造方法如下:

		/**
     * @param client 当前客户端实例
     * @param path   多个锁节点路径
     */
		public InterProcessMultiLock(CuratorFramework client, List<String> paths);
  
		/**
     * @param locks 多个锁对象
     */
		public InterProcessMultiLock(List<InterProcessLock> locks);

7.2.获取共享锁

测试场景:有一台服务实例 C1,启动3个线程并发执行,抢占同一个共享锁。

测试代码
@Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("。。。。。。。。。。。。。容器初始化完毕。。。。。。。。。。。。。。");
        String lockPath = "/ahao/lock";
        String lockPath2 = "/ahao/lock2";
        TimeUnit.SECONDS.sleep(3);

      	// 创建锁1
        InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
      	// 创建锁2
        InterProcessMutex mutex2 = new InterProcessMutex(client,lockPath2);
        // 创建共享锁
        InterProcessMultiLock multiLock = new InterProcessMultiLock(List.of(mutex,mutex2));

        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                try {
                    if (multiLock.acquire(5, TimeUnit.SECONDS)) {
                        log.info("获取到锁");
                        TimeUnit.SECONDS.sleep(3);
                    }else {
                        log.info("获取失败");
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    try {
                        multiLock.release();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            },"线程"+i).start();
        }

    }
输出日志

可见在第3个线程获取锁时,由于没有获取到/ahao/lock2对应的锁对象导致的超时。

2024-01-16  INFO 14352 --- [ 线程1] com.ahao.demo.CuratorDemoApplication     : 获取到锁
2024-01-16  INFO 14352 --- [ 线程2] com.ahao.demo.CuratorDemoApplication     : 获取到锁
2024-01-16  INFO 14352 --- [ 线程0] com.ahao.demo.CuratorDemoApplication     : 获取失败
Exception in thread "线程0" java.lang.RuntimeException: java.lang.Exception: java.lang.IllegalMonitorStateException: You do not own the lock: /ahao/lock2
	at com.ahao.demo.CuratorDemoApplication.lambda$run$0(CuratorDemoApplication.java:70)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.Exception: java.lang.IllegalMonitorStateException: You do not own the lock: /ahao/lock2
	at org.apache.curator.framework.recipes.locks.InterProcessMultiLock.release(InterProcessMultiLock.java:169)
	at com.ahao.demo.CuratorDemoApplication.lambda$run$0(CuratorDemoApplication.java:68)
	... 1 more
Caused by: java.lang.IllegalMonitorStateException: You do not own the lock: /ahao/lock2
	at org.apache.curator.framework.recipes.locks.InterProcessMutex.release(InterProcessMutex.java:140)
	at org.apache.curator.framework.recipes.locks.InterProcessMultiLock.release(InterProcessMultiLock.java:158)
	... 2 more

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

canvas绘制美队盾牌

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

项目管理十大知识领域之项目整体管理

1. 项目整体管理的定义和范畴 项目整体管理是指在整个项目生命周期中对项目进行全面规划、组织、协调、控制和监督的过程。这包括对项目目标、范围、时间、成本、质量、沟通、风险和采购等方面进行统一的管理和协调。项目整体管理的范畴涵盖了项目管理的方方面面&#xff0c;旨…

【特征工程】分类变量:MultiLabelBinarizer对多标签数据进行编码

MultiLabelBinarizer 说明介绍 1. MultiLabelBinarizer 是什么&#xff1f; MultiLabelBinarizer是scikit-learn库中的一个用于处理多标签数据的编码器。通常用于将多标签的分类任务中的标签转化为二进制形式&#xff0c;便于机器学习模型的处理。该编码器的主要目标是将每个…

leecode1011 | 在D天内送达包裹的能力

传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。 传送带上的第 i 个包裹的重量为 weights[i]。每一天&#xff0c;我们都会按给出重量&#xff08;weights&#xff09;的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。 返回能在 days 天内将传…

压力测试+接口测试(工具jmeter)

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因 为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是…

【架构师专题】架构师如何管理业务团队?

一个优秀的架构师一定是一个优秀的管理者&#xff0c;这个是没错的&#xff0c;反过来一个优秀的管理者不一定是一个优秀的架构师&#xff0c;这个是我做了这么对年的架构师心得。 曾经我也做过关于团队底下的一线开发人员对团队中架构师的调查报告&#xff0c;发现这些开发人…

从此不再为远程访问局域网发愁

下载地址 Windows 64位 (切勿直接在压缩文件中操作,全部解压到一处后再操作,请关闭某60(会胡乱拦截),可用其他任意安全软件)Mac OS X 64位 (给fastnat执行权限 chmod x ./fastnat.. 终端运行二进制,自行百度)Linux 64位 (给fastnat执行权限 chmod x ./fastnat..)Linux/ARM 32位…

自动驾驶中大火的AI大模型中有哪些研究方向,与Transformer何干?

摘要: 本文将针对大模型学习中可能遇见的问题进行分析梳理,以帮助开发者在利用大模型在自动驾驶场景处理中学习更好的策略,利用有关大模型性能评价的问题,制定一个科学的标准去判断大模型的长处和不足。 随着自动驾驶行业发展对于大数据量处理的强大需求,其要求处理数据的…

硬件知识积累 VPX 3U/6U 电源板的连接器引脚定义讲解 (简单说明)

本文章参考资料 &#xff1a;ANSI/VITA 62-2012 1. 首先参考资料里面引脚定义图&#xff1a; 1. 3U 的引脚定义图&#xff1a; (VPX引脚编号&#xff0c; 最大电流&#xff0c; 对应引脚的作用) 2. 6U的引脚定义图&#xff1a; (VPX引脚编号&#xff0c; 最大电流&#xff0c…

网络Yum仓库介绍、自定义Yum仓库、Yum仓库更新、邮件的收发、DNS服务介绍、构建DNS服务、递归解析、迭代解析、NTP时间同步

1 大鹏一日同风起&#xff0c;扶摇直上九万里 2 环境准备 所有虚拟机设置SELinux运行模式 [rootserver ~]# getenforceEnforcing[rootserver ~]# setenforce 0 #当前设置[rootserver ~]# getenforcePermissive[rootserver ~]# vim /etc/selinux/configSELINUXpermissive 所…

爬虫案例——使用超级鹰打码平台识别验证码

爬虫案例——使用超级鹰打码平台识别验证码 超级鹰是一个常用的打码平台&#xff0c;您可以按照以下步骤来使用它&#xff1a; 注册账户&#xff1a;首先&#xff0c;您需要在超级鹰网站上注册一个账户。访问超级鹰官方网站&#xff08;https://www.chaojiying.com/&#xff…

Lua从电脑端爬取短视频评论并作商业化分析

之前有个大客户让我写一篇关于抓取短视频评论数据的单子&#xff0c;说是帮助公司寻找意向客户以及所对应产品在短视频里面的展现以及热门程度&#xff0c;通过数据采集方式并作数据自动化分析&#xff0c;从而实现商业上的价值。对于我来说写个爬虫还不简单&#xff0c;通过三…

使用scipy处理图片——滤镜处理

大纲 black_tophatwhite_tophatconvolvecorrelategaussian_filtergaussian_laplacemaximum_filtermedian_filterminimum_filterpercentile_filterprewittrank_filtersobelspline_filteruniform_filter基础代码代码仓库 在《使用numpy处理图片——模糊处理》一文中&#xff0c;我…

NodeJs 第十六章 JWT

JWT 概述令牌的组成headerpayloadsignature 令牌的验证一个简单的登陆服务总结 随着前后端分离的发展&#xff0c;以及数据中心的建立&#xff0c;越来越多的公司会创建一个中心服务器&#xff0c;服务于各种产品线。 而这些产品线上的产品&#xff0c;它们可能有着各种终端设备…

如何在云端加速缓存构建

缓存是指将某类数据存储起来以便以后重复使用的过程&#xff0c;它的运用在开发场景中非常普遍。类似于你习惯把最常用的调料放在厨房台面上&#xff0c;而不是橱柜里&#xff0c;这样你在准备大餐时就可以轻松取用。 但对于一个更为技术性、更精确的用例&#xff0c;比如像谷…

CSDN 年度总结|知识改变命运,学习成就未来

欢迎来到英杰社区&#xff1a; https://bbs.csdn.net/topics/617804998 欢迎来到阿Q社区&#xff1a; https://bbs.csdn.net/topics/617897397 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff…

探索Vue3:深入理解响应式语法糖

🚀 欢迎来到我的专栏!专注于Vue3的实战总结和开发实践分享,让你轻松驾驭Vue3的奇妙世界! 🌈✨在这里,我将为你呈现最新的Vue3技术趋势,分享独家实用教程,并为你解析开发中的难题。让我们一起深入Vue3的魅力,助力你成为Vue大师! 👨‍💻💡不再徘徊,快来关注…

Spring Security-用户注销及记住我

用户注销 在配置类增加退出映射地址 Overrideprotected void configure(HttpSecurity http) throws Exception {//退出/注销http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();} 完整代码: package com.config;​import o…

MSVS C# Matlab的混合编程系列1 - 看似简单的问题引出

前言&#xff1a; 问题提出&#xff0c;如何把Matlab(本文简称MT)的算法集成到Visual Studio(本文简称VS)里面运行&#xff1f; 本文&#xff0c;通过编制一个MT中最简单的加法函数&#xff0c;我们把他做成 MSVS C#能够使用的动态库&#xff0c;说明了MSVS C# 和 MT集成的最…

TCP连接TIME_WAIT

TCP断开过程: TIME_WAIT的作用: TIME_WAIT状态存在的理由&#xff1a; 1&#xff09;可靠地实现TCP全双工连接的终止 在进行关闭连接四次挥手协议时&#xff0c;最后的ACK是由主动关闭端发出的&#xff0c;如果这个最终的ACK丢失&#xff0c;服务器将重发最终的FIN&#xf…