分布式锁实现方式

news2024/12/27 19:04:50

分布式锁

1 分布式锁介绍

1.1 什么是分布式

一个大型的系统往往被分为几个子系统来做,一个子系统可以部署在一台机器的多个 JVM(java虚拟机) 上,也可以部署在多台机器上。但是每一个系统不是独立的,不是完全独立的。需要相互通信,共同实现业务功能。

一句话来说:分布式就是通过计算机网络将后端工作分布到多台主机上,多个主机一起协同完成工作。

1.2 什么是锁–作用安全

现实生活中,当我们需要保护一样东西的时候,就会使用锁。例如门锁,车锁等等。很多时候可能许多人会共用这些资源,就会有很多个钥匙。但是有些时候我们希望使用的时候是独自不受打扰的,那么就会在使用的时候从里面反锁,等使用完了再从里面解锁。这样其他人就可以继续使用了。

  • 锁 单进程的系统中,存在多线程同时操作一个公共变量,此时需要加锁对变量进行同步操作,保证多线程的操作线性执行消除并发修改。解决的是单进程中的多线程并发问题。

在这里插入图片描述

1.4 什么是分布式锁

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。CAP

当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。

分布式锁: 在分布式环境下,多个程序/线程都需要对某一份(或有限制)的数据进行修改时,针对程序进行控制,保证同一时间节点下,只有一个程序/线程对数据进行操作的技术。

1.5 分布式锁的真实使用场景

场景一:

在这里插入图片描述

场景二:

在这里插入图片描述

1.5 分布式锁的执行流程

在这里插入图片描述

1.6 分布式锁具备的条件

  • 互斥性:同一时刻只能有一个服务(或应用)访问资源,特殊情况下有读写锁
  • 原子性:一致性要求保证加锁和解锁的行为是原子性的
  • 安全性:锁只能被持有该锁的服务(或应用)释放
  • 容错性:在持有锁的服务崩溃时,锁仍能得到释放避免死锁
  • 可重用性:同一个客户端获得锁后可递归调用—重入锁和不可重入锁
  • 公平性:看业务是否需要公平,避免饿死–公平锁和非公平锁
  • 支持阻塞和非阻塞:和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)—阻塞锁和非阻塞锁PS:::自选锁
  • 高可用:获取锁和释放锁 要高可用
  • 高性能:获取锁和释放锁的性能要好
  • 持久性:锁按业务需要自动续约/自动延期

2.分布式锁的解决方案

2.1 数据库实现分布式锁

基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯一索引天然具有排他性,这刚好符合我们对锁的要求:同一时刻只能允许一个竞争者获取锁。加锁时我们在数据库中插入一条锁记录,利用业务id进行防重。当第一个竞争者加锁成功后,第二个竞争者再来加锁就会抛出唯一索引冲突,如果抛出这个异常,我们就判定当前竞争者加锁失败。防重业务id需要我们自己来定义,例如我们的锁对象是一个方法,则我们的业务防重id就是这个方法的名字,如果锁定的对象是一个类,则业务防重id就是这个类名。

2.1.1 基于数据库表实现

准备工作:创建tb_program表,用于记录当前哪个程序正在使用数据

CREATE TABLE `tb_program` (
  `program_no` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '程序的编号'
  PRIMARY KEY (`program_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

实现步骤:

  1. 程序访问数据时,将程序的编号(insert)存入tb_program表;
  2. 当insert成功,代表该程序获得了锁,即可执行逻辑;
  3. 当program_no相同的其他程序进行insert是,由于主键冲突会导致insert失败,则代表获取锁失败;
  4. 获取锁成功的程序在逻辑执行完以后,删除该数据,代表释放锁。

2.1.2 基于数据库的排他锁实现

除了可以通过增删操作数据表中的记录以外,其实还可以借助数据中自带的锁来实现分布式的锁。我们还用刚刚创建的那张数据库表,基于MySql的InnoDB引擎(MYSQL的引擎种类)可以通过数据库的排他锁来实现分布式锁。

实现步骤:

  1. 在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁
  2. 获得排它锁的线程即可获得分布式锁,执行方法的业务逻辑
  3. 执行完方法之后,再通过connection.commit();操作来释放锁。

实现代码

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.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.maweiqi</groupId>
    <artifactId>mysql-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--依赖包-->
    <dependencies>
        <!--核心包-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--一般分词器,适用于英文分词-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--中文分词器-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-smartcn</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!--对分词索引查询解析-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--检索关键字高亮显示-->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!-- MySql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

        <!-- Test dependencies -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Book

public class Book {

    // 图书ID
    private Integer id;
    // 图书名称
    private String name;
    // 图书价格
    private Float price;
    // 图书图片
    private String pic;
    // 图书描述
    private String desc;
}

BookDao

public interface BookDao {

    /**
     * 查询所有的book数据
     * @return
     */
    List<Book> queryBookList(String name) throws Exception;
}

BookDaoImpl实现类

public class BookDaoImpl implements BookDao {

    /***
     * 查询数据库数据
     * @return
     * @throws Exception
     */
    public List<Book> queryBookList(String name) throws Exception{

        // 数据库链接
        Connection connection = null;
        // 预编译statement
        PreparedStatement preparedStatement = null;
        // 结果集
        ResultSet resultSet = null;
        // 图书列表
        List<Book> list = new ArrayList<Book>();

        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 连接数据库
            connection = DriverManager.getConnection("jdbc:mysql://39.108.189.37:3306/lucene", "root", "root");
            //关闭自动提交
            connection.setAutoCommit(false);
            // SQL语句
            String sql = "SELECT * FROM book where id = 1 for update";
            // 创建preparedStatement
            preparedStatement = connection.prepareStatement(sql);
            // 获取结果集
            resultSet = preparedStatement.executeQuery();
            // 结果集解析
            while (resultSet.next()) {
                Book book = new Book();
                book.setId(resultSet.getInt("id"));
                book.setName(resultSet.getString("name"));
                list.add(book);
            }
            System.out.println(name + "执行了for update");
            System.out.println("结果为:" + list);
            //锁行后休眠5秒
            Thread.sleep(5000);

            //休眠结束释放
            connection.commit();
            System.out.println(name + "结束");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }
}

测试类

public class Test {

    private BookDao bookDao = new BookDaoImpl();

    @org.junit.Test
    public void testLock() throws Exception  {
        new Thread(new LockRunner("线程1")).start();
        new Thread(new LockRunner("线程2")).start();
        new Thread(new LockRunner("线程3")).start();
        new Thread(new LockRunner("线程4")).start();
        new Thread(new LockRunner("线程5")).start();
        Thread.sleep(200000L);
    }

    class LockRunner implements Runnable {

        private String name;

        public LockRunner(String name) {
            this.name = name;
        }

        public void run() {
            try {
                bookDao.queryBookList(name);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

执行结果

在这里插入图片描述

2.1.3 优点及缺点

优点: 简单,方便,快速实现

缺点: 基于数据库,开销比较大,对数据库性能可能会存在影响

2.2 Redis实现分布式锁

2.2.1 基于 REDIS 的 SETNX()、EXPIRE() 、GETSET()方法做分布式锁

实现原理

setnx():setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0
expire():expire 设置过期时间,要注意的是 setnx 命令不能设置 key 的超时时间,只能通过 expire() 来对 key 设置。
getset():这个命令主要有两个参数 getset(key,newValue)。该方法是原子的,对 key 设置 newValue 这个值,并且返回 key 原来的旧值。假设 key 原来是不存在的,那么多次执行这个命令,会出现下边的效果:

getset(key, “value1”) 返回 null 此时 key 的值会被设置为 value1
getset(key, “value2”) 返回 value1 此时 key 的值会被设置为 value2

实现流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XgWj7CEH-1692413826329)(images\7.png)]

  1. setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁。
  2. get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取。
  3. 计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime。判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
  4. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

代码实现

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.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.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.maweiqi</groupId>
    <artifactId>redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis</name>
    <description>redis实现分布式锁测试</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

RedisUtil工具类

@Component
public class RedisUtil {
    //定义默认超时时间:单位毫秒
    private static final Integer LOCK_TIME_OUT = 10000;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 外部调用加锁方法
     */
    public Boolean tryLock(String key, Long timeout) throws Exception{

        //获取当前系统时间设置为开始时间
        Long startTime = System.currentTimeMillis();

        //设置返回默认值-false:加锁失败
        boolean flag = false;

        //死循环获取锁:1.获取锁成功退出 2.获取锁超时退出
        while(true){
            //判断是否超时
            if((System.currentTimeMillis() - startTime) >= timeout){
                break;
            }else{
                //获取锁
                flag = lock(key);
                //判断是否获取成功
                if(flag){
                    break;
                }else{
                    //休息0.1秒重试,降低服务压力
                    Thread.sleep(100);
                }
            }
        }
        return flag;
    }

    /**
     * 加锁实现
     * @param key
     * @return
     */
    private Boolean lock(String key){
        return (Boolean) stringRedisTemplate.execute((RedisCallback) redisConnection -> {
            //获取当前系统时间
            Long time = System.currentTimeMillis();

            //设置锁超时时间
            Long timeout = time + LOCK_TIME_OUT + 1;

            //setnx加锁并获取解锁结果
            Boolean result = redisConnection.setNX(key.getBytes(), String.valueOf(timeout).getBytes());

            //加锁成功返回true
            if(result){
                return true;
            }

            //加锁失败判断锁是否超时
            if(checkLock(key, timeout)){
                //getset设置值成功后,会返回旧的锁有效时间
                byte[] newtime = redisConnection.getSet(key.getBytes(), String.valueOf(timeout).getBytes());
                if(time > Long.valueOf(new String(newtime))){
                    return true;
                }
            }
            //默认加锁失败
            return false;
        });

    }

    /**
     * 释放锁
     */
    public Boolean release(String key){
        return (Boolean) stringRedisTemplate.execute((RedisCallback) redisConnection -> {
            Long del = redisConnection.del(key.getBytes());
            if (del > 0){
                return true;
            }
            return false;
        });
    }

    /**
     * 判断锁是否超时
     */
    private Boolean checkLock(String key, Long timeout){

        return (Boolean) stringRedisTemplate.execute((RedisCallback) redisConnection -> {
            //获取锁的超时时间
            byte[] bytes = redisConnection.get(key.getBytes());

            try {
                //判断锁的有效时间是否大与当前时间
                if(timeout > Long.valueOf(new String(bytes))){
                    return true;
                }
            }catch (Exception e){
                e.printStackTrace();
                return false;
            }
            return false;
        });
    }
}

RedisController测试类

@RestController
@RequestMapping(value = "/redis")
public class RedisController {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 获取锁
     * @return
     */
    @GetMapping(value = "/lock/{name}")
    public String lock(@PathVariable(value = "name")String name) throws Exception{
        Boolean result = redisUtil.tryLock(name, 3000L);
        if(result){
            return "获取锁成功";
        }
        return "获取锁失败";
    }

    /**
     * 释放锁
     * @param name
     */
    @GetMapping(value = "/unlock/{name}")
    public String unlock(@PathVariable(value = "name")String name){
        Boolean result = redisUtil.release(name);
        if(result){
            return "释放锁成功";
        }
        return "释放锁失败";
    }
}

2.2.2 优点及缺点

优点:性能极高

缺点:失效时间设置没有定值。设置的失效时间太短,方法没等执行完锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间,用户体验会降低。

2.3 zookeeper实现分布式锁

2.3.1 zookeeper 锁相关基础知识

  • zookeeper 一般由多个节点构成(单数),采用 zab 一致性协议。因此可以将 zk 看成一个单点结构,对其修改数据其内部自动将所有节点数据进行修改而后才提供查询服务。
  • zookeeper 的数据以目录树的形式,每个目录称为 znode, znode 中可存储数据(一般不超过 1M),还可以在其中增加子节点。
  • 子节点有三种类型。序列化节点,每在该节点下增加一个节点自动给该节点的名称上自增。临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除。最后就是普通节点。
  • Watch 机制,client 可以监控每个节点的变化,当产生变化会给 client 产生一个事件。

2.3.2 zookeeper 分布式锁的原理

  • 获取和释放锁原理:利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
  • 获取锁的顺序原理:上锁为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)。

2.3.2 zookeeper实现分布式锁流程

简易流程

在这里插入图片描述

获取锁流程:

  1. 先有一个锁根节点,lockRootNode,这可以是一个永久的节点
  2. 客户端获取锁,先在 lockRootNode 下创建一个顺序的临时节点,保证客户端断开连接,节点也自动删除
  3. 调用 lockRootNode 父节点的 getChildren() 方法,获取所有的节点,并从小到大排序,如果创建的最小的节点是当前节点,则返回 true,获取锁成功,否则,关注比自己序号小的节点的释放动作(exist watch),这样可以保证每一个客户端只需要关注一个节点,不需要关注所有的节点,避免羊群效应。
  4. 如果有节点释放操作,重复步骤 3

释放锁流程:

只需要删除步骤 2 中创建的节点即可

2.3.2 优点及缺点

优点:

  • 客户端如果出现宕机故障的话,锁可以马上释放
  • 可以实现阻塞式锁,通过 watcher 监听,实现起来也比较简单
  • 集群模式,稳定性比较高

缺点:

  • 一旦网络有任何的抖动,Zookeeper 就会认为客户端已经宕机,就会断掉连接,其他客户端就可以获取到锁。
  • 性能不高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。ZK 中创建和删除节点只能通过 Leader 服务器来执行,然后将数据同步到所有的 Follower 机器上。(zookeeper对外提供服务的只有leader)

2.4 consul实现分布式锁(eureka/Register:保存服务的IP 端口 服务列表)

2.4.1 实现原理及流程

基于Consul注册中心的Key/Value存储来实现分布式锁以及信号量的方法主要利用Key/Value存储API中的acquire和release操作来实现。acquire和release操作是类似Check-And-Set的操作:

acquire操作只有当锁不存在持有者时才会返回true,并且set设置的Value值,同时执行操作的session会持有对该Key的锁,否则就返回false

release操作则是使用指定的session来释放某个Key的锁,如果指定的session无效,那么会返回false,否则就会set设置Value值,并返回true

实现流程

在这里插入图片描述

实现步骤:

  1. 客户端创建会话session,得到sessionId;
  2. 使用acquire设置value的值,若acquire结果为false,代表获取锁失败;
  3. acquire结果为true,代表获取锁成功,客户端执行业务逻辑;
  4. 客户端业务逻辑执行完成后,执行release操作释放锁;
  5. 销毁当前session,客户端连接断开。

代码:

下载consul

启动consul命令: consul agent -dev

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.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.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.maweiqi</groupId>
    <artifactId>demo-consul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-consul</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

public class ConsulUtil {

    private ConsulClient consulClient;

    private String sessionId = null;

    /**
     * 构造函数
     */
    public ConsulUtil(ConsulClient consulClient) {
        this.consulClient = consulClient;
    }

    /**
     * 创建session
     */
    private String createSession(String name, Integer ttl){
        NewSession newSession = new NewSession();
        //设置锁有效时长
        newSession.setTtl(ttl + "s");
        //设置锁名字
        newSession.setName(name);
        String value = consulClient.sessionCreate(newSession, null).getValue();
        return value;
    }

    /**
     * 获取锁
     */
    public Boolean lock(String name, Integer ttl){
        //定义获取标识
        Boolean flag = false;
        //创建session
        sessionId = createSession(name, ttl);
        //死循环获取锁
        while (true){
            //执行acquire操作
            PutParams putParams = new PutParams();
            putParams.setAcquireSession(sessionId);
            flag = consulClient.setKVValue(name, "local" + System.currentTimeMillis(), putParams).getValue();
            if(flag){
                break;
            }
        }
        return flag;
    }

    /**
     * 释放锁
     */
    public Boolean release(String name){
        //执行acquire操作
        PutParams putParams = new PutParams();
        putParams.setReleaseSession(sessionId);
        Boolean value = consulClient.setKVValue(name, "local" + System.currentTimeMillis(), putParams).getValue();
        return value;
    }

测试代码:

@SpringBootTest
class DemoApplicationTests {

    @Test
    public void testLock() throws Exception  {
        new Thread(new LockRunner("线程1")).start();
        new Thread(new LockRunner("线程2")).start();
        new Thread(new LockRunner("线程3")).start();
        new Thread(new LockRunner("线程4")).start();
        new Thread(new LockRunner("线程5")).start();
        Thread.sleep(200000L);
    }

    class LockRunner implements Runnable {

        private String name;

        public LockRunner(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            ConsulUtil lock = new ConsulUtil(new ConsulClient());
            try {
                if (lock.lock("test", 10)) {

                    System.out.println(name + "获取到了锁");
                    //持有锁5秒
                    Thread.sleep(5000);
                    //释放锁
                    lock.release("test");
                    System.out.println(name + "释放了锁");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

结果

在这里插入图片描述

在这里插入图片描述

2.4.2 优点及缺点

**优点:**基于consul注册中心即可实现分布式锁,实现简单、方便、快捷

缺点:

  • lock delay:consul实现分布式锁存在延迟,一个节点释放锁了,另一个节点不能立马拿到锁。需要等待lock delay时间后才可以拿到锁。
  • 高负载的场景下,不能及时的续约,导致session timeout, 其他节点拿到锁。

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

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

相关文章

作为一个产品经理怎么做一个项目的需求文档?(附有需求文档的部分展示)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于需求文档的相关操作吧 首先&#xff0c;我们需求明确的是&#xff0c;一个产品经理究竟需要做些什么&#xff1f;在项目的阶段中起到哪一些作用 一.编写产品需…

大数据面试题:Spark的任务执行流程

面试题来源&#xff1a; 《大数据面试题 V4.0》 大数据面试题V3.0&#xff0c;523道题&#xff0c;679页&#xff0c;46w字 可回答&#xff1a;1&#xff09;Spark的工作流程&#xff1f;2&#xff09;Spark的调度流程&#xff1b;3&#xff09;Spark的任务调度原理&#xf…

插入排序优化——超越归并排序的超级算法

插入排序及优化 插入排序算法算法讲解数据模拟代码 优化思路一、二分查找二、copy函数 优化后代码算法的用途题目&#xff1a;数星星&#xff08;POJ2352 star&#xff09;输入输出格式输入格式&#xff1a;输出格式 输入输出样例输入样例输出样例 题目讲解步骤如下AC 代码 插入…

GPDB-疑难杂症-PlaceHolderVar

GPDB-疑难杂症-PlaceHolderVar 从GPDB5升级到GPDB6时&#xff0c;遇到以往可以执行的SQL不能执行了。报错&#xff1a;PlaceHolderVar found where not expected!语法不兼容了&#xff1f; postgres# CREATE TABLE t1( id1 int) WITH (appendonlytrue, compresstypenone, b…

@Repeatable的作用以及具体如何使用

文章目录 1. 前言2. 先说结论3. 案例演示 1. 前言 最近无意看到某些注解上有Repeatable&#xff0c;出于比较好奇&#xff0c;因此稍微研究并写下此文章。 2. 先说结论 Repeatable的作用&#xff1a;使被他注释的注解可以在同一个地方重复使用。 具体使用如下&#xff1a; T…

shell脚本文本 三剑客AWK

TOC 一.AWK工具介绍 AWK是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具可以在无交互的模式下实现复杂的文本操作相较于sed常作用于一整行的处理&#xff0c;awk则比较倾向于一行当中分成数个字段来处理&#xff0c;因为awk相当适合小型的文本数据 1.1AWK命令…

听GPT 讲Prometheus源代码--discovery

Prometheus是一个开源的系统监控和警报工具包&#xff0c;以下是Prometheus源代码中一些主要的文件夹及其作用&#xff1a; cmd/&#xff1a;这个目录包含了Prometheus主要的命令行工具&#xff0c;如prometheus/&#xff0c;promtool/等。每个子目录都代表一个可执行的命令行应…

Unsafe upfileupload

文章目录 client checkMIME Typegetimagesize 文件上传功能在web应用系统很常见&#xff0c;比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后&#xff0c;后台会对上传的文件进行判断 比如是否是指定的类型、后缀名、大小等等&#xff0c;然后将其按…

高德地图开发者平台Python应用实践:快速入门周边商业环境信息查询

高德地图开发平台提供了丰富的API接口&#xff0c;可以方便地进行地图数据的开发和分析。在商业分析数据采集中&#xff0c;使用高德地图开发平台的周边查询功能可以快速获取周边商圈、小区等信息&#xff0c;为商业决策提供数据支持。 针对您的需求&#xff0c;我建议采用以下…

stream.map return

出现以下告警信息 Statement lambda can be replaced with expression lambda less... (CtrlF1) This inspection reports lambda expressions with code block bodies when expression-style bodies can be used 将 List<StudentDetailDto> studentDetailDtoList link…

防丢器Airtag国产版

Airtag是什么&#xff1f; AirTag是苹果公司设计的一款定位神奇&#xff0c;它通过一款纽扣电池进行供电&#xff0c;即可实现长达1-2年的关键物品的定位、查找的功能。 按照苹果公司自己的话说—— 您“丢三落四这门绝技&#xff0c;要‍失‍传‍了”。 AirTag 可帮你轻松追…

Objectarx 2021使用vs2019生成报错 /RTCc rejects conformant code

error C2338: /RTCc rejects conformant code错误解决 使用VS2019/VS2022生成项目报错 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C1189 #error: /RTCc rejects conformant code, so it is not supported by the C Standard Library. Either remove this compiler opti…

网络安全---webshell实践

一、首先环境配置 1.上传文件并解压 2.进入目录下 为了方便解释&#xff0c;我们只用两个节点&#xff0c;启动之后&#xff0c;大家可以看到有 3 个容器&#xff08;可想像成有 3 台服务器就成&#xff09;。 二、使用蚁剑去连接 因为两台节点都在相同的位置存在 ant.jsp&…

vue2.6升级vue2.7(panjiachen升级指南)vue-cli5多页面应用升级的坑

vue2.7升级指南 vue2.7升级指南 之前的架子使用的是 panjiachen&#xff0c;使用的是 vue2.6.14&#xff0c;现在升级为 vue2.7.x 升级vue/cli vue upgrade 这里推荐使用 vue upgrade 命令自动升级 # 确保安装全局 vue/cli $ npm install -g vue/cli $ vue upgradeWARN Th…

Vue 2 自定义指令

Vue 2自定义指令 Vue自定义指令允许我们在DOM元素上添加自己想要的行为来扩展Vue的功能。 一个自定义指令需要一个名称和一个定义对象。在定义对象中&#xff0c;你可以使用一些钩子函数来控制指令的行为&#xff1a; bind&#xff1a;在指令被绑定到元素上时使用&#xff0…

基于IMX6ULLmini的linux裸机开发系列七:中断处理流程

中断上下文 cpu通过内核寄存器来运行指令并进行数据的读写处理的&#xff0c;它在进入中断前一个时刻的具体值&#xff0c;称为中断上下文 中断上下文是指CPU在进入中断之前保存的寄存器状态和其他相关信息。当CPU接收到中断请求时&#xff0c;它会保存当前正在执行的指令的状…

广州华锐互动:3D数字孪生开发编辑器助力企业高效开发数字孪生应用

3D数字孪生开发编辑器是一种新兴的技术&#xff0c;它可以帮助企业更好地管理和维护其物联网设备。这些工具可以帮助企业实现对设备的实时监控、故障排除和优化&#xff0c;从而提高生产效率和降低成本。 数字孪生系统是一种将物理世界与数字世界相结合的技术&#xff0c;它可以…

Python web实战之细说 Django 的单元测试

关键词&#xff1a; Python Web 开发、Django、单元测试、测试驱动开发、TDD、测试框架、持续集成、自动化测试 大家好&#xff0c;今天&#xff0c;我将带领大家进入 Python Web 开发的新世界&#xff0c;深入探讨 Django 的单元测试。通过本文的实战案例和详细讲解&#xff…

【C#学习笔记】C#特性的继承,封装,多态

文章目录 封装访问修饰符静态类和静态方法静态构造函数 继承继承原则sealed修饰符里氏替换原则继承中的构造函数 多态接口接口的实例化 抽象类和抽象方法抽象类和接口的异同 虚方法同名方法new覆盖的父类方法继承的同名方法 运行时的多态性编译时的多态性 照理继承封装多态应该…

Chapter 14: Using Web Services | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介Python and Web ServicesUsing Web ServiceseXtensible Markup Language - XMLParsing XMLJavaScript Object Notation - JSONParsing JSONApplication Programming InterfacesSecurity and API usageGlossary Python for Everybody Expl…