Java开发 - 不知道算不算详细的分布式锁详解

Java 分布式锁:Redisson、Zookeeper、Spring 提供的 Redis 分布式锁封装详解 阅读详情

前言

今天给大家带来一篇关于分布式锁的好文,关于分布式系统下的内容,博主已经写了好几篇了,也收获了大家的不少好评。分布式系统在目前的开发中所占的比重还是比较大的,如果你还没接触过分布式系统,那么欢迎你去学习博主的微服务专栏,如果你对微服务略知一二,那么博主近期的挖祖坟系列就很适合你。让我们一起在Java这方土地上挖呀挖呀挖吧!

分布式锁

锁概念

代码中的锁其实和我们现实生活中很像,我们使用锁的目的是为了保证人身和财产的安全,这和在代码中的使用其实本质是一样的。

锁的使用是,我们希望的情况是:谁上的锁,由谁来打开,这样是最安全的情况,不管是外面上锁,还是进入室内上锁,都是如此。

在代码中,当多个线程同时才做一个变量的时候,就需要过程是同步的,操作是互斥的,而其本质就是锁的思想。

分布式锁概念

任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。这个我们在讲分布式事务的时候就已经给大家讲过了,关于CAP我不允许你还不知道,传送门:Java开发 - 不知道算不算详细的分布式事务详解​​​​​​​

分布式锁要做的事情就是在多个线程同时去修改数据的时候保证数据的安全,而同一时间只能有一个线程操作此数据就是我们所说的安全。操作时上锁,用完解锁,把下一个获取锁的机会留给其他人。

这让博主想到了生活中一个比较有味道的事情:蹲坑!谁也不希望在自己蹲坑的时候其他人可以进来和你一起方便吧?这个案例和分布式锁的思想高度吻合,有木有?有木有?哈哈哈哈~~ 

分布式锁的特点

分布式锁的特点其实很简单,很好理解,我们来列举一下:

  • 互斥性:同一时刻只能有一个服务在访问资源
  • 原子性:一致性要求保证加锁和解锁的行为是原子的,原子性指最小颗粒,不能被中断的一个或一系列操作
  • 安全性:谁上锁,谁释放
  • 容错性:当持有锁的服务崩溃时,锁仍能被释放,这样才能避免死锁
  • 高可用:高可用一般我们用来说明分布式系统,此处表示获取锁和释放锁是安全稳定的,也包括具有容错的能力
  • 高性能:获取锁和释放锁的性能要好,因为一些操作属于低效锁,下面会讲
  • 持久性:锁按业务需要自动续约/自动延期

分布式锁的应用场景

关于分布式锁的使用场景,简单列举几个比较常见的:

  • 银行取款和手机转账同时进行,你看看能不能能不能获取双倍的钱,显然不能,就需要分布式锁
  • 集群下不同的集群的用户去进行商品的秒杀,商品库存只有一个,那么显然只能有一个人秒杀成功,就需要用到分布式锁

我相信大家完全能想象到这个过程,所以关于其流程图就不再画出来了。

分布式锁的执行过程

虽然博主非常不想去画图,但是这里必须要有图了,文字再多,都不及一张逻辑清晰的流程图,下面,我们用一张图来说明分布式锁的执行流程:

看完有没有很简单,和你想的一样吗?不用把它想的那么难,其实就很简单啊!!! 

数据库实现分布式锁

关于实现,其实还是要根据具体的业务来做的,所以博主不仅是这里,包括下面,如果涉及具体的业务,就不写具体代码了,如果是通用的,回忆代码的形式展现给大家。

表锁实现

根据表锁实现大致可分为以下几个步骤:

  • 创建一张表,不需要有太多的参数,比如:​​​​​​​
    CREATE TABLE tb (
      check_no varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
      PRIMARY KEY (check_no) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    
    SET FOREIGN_KEY_CHECKS = 1;

    SET FOREIGN_KEY_CHECKS=0; //关闭外键约束检查

  • SET FOREIGN_KEY_CHECKS=1; //开启外键约束检查

  • 访问数据时,将一个编号存入到这张表中
  • 只要insert成功,就代表获取锁成功,反之获取锁失败
  • 由于主键冲突,一张表中只能存入一个相同的编号,锁就这样产生了
  • 获取锁成功的程序在逻辑执行完以后,删除该编号的数据,就代表释放锁了,这是其他的线程就可以来竞争这个锁,谁insert成功,谁就获取了锁,可以执行自己的任务

这张表是博主看过别人创建的,个人感觉参数设置略复杂了,从各方面来说,一张简单的表就足以实现了,希望和大家一起讨论下原因,还请大家不吝赐教。

条件实现

根据条件查询是以行锁为前提的,这个条件和下面的版本号有异曲同工之妙,先来说说条件查询。我们先来看看下面这句SQL:

update tb_stock set stock=stock-#{saleNum} where id = #{id} and stock-#{saleNum}>=0"

大家应该知道,在innoDB中,update操作就是行锁的标志,所以当使用了行锁,我不管有多少个线程去操作这条数据,但同一时间只能有一个线程在操作,总库存减去购买数量后的剩余库存一定是要>=0的,这就保证了商品不会超卖。这是乐观锁的思想,但乐观锁并不是不加锁,他只是一种思想,仅此而已,你只需要这种方法可行。乐观锁有两种思想,就是版本号和CAS,显然这里是CAS的做法。

关于乐观锁和悲观锁,博主就不细说了,有不了解的点击此链接查看了解:Java开发 - 数据库中的基本数据结构

版本号实现

版本号的实现和条件很像, 当没有判断依据的时候,就可以添加一个version字段,我们来看以下SQL:

update tb_user set name=#{name},version=version+1 where id=#{id} and version=#{version}

看完之后,其实感觉版本号和条件原理是一模一样的, 都是基于行锁原理来完成的,但他们存在一定问题,我们在下面来说。

数据库实现分布式锁缺点

从以上我们不难看出,它的缺点很明显,首先就是频繁的操作数据库,这一点肯定不好,另外,分布式锁还需要具备可重入,失效时间等特性,不仅如此,数据库本身的业务也很繁忙,性能上多少还是存在一定的问题。

以上问题也并不是不能解决,但解决这些问题,实现方式将会越来越复杂,性能问题也不得不考虑。如果是单机系统还可以考虑,既然都上分布式了,那何必使用这种方式呢?

zookeeper实现分布式锁

实现原理

zookeeper能够实现分布式锁得益于其设计者的巧妙设计,主要是使用了zookeeper的znode节点特性和watch机制来完成。

znode节点

说起来也很简单,znode节点共分为两大类,分别是:临时节点和持久节点,他们各自又分为两类:分别是有序节点和无序节点,看下方详细说明:

  • 持久节点:一旦创建,永久存在于zookeeper中,除非手动删除
  • 持久有序节点:一旦创建,永久存在于zookeeper中,除非手动删除。不同点是每个节点都默认存在节点序号,且是有序递增的
  • 临时节点:节点创建后,一旦服务器重启或宕机,则被自动删除
  • 临时有序节点:节点创建后,一旦服务器重启或宕机,则被自动删除。不同的地方是每个节点默认存在节点序号,且有序递增

watch监听机制

​watch监听机制主要用于监听节点的状态变更,并用于触发后续事件,当B节点监听A节点时,一旦A节点发生修改、删除、子节点列表发生变更等事件,B节点则会收到A节点改变的通知,接着完成其他任务,在这里指B监听到A释放了锁,就尝试去获取锁。

工作方式

虽然有序节点是有序的,但在创建第一个节点前,需要先创建一个父节点,然后在父节点下创建临时有序节点,之所以使用临时节点是为了保证服务器宕机或重启后,原先的锁能够被释放。随着节点的递增,那么最先获取到的锁的一定是序号最小的节点,任务完成后,删除该临时节点,接着去寻找下一个最小的节点,并且,监听机制是按照后一个监听前一个的规则来工作的,比如,B监听A,C监听B,D监听C,E监听D......以此类推,前一个完成后就会通知下一个节点去获取锁并执行任务,如下图:

锁思想实现(低效锁)

使用zookeeper时,有一种低效的实现方法也是很常见的,它的原理是只使用一个锁节点,当创建锁节点时,如果锁节点不存在,则创建成功,代表当前线程获取到锁,如果创建锁节点失败,代表已经有其他线程获取到锁,则该线程会监听锁节点的释放,释放就是删除此节点。当锁节点释放后,其他线程则继续尝试创建锁节点并加锁。

下面我们来做个模拟

创建一个新项目,pom文件如下:

<?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.6.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.codingfire</groupId>
    <artifactId>zookeeper_lock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zookeeper_lock</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--zookeeper-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.5</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

创建一个抽象类用于对外提供加解锁方法:

package com.codingfire.zookeeper_lock;

import org.I0Itec.zkclient.ZkClient;

public abstract class AbstractLock {

    //zookeeper服务器地址
    public static final String ZK_SERVER_ADDR="localhost:2181";

    //zookeeper超时时间
    public static final int CONNECTION_TIME_OUT=30000;
    public static final int SESSION_TIME_OUT=30000;

    //创建zk客户端
    protected ZkClient zkClient = new ZkClient(ZK_SERVER_ADDR,SESSION_TIME_OUT,CONNECTION_TIME_OUT);

    /**
     * 获取锁
     * @return
     */
    public abstract boolean tryLock();

    /**
     * 等待加锁
     */
    public abstract void waitLock();

    /**
     * 释放锁
     */
    public abstract void releaseLock();

    //加锁实现
    public void getLock() {

        String threadName = Thread.currentThread().getName();

        if (tryLock()){
            System.out.println(threadName+":   获取锁成功");
        }else {

            System.out.println(threadName+":   等待获取锁");
            waitLock();
            getLock();
        }
    }
}

创建实现抽象方法的子类:

package com.codingfire.zookeeper_lock;

import org.I0Itec.zkclient.IZkDataListener;

import java.util.concurrent.CountDownLatch;

public class LowLock extends AbstractLock {

    private static final String LOCK_NODE="/lock_node";

    private CountDownLatch countDownLatch;

    @Override
    public boolean tryLock() {
        if (zkClient == null){
            return false;
        }

        try {
            zkClient.createEphemeral(LOCK_NODE);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public void waitLock() {

        //注册监听
        IZkDataListener listener = new IZkDataListener() {

            //节点数据改变触发
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            //节点删除触发
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                if (countDownLatch != null){
                    countDownLatch.countDown();
                }
            }
        };
        zkClient.subscribeDataChanges(LOCK_NODE,listener);

        //如果节点存在,则线程阻塞等待
        if (zkClient.exists(LOCK_NODE)){
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await();
                System.out.println(Thread.currentThread().getName()+":  等待获取锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //节点不存在,删除监听
        zkClient.unsubscribeDataChanges(LOCK_NODE,listener);

    }

    @Override
    public void releaseLock() {
        System.out.println(Thread.currentThread().getName()+":    释放锁");
        zkClient.delete(LOCK_NODE);
        zkClient.close();

    }
}

 接着我们在启动类的main方法中来测试下以上代码:

package com.codingfire.zookeeper_lock;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.concurrent.TimeUnit;

@SpringBootApplication
public class ZookeeperLockApplication {

    public static void main(String[] args) {

        //模拟多个10个客户端
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(new LockRunnable());
            thread.start();
        }
    }

    private static class LockRunnable implements Runnable {
        @Override
        public void run() {

            AbstractLock abstractLock = new LowLock();

            abstractLock.getLock();

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            abstractLock.releaseLock();
        }
    }

}

 然后就可以运行测试了,不出意外,任务是一个个执行的,由于输出的时候还是打印了zookeeper的debug日志,导致页面很乱,所以就不截图了,大家自己运行看看结果就行,这日志还去不掉了,明明已经去掉了打印日志的工具:

<!--zookeeper-->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.5.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

有知道原因的童鞋可以给博主留言 。刚刚我们的代码是基于单节点实现的锁,那么就会导致一个问题:一旦锁释放,其他等待线程都会收到通知,并去竞争唯一的一个锁,这种由于被watch的znode节点的变化,而造成大量的通知操作就叫做羊群效应,会严重降低zookeeper性能。所以这种低效锁做法一般来说我们在分布式系统中不会使用。

zookeeper高效锁实现

为了解决低效锁的羊群问题,我们会让这些任务排队,这就会用到有序节点。有序节点中,最小的节点获取锁并执行任务,执行完毕后释放锁(删除节点),下一个watch的最小节点就会收到通知并获取锁,避免竞争,这就是高效锁,高效锁的实现过程如下:

高效锁的工具类也需要继承自抽象类:

package com.codingfire.zookeeper_lock;

import org.I0Itec.zkclient.IZkDataListener;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class HighLock extends AbstractLock {

    private static  String PARENT_NODE_PATH="";

    public HighLock(String parentNodePath){
        PARENT_NODE_PATH = parentNodePath;
    }

    //当前节点路径
    private String currentNodePath;

    //前一个节点的路径
    private String preNodePath;

    private CountDownLatch countDownLatch;

    @Override
    public boolean tryLock() {

        //判断父节点是否存在
        if (!zkClient.exists(PARENT_NODE_PATH)){
            //父节点不存在,创建持久节点
            try {
                zkClient.createPersistent(PARENT_NODE_PATH);
            } catch (Exception e) {
            }
        }

        //创建第一个临时有序节点
        if (currentNodePath==null || "".equals(currentNodePath)){
            //在父节点下创建临时有序节点
            currentNodePath =  zkClient.createEphemeralSequential(PARENT_NODE_PATH+"/","lock");
        }

        //不是第一个临时有序节点
        //获取父节点下的所有子节点列表
        List<String> childrenNodeList = zkClient.getChildren(PARENT_NODE_PATH);

        //因为有序号,所以进行升序排序
        Collections.sort(childrenNodeList);

        //判断是否加锁成功,当前节点是否为父节点下序号最小的节点
        if (currentNodePath.equals(PARENT_NODE_PATH+"/"+childrenNodeList.get(0))){
            //当前节点是序号最小的节点
            return true;
        }else {
            //当前节点不是序号最小的节点,获取其前置节点,并赋值
            int length = PARENT_NODE_PATH.length();
            int currentNodeNumber = Collections.binarySearch(childrenNodeList, currentNodePath.substring(length + 1));
            preNodePath = PARENT_NODE_PATH+"/"+childrenNodeList.get(currentNodeNumber-1);
        }

        return false;
    }

    @Override
    public void waitLock() {

        //注册监听
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                if (countDownLatch != null){
                    countDownLatch.countDown();
                }
            }
        };
        zkClient.subscribeDataChanges(preNodePath,listener);

        //判断前置节点是否存在,存在则阻塞
        if (zkClient.exists(preNodePath)){

            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //删除监听
        zkClient.unsubscribeDataChanges(preNodePath,listener);

    }


    @Override
    public void releaseLock() {

        zkClient.delete(currentNodePath);
        zkClient.close();
    }
}

调用方式如下:

    //获取当前方法名
    String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
    AbstractLock lock = new HighLock("/"+methodName);

    try {
        //加锁
        lock.getLock();

        //执行操作
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //释放锁
        lock.releaseLock();
    }

就是这么简单,以上也基本是业内普遍使用的封装类,差别不会很大,可直接使用。 在测试前,请确保你的zookeeper是开启的。

Redis实现分布式锁

单节点Redis锁

如果是单节点的redis,那么分布式锁的实现就相当简单了,因为redis本身是单线程的,基于这个特性,在并发情况下,各个请求都需要排队才能进入,同一时间只有一个线程可以进入并获取到锁。

redis实现分布式锁基于三个核心API:

  • setNx():向redis中存key-value,只有当key不存在时才会设置成功,否则返回0,体现了其互斥性
  • expire():设置key的过期时间,用于避免死锁出现
  • delete():删除key,用于释放锁

加锁

通过jedis.set进行加锁,如果返回值是OK,代表加锁成功,否则加锁失败,失败就自旋不断尝试获取锁,同时在一定时间内如果仍没有获取到锁,则退出自旋,不再尝试获取锁。加锁时需要使用 requestId来标识每个线程所持有的锁标记。

package com.codingfire.zookeeper_lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

public class SingleRedisLockUtil {

    JedisPool jedisPool = new JedisPool("localhost",6379);

    //锁过期时间
    protected long internalLockLeaseTime = 30000;

    //获取锁的超时时间
    private long timeout = 999999;

    /**
     * 加锁
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @return
     */
    SetParams setParams = SetParams.setParams().nx().px(internalLockLeaseTime);

    public boolean tryLock(String lockKey, String requestId){

        String threadName = Thread.currentThread().getName();

        Jedis jedis = this.jedisPool.getResource();

        Long start = System.currentTimeMillis();

        try{
            for (;;){
                String lockResult = jedis.set(lockKey, requestId, setParams);
                if ("OK".equals(lockResult)){
                    System.out.println(threadName+":   获取锁成功");
                    return true;
                }
                //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
                System.out.println(threadName+":   获取锁失败,等待中");
                long l = System.currentTimeMillis() - start;
                if (l>=timeout) {
                    return false;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }finally {
            jedis.close();
        }

    }
}

在创建类之前,你还需要添加几个依赖,否则会报错:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.0.1</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!--Redis分布式锁-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.1</version>
</dependency>

 

解锁

解锁也是有些学问的,要避免当前线程将别人的锁释放掉,当线程A加锁成功,一段时间后线程A来解锁,线程A的锁已经过期了,此时线程B也来加锁,因为线程A的锁已经过期,所以线程B可加锁成功。此时,线程A就有可能将线程B的锁给释放掉。这也是为什么加锁时要引入requestId的原因。所以当解锁时要判断当前锁键的value与传入的value是否相同,相同的话,则代表是同一个人,可以解锁。否则不能解锁。

​解锁时,如果先查询做对比,比对后相同再删除看着没错,但却有个坑,那就是原子性被忽略了,原子性要求我们这一系列的操作必须是一步完成的,否则在中间容易出现变故,所以查询和删除通常会用lua脚本完成。下面我们看看代码该怎么写:

在工具类中添加解锁方法:

/**
     * 解锁
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @return
     */
public boolean releaseLock(String lockKey,String requestId){

    String threadName = Thread.currentThread().getName();
    System.out.println(threadName+":释放锁");
    Jedis jedis = this.jedisPool.getResource();

    String lua =
        "if redis.call('get',KEYS[1]) == ARGV[1] then" +
        "   return redis.call('del',KEYS[1]) " +
        "else" +
        "   return 0 " +
        "end";

    try {
        Object result = jedis.eval(lua, Collections.singletonList(lockKey),
                                   Collections.singletonList(requestId));
        if("1".equals(result.toString())){
            return true;
        }
        return false;
    }finally {
        jedis.close();
    }

}

测试

在测试前,请确保你的Redis服务是开启的,然后运行如下代码:

package com.codingfire.zookeeper_lock;

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

public class RedisTest {
    public static void main(String[] args) {

        //模拟多个5个客户端
        for (int i=0;i<5;i++) {
            Thread thread = new Thread(new LockRunnable());
            thread.start();
        }
    }

    private static class LockRunnable implements Runnable {
        @Override
        public void run() {

            SingleRedisLockUtil singleRedisLock = new SingleRedisLockUtil();

            String requestId = UUID.randomUUID().toString();
            boolean lockResult = singleRedisLock.tryLock("lock", requestId);
            if (lockResult){

                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            singleRedisLock.releaseLock("lock",requestId);
        }
    }
}

控制台输出一角:

 

没办法截图特别全,大家看下就明白了。 

单节点问题

以上的单节点解决方案也只不过是一个基础的解决方案,还存在一定的问题,如果锁过期就会自动释放,此时如果任务还没执行完该怎么办?这时候同一个任务可能会重复执行,所以对于锁的超市时间的设置就比较讲究了,但是又没办法预测任务的时间长短,最好是能在锁快过期的时候动态延长时间,因为锁在还没过期时还没释放一定是任务没有执行完,这点毋庸置疑。

最好的办法就是创建一个守护线程,同时定义一个定时任务每隔一段时间去为未释放的锁增加过期时间。当业务执行完,释放锁后,再关闭守护线程。 这就解决了锁续期的问题。

单节点虽好,但Redis也是有可能存在集群的吧?毕竟Redis也是数据库的一种,为了数据的安全,缓存提升查询的效率,我做个主从啥的不过分吧?那我们就接着往下看。

Redisson分布式锁

​Redisson是redis官网推荐实现分布式锁的一个第三方类库,其功能非常强大,对各种锁都有实现,使用也非常简单,Redisson实现单节点的分布式锁相对而言更为容易,直接基于lock()&unlock()方法操作即可。

下面我们就先来看看Redisson的单节点分布式锁。

Redisson单节点分布式锁

依赖少不了:

<!--Redis分布式锁-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.1</version>
</dependency>

配置文件安排上:

server:
  redis:
    host: localhost
    port: 6379
    database: 0
    jedis:
      pool:
        max-active: 500
        max-idle: 1000
        min-idle: 4 

启动类中添加如下代码,最终的启动类如下:

package com.codingfire.zookeeper_lock;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.concurrent.TimeUnit;

@SpringBootApplication
public class ZookeeperLockApplication {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Bean
    public RedissonClient redissonClient(){
        RedissonClient redissonClient;

        Config config = new Config();
        String url = "redis://" + host + ":" + port;
        config.useSingleServer().setAddress(url);

        try {
            redissonClient = Redisson.create(config);
            return redissonClient;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {

        SpringApplication.run(ZookeeperLockApplication.class,args);
    }


}

 编写加解锁工具类:

package com.codingfire.zookeeper_lock;

import org.redisson.api.RLock;

@Component
public class RedissonLock {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 加锁
     * @param lockKey
     * @return
     */
    public boolean addLock(String lockKey){

        try {
            if (redissonClient == null){
                System.out.println("redisson client is null");
                return false;
            }

            RLock lock = redissonClient.getLock(lockKey);

            //设置锁超时时间为5秒,到期自动释放
            lock.lock(10, TimeUnit.SECONDS);

            System.out.println(Thread.currentThread().getName()+":  获取到锁");

            //加锁成功
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean releaseLock(String lockKey){

        try{
            if (redissonClient == null){
                System.out.println("redisson client is null");
                return false;
            }

            RLock lock = redissonClient.getLock(lockKey);
            lock.unlock();
            System.out.println(Thread.currentThread().getName()+":  释放锁");
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }
}

测试环节:

本来想把测试写在main方法里,但是发现静态方法调用不了外部非静态方法,还是算了,创建了一个测试类RedissonLockTest:

package com.codingfire.zookeeper_lock;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@SpringBootTest
@RunWith(SpringRunner.class)
public class RedissonLockTest {

    @Autowired
    private RedissonLock redissonLock;

    @Test
    public void easyLock(){
        //模拟多个5个客户端
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new LockRunnable());
            thread.start();
        }

        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class LockRunnable implements Runnable {
        @Override
        public void run() {
            redissonLock.addLock("lock");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            redissonLock.releaseLock("lock");
        }
    }
}

查看控制台输出:

 

五个线程按顺序执行完成,测试成功。

看门狗机制

虽然Redisson单节点实现分布式锁更方便,但是也存在一定的问题,我们在使用锁的时候应该看到了设置过期时间,大家有印象没?过期时间!!!和上面zookeeper中一样的问题,zookeeper使用了守护线程来给锁续期,这里我们使用的是看门狗机制。

看门狗机制用起来比守护线程更简便,分两步完成:

  • 不设置过期时间,默认是30s
  • 将加锁的方式由lock()改为tryLock()

另外,过期时间可修改:

config.setLockWatchdogTimeout(3000L);

分布式系统与红锁算法

单节点的Redis一旦出现宕机的情况锁就无法操作,所以像主从这样的配置是免不了的。但是主从时也存在一定的问题:master异步将数据复制到slave中,当某个线程持有了锁,在还没有将数据复制到slave时,master宕机,此时slave会被提升为master,新的master没有之前线程的锁信息,那么其他线程则又可以重新加锁,这该怎么办呢?好在有个红锁算法可以解决。

红锁是一种基于多节点redis实现分布式锁的算法,可以有效解决redis单点故障的问题。官方建议搭建五台redis服务器对红锁算法进行实现,就是比较烧钱~~~

红锁算法关于过期时间的计算非常严格,好在这些不需要我们亲自去计算,博主这里就偷个懒,不讲了,但是我们要确保在使用Redis的时候开启AOF,若Redis宕机,在ttl时间后再重启, 缺点就是ttl时间内Redis无法对外提供服务,但在多节点情况下也是可接受的。

红锁算法实现

创建配置类:

package com.codingfire.zookeeper_lock;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonRedLockConfig {

    public RedissonRedLock initRedissonClient(String lockKey){

        Config config1 = new Config();
        config1.useSingleServer().setAddress("redis://ip:8000").setDatabase(0);
        RedissonClient redissonClient1 = Redisson.create(config1);

        Config config2 = new Config();
        config2.useSingleServer().setAddress("redis://ip:8001").setDatabase(0);
        RedissonClient redissonClient2 = Redisson.create(config2);

        Config config3 = new Config();
        config3.useSingleServer().setAddress("redis://ip:8002").setDatabase(0);
        RedissonClient redissonClient3 = Redisson.create(config3);

        Config config4 = new Config();
        config4.useSingleServer().setAddress("redis://ip:8003").setDatabase(0);
        RedissonClient redissonClient4 = Redisson.create(config4);

        Config config5 = new Config();
        config5.useSingleServer().setAddress("redis://ip:8004").setDatabase(0);
        RedissonClient redissonClient5 = Redisson.create(config5);

        RLock rLock1 = redissonClient1.getLock(lockKey);
        RLock rLock2 = redissonClient2.getLock(lockKey);
        RLock rLock3 = redissonClient3.getLock(lockKey);
        RLock rLock4 = redissonClient4.getLock(lockKey);
        RLock rLock5 = redissonClient5.getLock(lockKey);

        RedissonRedLock redissonRedLock = new RedissonRedLock(rLock1,rLock2,rLock3,rLock4,rLock5);

        return redissonRedLock;
    }
}

由于需要多台Redis服务器,大家可以通过Docker本地模拟。

创建新的测试类进行测试:

package com.codingfire.zookeeper_lock;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.RedissonRedLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@SpringBootTest
@RunWith(SpringRunner.class)
public class RedLockTest {

    @Autowired
    private RedissonRedLockConfig redissonRedLockConfig;

    @Test
    public void testRedLock(){
        //模拟多个5个客户端
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new RedLockTest.RedLockRunnable());
            thread.start();
        }

        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class RedLockRunnable implements Runnable {
        @Override
        public void run() {
            RedissonRedLock redissonRedLock = redissonRedLockConfig.initRedissonClient("demo");

            try {
                boolean lockResult = redissonRedLock.tryLock(100, 10, TimeUnit.SECONDS);

                if (lockResult){
                    System.out.println("获取锁成功");
                    TimeUnit.SECONDS.sleep(3);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                redissonRedLock.unlock();
                System.out.println("释放锁");
            }
        }
    }
}

红锁原理

红锁算法允许加锁失败节点的个数限制为(N-(N/2+1)),加入当前假设五个节点,则允许失败节点数为2,不能超过一半,加锁时遍历所有节点执行lua脚本加锁,保证原子性。如果获取锁成功,就添加到已获取锁集合中,如果抛出异常,为了防止其他节点加锁成功,需要解锁所有节点,加锁失败。若是加锁失败,就没有必要继续从后面的节点申请锁,红锁算法要求至少N/2+1 个节点都加锁成功,才算最终的锁申请成功。此时,会计算从各个节点获取锁已经消耗的总时间,如果大于等于最大等待时间,申请锁失败,否则,申请锁成功,分布式锁加锁成功。

结语

看,分布式锁其实也没有那么难嘛,如果你能看到这里,那么相信你一定会有所收获,多看几遍,对理解分布式锁有好处,学完分布式锁,又可以开心的出去装13了,真开心!!!学习是一个循序渐进的过程,学完之后不代表就会了,过一段时间你多半是会忘记的,就好比博主,写完之后过一段很多东西都会忘记,也需要不断的回顾才能加深印象,学习是投资自己,只有金刚钻硬了,才能去揽那瓷器活儿。加油吧,少年们!

Java分布式锁详解:概念、实现与应用 分布式锁是一种在分布式系统或集群环境中用于控制多个进程/线程对共享资源进行互斥访问的协调机制。在单机系统中,我们可以使用Java内置的关键字或等实现线程间的互斥访问,但在分布式环境下,这些本地锁机制无法跨JVM工作,因此需要分布式锁来协调不同节点上的进程。分布式锁的核心目的是在分布式系统中实现跨进程的互斥访问,确保在同一时间只有一个客户端(可能位于不同物理机器上)能够执行特定的操作或访问特定的资源。 阅读详情

相关推荐

Zookeeper实现分布式锁详细步骤,你一定要知道

前几天分享了分布式锁的三种实现方案(我们是这样一步一步实现分布式锁的),其中对于zookeeper实现方式,有些朋友说想知道实现的总体流程。那么今天我就来将zookeeper的具体实现流程通过一步一图的形式再来说一下,然后大家可以根据前面的代码进行一起理解下,这样应该会更容易搞懂zookeeper的分布式锁。 zookeeper节点特征 现在我们都知道zookeeper是基于树形数据结构...

csdn681的博客 1100

Java 的锁机制:原理与源码详解

锁(Lock)就像现实生活中房间的门锁:当多个人(线程)同时想进入同一个房间(访问共享资源,比如一个变量或对象)时,锁确保只有一个人能进去,其他人得在门外等着。这样可以避免混乱,比如防止两个人同时修改一个银行账户余额导致数据错误。在Java中,锁是用来解决多线程并发访问共享资源时可能出现的数据不一致问题。简单说,锁是多线程编程中保证线程安全的工具。问题背景:多线程并发访问共享资源可能导致数据不一致,需要锁来保证线程安全。锁的实现。

goTsHgo的博客 1636

STM32实战:5分钟搞定MXL90614红外测温模块的I2C驱动(附完整代码)

本文提供了一份快速集成MLX90614红外测温模块的STM32实战指南。通过详细的软件I2C驱动代码,重点解析了SMBus通信协议、CRC校验及温度数据转换,帮助开发者避开常见陷阱,实现5分钟内完成模块驱动并读取准确温度值。

wasm7browser的博客 354

Java分布式锁详解

数据库锁:适合并发量低、可靠性要求不高、已有数据库环境的场景Redis锁:适合高性能、高可用场景,但对一致性要求不能太高Zookeeper锁:适合强一致性、高可靠场景,但性能相对较低实际项目中,推荐使用成熟的框架如Redisson或Curator,它们已经处理了各种边界条件和异常情况。对于关键业务,可以考虑结合多种实现方式提高可靠性。

Made In SQL 1355

BL0910-从零到一:多路交/直流电能计量的实战配置与校准

本文详细介绍了BL0910芯片在多路交直流电能计量中的实战应用。从硬件连接、SPI通信配置,到软件初始化、核心寄存器设置,再到偏置与增益校准,提供了完整的配置与校准指南。文章重点阐述了直流测量模式的切换要点及数据读取的工程化处理,帮助开发者快速实现高精度、多通道的电能监控方案。

rice5的博客 567

nnU-Net v2实战:从零开始配置环境与训练自定义医学影像数据集

本文详细介绍了nnU-Net v2在医学影像分割中的实战应用,从环境配置到自定义数据集训练的全流程。重点讲解了Python 3.9+环境搭建、PyTorch安装技巧、医学影像数据预处理规范,以及五折交叉验证训练方法,帮助开发者快速掌握这一先进的医学影像分析工具。

weixin_28718487的博客 352

三、详解Redis分布式锁&Redisson分布式锁

在传统的java进程中,我们常常用Synchronized或者ReentrantLock来对临界区进行加锁,防止多个线程之间并行访问,导致数据读写异常。但是这种锁的粒度仅限于当前jvm中,在工业生产环境下,往往一个web项目会部署多台机器,也就意味着会有多个jvm。那么这几个jvm是独立的,这就导致上述的锁失效。分布式锁是一种在分布式系统环境下,通过多个节点对共享资源进行访问控制的机制。

2301_77203215的博客 2574

Dual-stream multiple instance learning networks for tumor detection in Whole Slide Image——论文笔记

关于多示例学习的资料网上比较少,但是在医学图像方面还是有人在应用的。主要是因为一般比较大,几个G都是十分常见的,这么大图片是无法直接训练,一般都会进行切图,所以天然契合多示例学习bag和instance的概念。最近看了一篇论文刚好关于这方面的,所以做下笔记。 github地址:https://github.com/binli123/dsmil-wsi paper:https://arxiv.org/abs/2011.08939 背景知识 在多示例学习(MIL)中,一组训练样本被视为包含多个instance的

weixin_41693877的博客 4170

Java面试必备:分布式锁的实现方式详解

在分布式系统中,多个进程或服务需要协调对共享资源的访问时,分布式锁就成为了一个关键组件。本文将详细介绍分布式锁的常见实现方式,并通过流程图帮助理解各种实现机制。

qq_58299462的博客 2075

静态时序分析—门控时钟检查(clock gating check)

clock gating check的条件与类型

m0_61544122的博客 9611

基于MATLAB/Simulink平台进行新能源微电网运行控制的建模与仿真

新能源微电网是现代电力系统的重要组成部分,能够有效整合分布式能源(如光伏、风力发电)和储能设备,实现局部区域内的能源高效利用。微电网的运行控制涉及多源能量管理、负荷平衡以及并网/孤岛模式切换等关键问题。基于MATLAB/Simulink平台进行新能源微电网运行控制的建模与仿真,可以帮助研究者验证控制策略的有效性,并优化系统的动态性能和经济性。本项目旨在构建一个完整的新能源微电网模型,包括电源模块、储能模块、负荷模块以及控制器模块,并通过仿真分析其运行特性。

xiaoheshang_123的博客 424

java分布式锁 这篇很详细

java分布式锁什么是锁?什么是分布式?什么是分布式锁?我们需要怎样的分布式锁?基于数据库做分布式锁优缺点基于 Redis 做分布式锁基于 ZooKeeper 做分布式锁优缺点基于 Consul 做分布式锁总结参考资料 什么是锁? 在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。 而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记

weixin_43767695的博客 986

PY32F406K1CU6+I2C+ AT24C64 基本配置

【代码】PY32F406K1CU6+I2C+ AT24C64 基本配置。

sam的博客 90

如果连Redis分布式锁的这些坑都不知道,我怀疑你是假的开发

摘要:用锁遇到过哪些问题? 一、白话分布式 什么是分布式,用最简单的话来说,就是为了降低单个服务器的压力,将功能分布在不同的机器上面;就比如: 本来一个程序员可以完成一个项目:需求->设计->编码->测试 但是项目多的时候,一个人也扛不住,这就需要不同的人进行分工合作了 这就是一个简单的分布式协同工作了; 二、分布式锁 首先看一个问题,如果说某个环节被终止或者别侵占,就会发生不可知的事情 这就会出现,设计好的或者设计的半成品会被破坏,导致后面环节出.

m0_67645544的博客 327

利用Gretna软件计算脑网络的全局属性和局部属性指标

使用Gretna,你可以计算脑网络的全局属性指标(如网络特征、小世界性和模块度),以及局部属性指标(如节点度和聚类系数)。通过Gretna,你可以计算脑网络的稀疏度,并进一步探索网络的特征和特性。Gretna是一款基于MATLAB平台的脑网络分析工具,它提供了丰富的函数和工具箱,用于计算和分析脑网络的结构和功能。在脑网络中,较高的稀疏度可能意味着神经系统的高效性和信息处理能力,因为较少的连接可以降低信息传递的成本。连接矩阵是一个N*N的矩阵,其中N表示节点的数量,矩阵的每个元素表示节点之间的连接强度。

code_program481的博客 1809

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

分布式锁详解

它管理数据一致性,防止多个节点同时修改相同数据,处理资源竞争,保障事务原子性,避免任务重复执行,同时协调和同步节点操作,减少死锁的可能性。例如,在表中记录锁的创建时间,并定期检查是否超时,如果超时则自动释放锁。只有在比自己序号小的节点被删除后,才会再次检查自己是否成为最小的节点,进而获取锁。利用顺序节点实现锁的公平性,保证了锁的获取顺序。,查出来的数据是相同的,然后依次执行库存减一操作,此时库存会变成-1件,这就造成了超卖问题。当释放锁时,节点会检查锁的持有者是否匹配,只有匹配的情况下才会删除锁。

努力分享一些干货 2611

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

分布式锁的详细解释

可以通过在数据库中创建一张锁表,对需要加锁的资源在该表中插入一条记录,当其他进程或线程尝试获取锁时,通过数据库事务机制来保证原子性和互斥性;使用分布式缓存,如Redis或Memcache,将锁作为一个缓存键的值进行存储。其他进程或线程在获取锁时,先尝试设置该缓存键,如果设置成功,则表示获取到锁;使用ZooKeeper这样的分布式协调服务,通过创建临时有序节点来实现锁。其他进程或线程在获取锁时,需要通过比较自身创建的节点是否是最小的节点来判断是否获取到锁;

Flying_Fish_roe的博客 1315

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

java 分布式锁

  在java的分布式系统中,有时候会出现不同的服务操作同一个资源的情况,如交易系统和充值系统都要操作用户账户,分布式锁为解决分布式系统中多个应用同时访问同一个资源的问题。 java的分布式锁主要有三种实现方式: 基于数据库的分布式锁 基于缓存的分布式锁 基于zookeeper的分布式锁 下面对这三种方式具体分析一下 1.基于数据库的分布式锁 数据库实现分布式锁方式比较多,如悲观锁(...

baixiaozheng的博客 7536

Java开发 - 不知道算不算详细的MySQL多维度性能优化剖析

关于优化,我们不能够断章取义,有时候我们会直接去网上搜一些优化相关的博客,然后照着做。这是不对的,起码不是百分百正确,套路是这个套路,但优化就像不同的人去买衣服,每个人都有自己的特殊情况,我们要具体情况具体分析,毕竟不是每个人的应用和服务器都是一样的,千人千面用在这里再好不过了。MySQL几乎是目前绝大多数互联网公司的选择,出色的性能,低廉的成本,丰富的资源深受大众的喜爱。所以本文我们将MySQL作为我们的目标数据库。但优化有风险,优化需谨慎。优化一般针对的是一个复杂的且已经上线的系统;

CodingFire的博客 1567

Redis实现分布式锁详解

分布式锁满足分布式系统或集群模式下多进程可见并且互斥的锁。分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路可见性:多个线程都能看到相同的结果,注意:这个地方说的可见性并不是并发编程中指的内存可见 性,只是说多个进程之间都能感知到变化的意思互斥:互斥是分布式锁的最基本的条件,使得程序串行执行高可用:程序不易崩溃,时时刻刻都保证较高的可用性高性能。

weixin_43811057的博客 4587

Redission分布式锁详解

​ **

qq_39594419的博客 1888

如何基于Redis实现分布式锁,详细教程拿走不送~

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步! 如果有什么问题,欢迎大家评论,一起探讨,代码如有问题,欢迎各位大神指正! 给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔! ...

smilehappiness的博客 1194

还不知道 Redis 分布式锁的背后原理?还不赶快学习一下

每天早上七点三十,准时推送干货前言以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存。可阿粉在工作中发现,Redis在生产中并不只是当作缓存这么简单。在阿粉接触到的项目中...

Java极客技术 412

Java中如何实现分布式锁,详细教程来啦~

创作不易,如果觉得这篇文章对你有帮助,欢迎各位老铁点个赞支持下呗,您的支持是我创作的最大动力! 文章目录1 前言2 什么是分布式架构2.1 基础概念2.2 集群部署和分布式部署的区别2.3 分布式架构带来哪些问题2.3.1 分布式架构需要解决的问题2.3.2 CAP理论3 锁的分类4 实现分布式锁4.1 为什么要使用分布式锁4.2 实现分布式锁的几种方式4.2.1 基于Zookeeper实现分布式锁4.2.2 基于Redis实现分布式锁4.2.3 基于数据库实现分布式锁 1 前言 随着时代的变化,Jav.

smilehappiness的博客 2333
上一篇: Java开发 - 不知道算不算详细的MySQL多维度性能优化剖析
下一篇: Java开发 - 你不知道的JVM优化详解
CodingFire
CodingFire  2022年度博客之星移动开发领域TOP 5 2022年度博客之星移动开发领域TOP 5 领域专家: 移动开发技术领域 领域专家: 移动开发技术领域
博客等级 码龄11年 7927粉丝 387原创
评论 76
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodingFire

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值