分布式锁实现方式

分布式锁

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, 其他节点拿到锁。

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

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

相关文章

【数据结构OJ题】有效的括号

原题链接&#xff1a;https://leetcode.cn/problems/valid-parentheses/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 这道题目主要考查了栈的特性&#xff1a; 题目的意思主要是要做到3点匹配&#xff1a;类型、顺序、数量。 题目给的例子是比较…

神经网络基础-神经网络补充概念-02-逻辑回归

概念 逻辑回归是一种用于二分分类问题的统计学习方法&#xff0c;尽管名字中带有"回归"一词&#xff0c;但实际上它用于分类任务。逻辑回归的目标是根据输入特征来预测数据点属于某个类别的概率&#xff0c;然后将概率映射到一个离散的类别标签。 逻辑回归模型的核…

Django实现音乐网站 ⑾

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是前端开发前的一些必要配置和首页展示开发。 目录 配置应用路由 创建应用路由文件 应用路径加入项目路径 创建项目模板 创建项目及应用模板路径 设置模板路径 设置静态资源路径 创建静态资源路径 配置静态…

Qt安卓开发经验技巧总结V202308

01&#xff1a;01-05 pro中引入安卓拓展模块 QT androidextras 。pro中指定安卓打包目录 ANDROID_PACKAGE_SOURCE_DIR $$PWD/android 指定引入安卓特定目录比如程序图标、变量、颜色、java代码文件、jar库文件等。 AndroidManifest.xml 每个程序唯一的一个全局配置文件&…

webshell实践,在nginx上实现负载均衡

1、配置多台虚拟机&#xff0c;用作服务器 在不同的虚拟机上安装httpd服务 我采用了三台虚拟机进行服务器设置&#xff1a;192.168.240.11、192.168.240.12、192.168.240.13 [rootnode0-8 /]# yum install httpd -y #使用yum安装httpd服务#开启httpd服务 [rootnode0-8 /]# …

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

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

CSS 字体修饰属性

前言 字体修饰属性 属性说明font-family指定文本显示字体font-size设置字体的大小font-weight设置字体的粗细程度font-style设置字体的倾斜样式text-decoration给文本添加装饰线text-indent设置文本的缩进text-align设置文本的对齐方式line-height设置行高color设置文本的颜色…

Shell脚本基础教程

Shell脚本基础教程 Shell参数定义 定义变量 想要定义变量&#xff0c;只需要使用如下命令即可。 variable_namevariable_valuevariable_name表示变量名&#xff0c;variable_value表示变量值。注意&#xff0c;等号与变量名和变量值之间不能有空格。 变量名的命名需要遵循…

C语言入门_Day7 逻辑运算

目录&#xff1a; 前言 1.逻辑运算 2.优先级 3.易错点 4.思维导图 前言 算术运算用来进行数据的计算和处理&#xff1b;比较运算是用来比较不同的数据&#xff0c;进而来决定下一步怎么做&#xff1b;除此以外还有一种运算叫做逻辑运算&#xff0c;它的应用场景也是用来影…

电脑远程接入软件可以进行文件传输吗?快解析内网穿透

电脑远程接入软件的出现&#xff0c;让我们可以在两台电脑之间进行交互和操作。但是&#xff0c;很多人对于这些软件能否进行文件传输还存在一些疑问。下面的文章将解答这个问题。 1.电脑远程接入软件可以进行文件传输。传统上&#xff0c;我们可能会通过传输线或者移动存储设…

Redis在Java中的基本使用

本片将介绍 Redis 在 Java 中的基本使用 文章目录 1、使用jedis操作redis1.1、Jedis简介1.2、引入jedis的Maven依赖1.2、获取连接1.3、使用实例 2、对于JedisPooled的使用2.1、使用JedisPooled2.2、关于连接池 3、SpringBoot下使用Redis3.1、引入Maven依赖3.2、配置Redis连接3.…

RabbitMq:Topic exchange(主题交换机)的理解和使用

RabbitMq:Topic exchange(主题交换机)的理解和使用 在RabbitMq中&#xff0c;生产者的消息都是通过交换机来接收&#xff0c;然后再从交换机分发到不同的队列中去&#xff0c;在分发的过程中交换机类型会影响分发的逻辑&#xff0c;下面主要讲解一下主题交换机。 ​ 主题交换…

pycharm上传项目到github,版本管理

前提&#xff1a;下载git 设置Git路径 登录Github 此时自动打开浏览器&#xff0c;并打开连接页面&#xff0c;点击 Authorize GitHub。登录&#xff1a; 创建本地仓库 提交到Github 填写初始提交相关信息 origin&#xff0c;它们只是远程服务器的一个别名&#xff0c;否则你就…

NPM 创建和管理组织

目录 1、创建一个组织 2、将用户帐户转换为组织 3、组织中开启双因素身份验证 3.1 关于组织的双因素身份验证 3.2 先决条件 3.3 在您的组织中要求双因素身份验证 3.4 帮助已删除的成员和外部协作者重新加入您的组织 4、重命名组织 5、删除组织 1、创建一个组织 任何n…

“Spring管理JavaBean的过程及Bean的生命周期“

目录 引言1.弹簧容器2. Bean的生命周期2.1 配置javaBean2.2. 解析Bean的定义2.3 检查是否需要添加自己的功能2.4 初始化2.5 实现Aware接口2.6 扩展2.7. 销毁 3. 单例模式和原型模式3.1. 单例模式3.2. 原型模式 4. 总结 引言 Spring框架是一个非常流行的Java应用程序框架&#…

【【verilog典型电路设计之流水线结构】】

verilog典型电路设计之流水线结构 下图是一个4位的乘法器结构&#xff0c;用verilog HDL 设计一个两级流水线加法器树4位乘法器 对于流水线结构 其实需要做的是在每级之间增加一个暂存的数据用来存储 我们得到的东西 我们一般来说会通过在每一级之间插入D触发器来保证数据的联…

【AIGC】 快速体验Stable Diffusion

快速体验Stable Diffusion 引言一、安装二、简单使用2.1 一句话文生图2.2 详细文生图 三、进阶使用 引言 stable Diffusion是一款高性能的AI绘画生成工具&#xff0c;相比之前的AI绘画工具&#xff0c;它生成的图像质量更高、运行速度更快&#xff0c;是AI图像生成领域的里程碑…

Linux/Ubuntu 的日常更新,如何操作?

我安装的是Ubuntu 20.04.6 LTS的Windows上Linux子系统版本&#xff0c;启动完成后显示&#xff1a; Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.90.4-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.c…

Azure不可变Blob存储

文章目录 Azure不可变Blob存储介绍Azure不可变性策略实战演练 Azure不可变Blob存储介绍 不可变的存储是一种用于存储业务关键型 Blob 数据的存储方式。与可变存储相反&#xff0c;不可变存储的特点是一旦数据被写入后&#xff0c;便无法再对其进行修改或删除。这种存储方式提供…

【2023最新爬虫】爬取知乎任意问题下的全部回答

老规矩&#xff0c;先上结果&#xff1a; 爬取了前200多页&#xff0c;每页5条数据&#xff0c;共1000多条回答。&#xff08;程序设置的自动判断结束页&#xff0c;我是手动break的&#xff09; 共爬到13个字段&#xff0c;包含&#xff1a; 问题id,页码,答主昵称,答主性别,…