精华内容
下载资源
问答
  • Java分布式锁

    万次阅读 2020-08-01 13:34:14
    1 分布式锁介绍 1.1 什么是分布式 一个大型的系统往往被分为几个子...现实生活中,当我们需要保护一样东西的时候,就会使用锁。例如门锁,车锁等等。很多时候可能许多人会共用这些资源,就会有很多个钥匙。但是有些

    1 分布式锁介绍

    1.1 什么是分布式

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

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

    1.2 什么是锁--作用安全

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

    JAVA程序中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量,而同步的本质是通过锁来实现的。如 Java 中 synchronize 是在对象头设置标记,Lock 接口的实现类基本上都只是某一个 volitile 修饰的 int 型变量其保证每个线程都能拥有对该 int 的可见性和原子修改

    1.4 什么是分布式锁

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

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

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

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

    场景一:

    场景二:

    1.5 分布式锁的执行流程

    1.6 分布式锁具备的条件

    • 互斥性:同一时刻只能有一个服务(或应用)访问资源,特殊情况下有读写锁

    • 原子性:一致性要求保证加锁和解锁的行为是原子性的

    • 安全性:锁只能被持有该锁的服务(或应用)释放

    • 容错性:在持有锁的服务崩溃时,锁仍能得到释放避免死锁

    • 可重用性:同一个客户端获得锁后可递归调用---重入锁和不可重入锁

    • 公平性:看业务是否需要公平,避免饿死--公平锁和非公平锁

    • 支持阻塞和非阻塞:和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)---阻塞锁和非阻塞锁==PS:::自选锁==

    • 高可用:获取锁和释放锁 要高可用

    • 高性能:获取锁和释放锁的性能要好

    • 持久性:锁按业务需要自动续约/自动延期

    2.分布式锁的解决方案

    2.1 数据库实现分布式锁

    2.1.1 基于数据库表实现

    创建表:

    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.itheima</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", "ybbmysql", "ybbmysql");
                //关闭自动提交
                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();
                }
            }
        }
    }

    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

    实现流程

     

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

    展开全文
  • 本文介绍下分布式锁的一个使用场景 分享本文的缘由是因为今天在写代码时需要处理一个原子性问题,场景是:业务功能需要先查询数据,再根据数据判断是否要更新数据,在这个查询+更新的过程必然会存在高并发下的原子性...

    前言

    本文介绍下分布式锁的一个使用场景
    分享本文的缘由是因为今天在写代码时需要处理一个原子性问题,场景是:业务功能需要先查询数据,再根据数据判断是否要更新数据,在这个查询+更新的过程必然会存在高并发下的原子性问题

    那么如何解决这个问题呢,那么就要说到我们的主角:分布式锁了

    分布式锁介绍

    分布式锁:即在多集群多节点环境下确保只有一个线程可以拿到锁,防止并发出现的问题,类似于synchronized,只不过synchronized不能处理多节点的问题

    解决上述问题的一种解决方式就是使用分布式锁,虽然性能会比较低,但是笔者的场景是一个统计功能,并且是异步的,所以并不影响性能
    核心代码如下:

    场景介绍

    try {
    	// 这里可以根据业务场景做分段锁,可以适当提升性能
    	String lock = "lock_key";
    	long keyValue = System.currentTimeMillis();
    	// 获取锁,超时时间60秒每隔0.5秒尝试获取一次,60秒后没获取到则放弃
    	boolean getLock = redisUtils.getLockByInterval(lock,keyValue,60,0.5,0);
    	if(getLock) {
    		xxx
    	}
    } catch (Exception e) {
      log.error("异常:", e);
    } finally {
        if(getLock) {
            redisUtils.unLock(lock,keyValue);
        }
    }
    

    RedisUtils类getLockByInterval

      /**
         * 获取锁,如果没获取到那么每隔interval秒重试一次,重试直到timeout秒
         * @param key
         * @param value
         * @param timeout 超时时间(单位秒)
         * @param interval 超时时间(单位秒)
         * @param currentTime 当前时间,调用时传0,只用于递归时间
         * @return
         */
        public boolean getLockByInterval(String key,Object value,int timeout,double interval,double currentTime) {
            if(currentTime > timeout) {
                return false;
            }
            // 相当于redis的setnx命令
            boolean getLock = redisTemplate.opsForValue().setIfAbsent(key,value,timeout, TimeUnit.SECONDS);
            if(!getLock) {
                //没有拿到锁就间隔interval再次尝试获取锁,直到总时间大于timeout
                try {
                    Thread.sleep(Double.valueOf(interval*1000).longValue());
                } catch (InterruptedException e) {
                    log.error("RedisUtils getLockByInterval error:",e);
                }
                currentTime += interval;
                // 递归
                return getLockByInterval(key,value,timeout,interval,currentTime);
            }
            return true;
        }
    

    上述代码其实还是不够完美,当并发量足够大时可能存在某线程在超时时间内还是没有抢占到锁,因为获取锁的机制是按照间隔时间来获取的,并且属于非公平锁,即不是先到的线程有权利优先获取锁,这里可以看到redis的分布式锁并不是很友好,这里再介绍下zookeeper的分布式锁

    分布式锁对比

    redis分布式锁:通过redis通过的sexNx命令实现,即当key不存在时调用setNx返回true,否则返回false,获取不到锁的线程只能轮询去尝试获取锁
    优点:性能高,使用简单,在允许偶发锁失效的场景下推荐使用
    缺点:通过轮询抢占锁的机制不是很可靠,当某线程占用锁时间较长时可能导致其他线程抢占锁失败

    zookeeper分布式锁:zk的分布式锁机制是利用zk的临时有序节点,即多个线程同时抢占锁会创建多个节点如a1->a2->a3->a4->a5…,只有拿到第一个节点的线程获得锁,里面节点注册是的watch机制,即a1使用完后会释放当前节点,同时watch下一个节点a2,依次类推
    优点:不依靠轮询抢占锁,依靠的是节点间的通信,比较可靠,当业务场景要求比较高是推荐使用
    缺点:性能不如redis缓存锁高

    展开全文
  • Redis命令SETNX的使用(包含Java分布式锁实现) 可以参考Redis官网对SETNX命令的介绍: https://redis.io/commands/setnx SETNX命令简介 命令格式 SETNX key value 将 key 的值设为 value,当且仅当 key ...

    Redis命令SETNX的使用(包含Java分布式锁实现)

    可以参考Redis官网对SETNX命令的介绍:

    https://redis.io/commands/setnx

    SETNX命令简介

    命令格式

    SETNX key value

    将 key 的值设为 value,当且仅当 key 不存在。 
    若给定的 key 已经存在,则 SETNX 不做任何动作。 
    SETNX 是SET if Not eXists的简写。

    返回值
    返回整数,具体为 

     - 1,当 key 的值被设置 
    - 0,当 key 的值没被设置

    例子

    redis> SETNX mykey “hello” 
    (integer) 1 
    redis> SETNX mykey “hello” 
    (integer) 0 
    redis> GET mykey 
    “hello” 
    redis>

    使用SETNX实现分布式锁
    多个进程执行以下Redis命令:

    SETNX lock.foo <current Unix time + lock timeout + 1>

    如果 SETNX 返回1,说明该进程获得锁,SETNX将键 lock.foo 的值设置为锁的超时时间(当前时间 + 锁的有效时间)。 
    如果 SETNX 返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断地尝试 SETNX 操作,以获得锁。

    解决死锁
    考虑一种情况,如果进程获得锁后,断开了与 Redis 的连接(可能是进程挂掉,或者网络中断),如果没有有效的释放锁的机制,那么其他进程都会处于一直等待的状态,即出现“死锁”。

    上面在使用 SETNX 获得锁时,我们将键 lock.foo 的值设置为锁的有效时间,进程获得锁后,其他进程还会不断的检测锁是否已超时,如果超时,那么等待的进程也将有机会获得锁。

    然而,锁超时时,我们不能简单地使用 DEL 命令删除键 lock.foo 以释放锁。考虑以下情况,进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。进程P2,P3正在不断地检测锁是否已释放或者已超时,执行流程如下:

    • P2和P3进程读取键 lock.foo 的值,检测锁是否已超时(通过比较当前时间和键 lock.foo 的值来判断是否超时)
    • P2和P3进程发现锁 lock.foo 已超时
    • P2执行 DEL lock.foo命令
    • P2执行 SETNX lock.foo命令,并返回1,即P2获得锁
    • P3执行 DEL lock.foo命令将P2刚刚设置的键 lock.foo 删除(这步是由于P3刚才已检测到锁已超时)
    • P3执行 SETNX lock.foo命令,并返回1,即P3获得锁
    • P2和P3同时获得了锁

    从上面的情况可以得知,在检测到锁超时后,进程不能直接简单地执行 DEL 删除键的操作以获得锁。

    为了解决上述算法可能出现的多个进程同时获得锁的问题,我们再来看以下的算法。 
    我们同样假设进程P1已经首先获得了锁 lock.foo,然后进程P1挂掉了。接下来的情况:

    • 进程P4执行 SETNX lock.foo 以尝试获取锁
    • 由于进程P1已获得了锁,所以P4执行 SETNX lock.foo 返回0,即获取锁失败
    • P4执行 GET lock.foo 来检测锁是否已超时,如果没超时,则等待一段时间,再次检测
    • 如果P4检测到锁已超时,即当前的时间大于键 lock.foo 的值,P4会执行以下操作 
    • GETSET lock.foo <current Unix timestamp + lock timeout + 1>
    • 由于 GETSET 操作在设置键的值的同时,还会返回键的旧值,通过比较键 lock.foo 的旧值是否小于当前时间,可以判断进程是否已获得锁
    • 假如另一个进程P5也检测到锁已超时,并在P4之前执行了 GETSET 操作,那么P4的 GETSET 操作返回的是一个大于当前时间的时间戳,这样P4就不会获得锁而继续等待。注意到,即使P4接下来将键 lock.foo 的值设置了比P5设置的更大的值也没影响。

    另外,值得注意的是,在进程释放锁,即执行 DEL lock.foo 操作前,需要先判断锁是否已超时。如果锁已超时,那么锁可能已由其他进程获得,这时直接执行 DEL lock.foo 操作会导致把其他进程已获得的锁释放掉。

     

    使用SETNX命令结合Java应用实现分布式锁

    项目使用SETNX实现分布式锁原因:

    由于线上所有的微服务都是采用两个服务节点来进行部署的,正常情况下不会出现两个服务节点同时执行同一个方法,即同一段业务逻辑,遇到这样一种情况,在部署的微服务中存在定时任务,也就是在将来的某个时间点会触发这个任务自动执行,那么这时候两个服务节点在这个时间点都会开启一个线程去执行相同的代码逻辑,显然这是违反业务逻辑的,即重复执行了一样的逻辑,因此在这种情况下就需要加入分布式锁来解决这样的定时任务场景问题。

    其中一个定时任务代码:

    package com.huajin.usercenter.task;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import com.huajin.common.util.DateUtil;
    import com.huajin.usercenter.enums.CertStatus;
    import com.huajin.usercenter.po.CertInfoPo;
    import com.huajin.usercenter.service.seal.CertService;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Component
    @Slf4j
    public class CertTask {
    	
    	@Autowired
    	private CertService certService;
    	
    	@Scheduled(cron = "0 0 0 * * ?")
        public void updateAuthCode() {
    		Map<String, Object> map = new HashMap<>();
    		map.put("status", CertStatus.NotDownload.getValue());
    		map.put("updateTimeEnd", DateUtil.add(new Date(), Calendar.DAY_OF_YEAR, -10));
    		List<CertInfoPo> list = certService.select(map);
    		for(CertInfoPo po : list) {
    			try {
    				certService.updateAuthCode(po.getSn());
    			} catch (Exception e) {
    				log.error("", e);
    			}
    		}
        }
    	
    }
    

    介绍了使用分布式锁的场景,那么怎么使用Redis提供的SETNX命令实现分布式锁呢?

    由于项目中不止存在一个定时任务,且使用分布式锁不属于主业务逻辑部分,因此采用了AOP面向切面编程,项目中存在的定时任务均在task包下,还有一个特点就是所有的定时任务方法都有Spring提供的定时任务注解@Scheduled

    ,考虑到这里我们便知道了切点就是两者的结合,为了保险起见,采用&&符号连接这两个条件,接下来就是考虑使用何种通知方式,考虑到线程执行的快慢,定时任务方法时刻都在两个服务节点开启的定时任务线程争夺中,因此在这里使用@Around注解,即环绕通知实现。

    在这里特别强调一下@Around环绕通知去其它几个通知的区别,@Around环绕通知接受的是ProceedingJoinPoint接口的实现类的一个实例,该ProceedingJoinPoint接口继承自JoinPoint接口,ProceedingJoinPoint接口比JoinPoint接口多暴露了两个方法,如下所示:

    接下来便是使用RedisTemplate提供的API来操作Redis命令SETNX实现分布式锁,

    package com.huajin.usercenter.task;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Aspect
    @Component
    @Slf4j
    public class TaskAspect {
    	
    	@Autowired
    	private RedisTemplate<Object, Object> redisTemplate;
    	
    	@Around("execution(* com.huajin.usercenter.task..*.*(..)) && @annotation(org.springframework.scheduling.annotation.Scheduled)")
    	public Object task(ProceedingJoinPoint point) {
    		String methodName = point.getTarget().getClass().getName() + "." + point.getSignature().getName();
    		Boolean result = redisTemplate.opsForValue().setIfAbsent(methodName, methodName);
    		if(result != null && result) {
    			try {
    				return point.proceed();
    			} catch (Throwable e) {
    				log.error("", e);
    			} finally {
    				redisTemplate.delete(methodName);
    			}
    		}
    		return null;
    	}
    	
    }
    

    以上两个服务节点开启的定时任务线程中的某个线程在key不存在的情况下则相当于与获取到了锁,待目标方法执行完毕后或者抛出异常都可以删除这个key,即也就是释放锁,达到了分布式锁的目的。

     

    参考:

    https://blog.csdn.net/lihao21/article/details/49104695


     

    展开全文
  • 分布式锁之 redis分布式锁和zookeeper分布式锁的实现原理 以及它们各自使用场景 文章目录 前言:在分布式系统的时代中,微服务、服务治理、中间件等名次已经贯穿了我们开发的各个过程,给开发者们提供了很多便捷...

    文章目录

    • 前言:在分布式系统的时代中,微服务、服务治理、中间件等名次已经贯穿了我们开发的各个过程,给开发者们提供了很多便捷的方式去更好的开发大型的web系统,这篇文章,主要给大家简单说一下分布式锁。
    • 一、什么是分布式锁?
    • 二、分布式锁给我们解决哪些问题?
    • 三、分布式锁实现原理
      • redis分布式锁 --- 基于jedis实现
      • zookeeper分布式锁 --- 基于zkclient实现
      • 它们的不同之处

    前言

    举个栗子:在我刚毕业那会2016年,我本人还沉浸在单体的SSM架构中,写个Schyoronized给某服务层某个业务共享实栗加把锁,这就ok了,确实,在我们常见的xxx管理系统中,没有那么大的数据量,也没必要去引入中间件去搞。但,时代不同了,无论从tob端或者toc端,目前大型的系统网站,在开发初期,在技术选型上,都在追求或者考虑未来的高并发、高可用的场景。往往单一的jvm实例子便可通过传统的加锁方式去实现,但若是一个服务部署在多台机器上呢,或者是多个服务都在修改应用层某个共享实例呢。此时,我们的分布式锁便派上用场了。

     

    一、什么是分布式锁

    分布式锁顾名思义,在分布式系统下,在高并发的场景下,我们为了协调资源不被随意修改而做的对系统共享资源的保护。

    举个例子:用户下单后减库存,用户A请求应用服务器,对productA商品下单并去创建了新的订单,库存系统同步去减库存,若此时用户B也去请求该商品,并去下单,但用户B可能请求的不是一个服务器,因为在分布式环境下,一个服务可能部署在多台主机上,那么B此时拿到的库存数量可能还是之前的那个数量,这就可能导致超卖情况发生。

    以上只是简单的一个说明,用户下单和减库存,还要考虑到的东西很多。

    二、解决了哪些问题

    协调各个服务节点去修改共享资源,保证数据正确性。

    三、分布式锁实现原理

    (1)redis分布式锁

    原理:利用redis的setnx、expire、getset命令对应的提供的API函数接口实现;

       获取redis锁步骤如下:

    • a、线程A获取redis锁,通过setnx(key, 当前时间戳+key的失效时间)命令,若返回1,则redis缓存中不存在该key,则获得锁;
    • b、若a中存在该key,则继续循环判断,重复的去获取该key对应value,通过get(key)获取;
    • c、若b中获取到的value值 < 当前时间,则表明该key已经在缓存中失效了,线程A已经有了获取redis锁的权利了;
    • d、此时线程A通过getset命令,将当前时间戳设置到该key对应的缓存上,并返回旧的值;
    • e、若返回的旧值 == 在b中获取的值,则此时线程A成功获取到锁;
    • f、若不相等,则表明有其他线程在线程A之前已经获取到该锁,则线程A又要循环往复的在去判断;

    总而言之,自我感觉有点像自旋锁的意思,通过CAS不断去比较当前修改的旧值和期待的值是否一致;

    以下是redis锁的实现原理,包含了释放锁的过程;

    public class RedisKeyLock {
        private static Logger logger = Logger.getLogger(RedisKeyLock.class);
        private final static long ACCQUIRE_LOCK_TIMEOUT_IN_MS = 10 * 1000;
        private final static int EXPIRE_IN_SECOND = 5;//锁失效时间
        private final static long WAIT_INTERVAL_IN_MS = 100;
        private static RedisKeyLock lock;
        private JedisPool jedisPool;
        private RedisKeyLock(JedisPool pool){
            this.jedisPool = pool;
        }
        public static RedisKeyLock getInstance(JedisPool pool){
            if(lock == null){
                lock = new RedisKeyLock(pool);
            }
            return lock;
        }
    
        public void lock(final String redisKey) {
            Jedis resource = null;
            try {
                long now = System.currentTimeMillis();
                resource = jedisPool.getResource();
                long timeoutAt = now + ACCQUIRE_LOCK_TIMEOUT_IN_MS;
                boolean flag = false;
                while (true) {
                    String expireAt = String.valueOf(now + EXPIRE_IN_SECOND * 1000);
                    long ret = resource.setnx(redisKey, expireAt);
                    if (ret == 1) {//已获取锁
                        flag = true;
                        break;
                    } else {//未获取锁,重试获取锁
                        String oldExpireAt = resource.get(redisKey);
                        if (oldExpireAt != null && Long.parseLong(oldExpireAt) < now) {
                            oldExpireAt = resource.getSet(redisKey, expireAt);
                            if (Long.parseLong(oldExpireAt) < now) {
                                flag = true;
                                break;
                            }
                        }
                    }
                    if (timeoutAt < now) {
                        break;
                    }
                  TimeUnit.NANOSECONDS.sleep(WAIT_INTERVAL_IN_MS);
                }
                if (!flag) {
                    throw new RuntimeException("canot acquire lock now ...");
                }
            } catch (JedisException je) {
                logger.error("lock", je);
                je.printStackTrace();
                if (resource != null) {
                    jedisPool.returnBrokenResource(resource);
                }
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("lock", e);
            } finally {
                if (resource != null) {
                    jedisPool.returnResource(resource);
                }
            }
        }
        public boolean unlock(final String redisKey) {
            Jedis resource = null;
            try {
                resource = jedisPool.getResource();
                resource.del(redisKey);
                return true;
            } catch (JedisException je) {
                je.printStackTrace();
                if (resource != null) {
                    jedisPool.returnBrokenResource(resource);
                }
                return false;
            } catch (Exception e) {
                logger.error("lock", e);
                return false;
            } finally {
                if (resource != null) {
                    jedisPool.returnResource(resource);
                }
            }
        }
    }

     

    (2)zookeeper分布式锁

    原理:利用zookeeper系统结构,即类似linux的树形文件目录结构,根节点下有很多子节点,服务在访问zk时,通过创建临时顺序节点方式,并去比较当前节点的序号是否为其中最小,若为最小,则获得锁,并通知后续节点,删除自身节点;否则,进入等待状态;

    以下是获取和释放的代码

    public class ZooKeeperDistributedLock implements Watcher{
    
        private ZooKeeper zk;
        private String locksRoot= "/locks";
        private String productId;
        private String waitNode;
        private String lockNode;
        private CountDownLatch latch;
        private CountDownLatch connectedLatch = new CountDownLatch(1);
    private int sessionTimeout = 30000;
    
        public ZooKeeperDistributedLock(String productId){
            this.productId = productId;
             try {
           String address = "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181";
                zk = new ZooKeeper(address, sessionTimeout, this);
                connectedLatch.await();
            } catch (IOException e) {
                throw new LockException(e);
            } catch (KeeperException e) {
                throw new LockException(e);
            } catch (InterruptedException e) {
                throw new LockException(e);
            }
        }
    
        public void process(WatchedEvent event) {
            if(event.getState()==KeeperState.SyncConnected){
                connectedLatch.countDown();
                return;
            }
    
            if(this.latch != null) {
                this.latch.countDown();
            }
        }
    
        public void acquireDistributedLock() {
            try {
                if(this.tryLock()){
                    return;
                }
                else{
                    waitForLock(waitNode, sessionTimeout);
                }
            } catch (KeeperException e) {
                throw new LockException(e);
            } catch (InterruptedException e) {
                throw new LockException(e);
            }
    }
    
        public boolean tryLock() {
            try {
             // 传入进去的locksRoot + “/” + productId
            // 假设productId代表了一个商品id,比如说1
            // locksRoot = locks
            // /locks/10000000000,/locks/10000000001,/locks/10000000002
                lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    
                // 看看刚创建的节点是不是最小的节点
             // locks:10000000000,10000000001,10000000002
                List<String> locks = zk.getChildren(locksRoot, false);
                Collections.sort(locks);
    
                if(lockNode.equals(locksRoot+"/"+ locks.get(0))){
                    //如果是最小的节点,则表示取得锁
                    return true;
                }
    
                //如果不是最小的节点,找到比自己小1的节点
          int previousLockIndex = -1;
                for(int i = 0; i < locks.size(); i++) {
            if(lockNode.equals(locksRoot + “/” + locks.get(i))) {
                         previousLockIndex = i - 1;
                break;
            }
           }
    
           this.waitNode = locks.get(previousLockIndex);
            } catch (KeeperException e) {
                throw new LockException(e);
            } catch (InterruptedException e) {
                throw new LockException(e);
            }
            return false;
        }
    
        private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException {
            Stat stat = zk.exists(locksRoot + "/" + waitNode, true);
            if(stat != null){
                this.latch = new CountDownLatch(1);
                this.latch.await(waitTime, TimeUnit.MILLISECONDS);                   this.latch = null;
            }
            return true;
    }
    
        public void unlock() {
            try {
            // 删除/locks/10000000000节点
            // 删除/locks/10000000001节点
                System.out.println("unlock " + lockNode);
                zk.delete(lockNode,-1);
                lockNode = null;
                zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
    }
    
        public class LockException extends RuntimeException {
            private static final long serialVersionUID = 1L;
            public LockException(String e){
                super(e);
            }
            public LockException(Exception e){
                super(e);
            }
    }
    
    // 如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁,后面的每个人都会去监听排在自己前面的那个人创建的node上,一旦某个人释放了锁,排在自己后面的人就会被zookeeper给通知,一旦被通知了之后,就ok了,自己就获取到了锁,就可以执行代码了
    
    }

    (3)不同之处:redis分布式锁需要不断重试去获取,比较消耗性能,zk锁不需要,只需要注册一个监听器,等待前节点的通知;

    同时,redis服务若是挂掉了,锁只能等待超时时间后才可再被其他线程获得;而zk服务若是挂掉了,则会自动删除临时节点并释放锁,总体开销较小;

     

    注:第一次写博客,有哪些错误之处,还请指正并谅解。

     

     

     

     

     

     

     

     

     

    展开全文
  • Java分布式锁那点事

    千次阅读 2017-11-20 19:29:07
    分布式锁那点事为什么要使用分布式锁为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制...
  • 分布式锁是保护分布式系统中的多个进程对于某个共享资源的使用 分布式锁所需的特点 高可用的获取和释放; 高性能的获取和释放; 具备可重入性; 具备失效机制,防止死锁; 具备非阻塞特性,即没有获取...
  • java 分布式锁方案

    2016-09-08 21:23:45
    第一步,自身的业务场景: 在我日常做的项目中,目前涉及了以下这些业务场景: 场景一: 比如分配任务场景。在这个场景中,由于是公司的业务后台系统,主要是用于审核人员的审核工作,并发量并不是很高,而且...
  • 这里写目录标题业务场景无锁情况下下单单进程jvm加锁分布式无锁分布式锁redis分布式锁基于zookeeper实现分布式锁 业务场景 在实际开发过程中,会遇到下单操作,这里涉及到库存问题。 无锁情况下下单 假如现在有一个...
  • 以前一直用redis作为分布式锁的实现,也知道zookeeper可以实现,但是对于分布式锁没有系统梳理,忽略了数据库作为分布式锁的重要应用,本文主要梳理分布式锁实现的主要思路: 按照加锁位置,分为在应用层,缓存层,...
  • Config config = new Config(); config.useClusterServers() .addNodeAddress("redis://192.168.31.101:7001") .addNodeAddress("redis://192.168.31.101:7002") .addNodeAddress("redis://192.168.31.101:7003") ...
  • JAVA分布式锁

    千次阅读 2019-05-31 13:31:49
    Java分布式锁的实现与原理#什么是分布式锁为什么需要用分布式锁功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容...
  • java实现分布式锁

    2020-02-19 22:16:19
    但现在我们的服务基本都是基于分布式集群来实现部署的,对于一些共享资源,例如我们之前讨论过的库存,在分布式环境下使用 Java 的方式就失去作用了。 这时,我们就需要实现分布式锁来保证共享资源的原子性。除此...
  • java实现redis分布式锁实例

    万次阅读 2017-04-25 22:35:00
    无意中发现了一个巨牛的...java实现redis分布式锁 应用场景:多并发 特点:分布式锁、动态解决由redis宕机产生死锁的情况,基于wait()、notify()有效提高效率节省资源 Junit类,其中 testTryLock 包含多线...
  • 三种实现分布式锁的方式

    万次阅读 多人点赞 2018-06-14 15:01:57
    一、为什么要使用分布式锁我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug!注意这是单机应用,也就是...
  • 分布式场景下数据一致性的问题——【分布式Java常用技术方案.pdf
  • 我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的...推荐文章:Java 面试知识点解析;Mysql优化技巧(数据库设计、命名规范、索引优化 原文出自:https://blog.csdn.net/seesun2012 ...
  • 不适用分布式锁会怎样? 以抢购商品的高并发场景为例,通常单体的应用可以通过同步代码来实现顺序对数据库的操作,保证程序是...使用redis实现分布式锁主要是使用其SETNX操作:SETNX [key] [value],当key不存在时,
  • Redis分布式锁的正确实现方式(Java版)

    万次阅读 多人点赞 2019-05-19 22:57:03
    ... 一、什么是分布式锁? 要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。 线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,...
  • 最近由于工作很忙,很长时间没有更新博客了,今天为大家带来一篇有关...Redisson的分布式可重入RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁。 public void testReentrantLo
  • 分布式锁】三种分布式锁的实现【原创】

    千次阅读 多人点赞 2021-03-02 22:55:58
    三种分布式锁的实现,Redis分布式锁,数据库,Zookeeper分布式锁,主要介绍的是Redis分布式锁
  • Java 基于 Redis的分布式锁 Redisson基本用法用分布式锁的原因主要使用场景Maven Jar包Redisson配置RedissonController.java分布式锁的原因 在高并发场景上选型分布式服务架构时, 接口访问负载分散导致无法保证...
  • 文章目录案例 案例 秒杀、大促之类的活动,一个共同特点就是访问量激增,在高并发下会出现成千上万人抢购一个商品的场景。...Java 语言给我们提供了线程,开放了处理机制的 API,比如 Synchronized、
  • java+Redis实现分布式锁

    千次阅读 2019-02-16 16:10:07
    java+Redis实现分布式锁 前言 分布式锁一般有三种实现方式:1. 数据库乐观;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍...
  • 分布式锁要解决的场景使用方法

    千次阅读 2020-06-01 13:52:19
    如果是单机应用中,对于同一个对象的变量的访问,可以通过JUC的机制来控制同一时间只有一个线程来拿到资源或者改变资源,但是在分布式的、可能同一个应用还是集群部署的情况下,那么一个对象可能在多台机器的内存...
  • 在博客“zookeeper实现分布式锁的两种方式”中介绍了分布式锁使用场景,以及如何用zookeeper分别实现简单和高性能的分布式锁,这里就不再重复介绍分布式锁场景,今天主要给大家带来另外两种实现分布式锁的方式--...
  • Redisson单进程Redis分布式悲观使用与实现 本文基于Redisson 3.7.5 3. 读写 Redisson的分布式可重入读写RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。同时还支持...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 182,930
精华内容 73,172
关键字:

java分布式锁使用场景

java 订阅