前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >必须掌握【分布式锁】三种实现方式

必须掌握【分布式锁】三种实现方式

作者头像
田维常
发布于 2020-05-14 13:03:14
发布于 2020-05-14 13:03:14
12.6K00
代码可运行
举报
运行总次数:0
代码可运行

一、为什么要使用分布式锁

我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug!

注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间!

后来业务发展,需要做集群,一个应用需要部署到几台机器上然后做负载均衡,大致如下图:

上图可以看到,变量A存在JVM1、JVM2、JVM3三个JVM内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象,例如:UserController控制器中的一个整形类型的成员变量),如果不加任何控制的话,变量A同时都会在JVM分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的!即使不是同时发过来,三个请求分别操作三个不同JVM内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的!

如果我们业务中确实存在这个场景的话,我们就需要一种方法解决这个问题!

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

二、分布式锁应该具备哪些条件

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:

  1. 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
  2. 高可用的获取锁与释放锁;
  3. 高性能的获取锁与释放锁;
  4. 具备可重入特性;
  5. 具备锁失效机制,防止死锁;
  6. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

三、分布式锁的三种实现方式

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。

  • 基于缓存(Redis等)实现分布式锁;
  • 基于数据库实现分布式锁;
  • 基于Zookeeper实现分布式锁;
基于 Redis 的分布式锁
利用 SETNX 和 SETEX

基本命令主要有:

  • SETNX(SET If Not Exists):当且仅当 Key 不存在时,则可以设置,否则不做任何动作。
  • SETEX:可以设置超时时间

其原理为:通过 SETNX 设置 Key-Value 来获得锁,随即进入死循环,每次循环判断,如果存在 Key 则继续循环,如果不存在 Key,则跳出循环,当前任务执行完成后,删除 Key 以释放锁。

这种方式可能会导致死锁,为了避免这种情况,需要设置超时时间。

下面,请看具体的实现步骤。

1.创建一个 Maven 工程并在 pom.xml 加入以下依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>

        <!-- 开启web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

2.创建启动类 Application.java:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@SpringBootApplication
public class Application {

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

}

3.添加配置文件 application.yml:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
server:
  port: 8080
spring:
  redis:
    host: localhost
    port: 6379

4.创建全局锁类 Lock.java:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 全局锁,包括锁的名称
 */
public class Lock {
    private String name;
    private String value;

    public Lock(String name, String value) {
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public String getValue() {
        return value;
    }

}

5.创建分布式锁类 DistributedLockHandler.java:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class DistributedLockHandler {

    private static final Logger logger = LoggerFactory.getLogger(DistributedLockHandler.class);
    private final static long LOCK_EXPIRE = 30 * 1000L;//单个业务持有锁的时间30s,防止死锁
    private final static long LOCK_TRY_INTERVAL = 30L;//默认30ms尝试一次
    private final static long LOCK_TRY_TIMEOUT = 20 * 1000L;//默认尝试20s

    @Autowired
    private StringRedisTemplate template;

    /**
     * 尝试获取全局锁
     *
     * @param lock 锁的名称
     * @return true 获取成功,false获取失败
     */
    public boolean tryLock(Lock lock) {
        return getLock(lock, LOCK_TRY_TIMEOUT, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
    }

    /**
     * 尝试获取全局锁
     *
     * @param lock    锁的名称
     * @param timeout 获取超时时间 单位ms
     * @return true 获取成功,false获取失败
     */
    public boolean tryLock(Lock lock, long timeout) {
        return getLock(lock, timeout, LOCK_TRY_INTERVAL, LOCK_EXPIRE);
    }

    /**
     * 尝试获取全局锁
     *
     * @param lock        锁的名称
     * @param timeout     获取锁的超时时间
     * @param tryInterval 多少毫秒尝试获取一次
     * @return true 获取成功,false获取失败
     */
    public boolean tryLock(Lock lock, long timeout, long tryInterval) {
        return getLock(lock, timeout, tryInterval, LOCK_EXPIRE);
    }

    /**
     * 尝试获取全局锁
     *
     * @param lock           锁的名称
     * @param timeout        获取锁的超时时间
     * @param tryInterval    多少毫秒尝试获取一次
     * @param lockExpireTime 锁的过期
     * @return true 获取成功,false获取失败
     */
    public boolean tryLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
        return getLock(lock, timeout, tryInterval, lockExpireTime);
    }


    /**
     * 操作redis获取全局锁
     *
     * @param lock           锁的名称
     * @param timeout        获取的超时时间
     * @param tryInterval    多少ms尝试一次
     * @param lockExpireTime 获取成功后锁的过期时间
     * @return true 获取成功,false获取失败
     */
    public boolean getLock(Lock lock, long timeout, long tryInterval, long lockExpireTime) {
        try {
            if (StringUtils.isEmpty(lock.getName()) || StringUtils.isEmpty(lock.getValue())) {
                return false;
            }
            long startTime = System.currentTimeMillis();
            do{
                if (!template.hasKey(lock.getName())) {
                    ValueOperations<String, String> ops = template.opsForValue();
                    ops.set(lock.getName(), lock.getValue(), lockExpireTime, TimeUnit.MILLISECONDS);
                    return true;
                } else {//存在锁
                    logger.debug("lock is exist!!!");
                }
                if (System.currentTimeMillis() - startTime > timeout) {//尝试超过了设定值之后直接跳出循环
                    return false;
                }
                Thread.sleep(tryInterval);
            }
            while (template.hasKey(lock.getName())) ;
        } catch (InterruptedException e) {
            logger.error(e.getMessage());
            return false;
        }
        return false;
    }

    /**
     * 释放锁
     */
    public void releaseLock(Lock lock) {
        if (!StringUtils.isEmpty(lock.getName())) {
            template.delete(lock.getName());
        }
    }

}

6.最后创建 HelloController 来测试分布式锁。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class HelloController {

    @Autowired
    private DistributedLockHandler distributedLockHandler;

    @RequestMapping("index")
    public String index(){
        Lock lock=new Lock("lynn","min");
        if(distributedLockHandler.tryLock(lock)){
            try {
                //为了演示锁的效果,这里睡眠5000毫秒
                System.out.println("执行方法");
                Thread.sleep(5000);
            }catch (Exception e){
                e.printStackTrace();
            }
            distributedLockHandler.releaseLock(lock);
        }
        return "hello world!";
    }
}

7.测试。

启动 Application.java,连续访问两次浏览器:http://localhost:8080/index,控制台可以发现先打印了一次“执行方法”,说明后面一个线程被锁住了,5秒后又再次打印了“执行方法”,说明锁被成功释放。

通过这种方式创建的分布式锁存在以下问题:

  1. 高并发的情况下,如果两个线程同时进入循环,可能导致加锁失败。
  2. SETNX 是一个耗时操作,因为它需要判断 Key 是否存在,因为会存在性能问题。

因此,Redis 官方推荐 Redlock 来实现分布式锁。

利用 Redlock

通过 Redlock 实现分布式锁比其他算法更加可靠,继续改造上一例的代码。

1.pom.xml 增加以下依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.7.0</version>
        </dependency>

2.增加以下几个类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 获取锁后需要处理的逻辑
 */
public interface AquiredLockWorker<T> {
    T invokeAfterLockAquire() throws Exception;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 获取锁管理类
 */
public interface DistributedLocker {

    /**
     * 获取锁
     * @param resourceName  锁的名称
     * @param worker 获取锁后的处理类
     * @param <T>
     * @return 处理完具体的业务逻辑要返回的数据
     * @throws UnableToAquireLockException
     * @throws Exception
     */
    <T> T lock(String resourceName, AquiredLockWorker<T> worker) throws UnableToAquireLockException, Exception;

    <T> T lock(String resourceName, AquiredLockWorker<T> worker, int lockTime) throws UnableToAquireLockException, Exception;

}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 异常类
 */
public class UnableToAquireLockException extends RuntimeException {

    public UnableToAquireLockException() {
    }

    public UnableToAquireLockException(String message) {
        super(message);
    }

    public UnableToAquireLockException(String message, Throwable cause) {
        super(message, cause);
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 获取RedissonClient连接类
 */
@Component
public class RedissonConnector {
    RedissonClient redisson;
    @PostConstruct
    public void init(){
        redisson = Redisson.create();
    }

    public RedissonClient getClient(){
        return redisson;
    }

}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class RedisLocker  implements DistributedLocker{

    private final static String LOCKER_PREFIX = "lock:";

    @Autowired
    RedissonConnector redissonConnector;
    @Override
    public <T> T lock(String resourceName, AquiredLockWorker<T> worker) throws InterruptedException, UnableToAquireLockException, Exception {

        return lock(resourceName, worker, 100);
    }

    @Override
    public <T> T lock(String resourceName, AquiredLockWorker<T> worker, int lockTime) throws UnableToAquireLockException, Exception {
        RedissonClient redisson= redissonConnector.getClient();
        RLock lock = redisson.getLock(LOCKER_PREFIX + resourceName);
        // Wait for 100 seconds seconds and automatically unlock it after lockTime seconds
        boolean success = lock.tryLock(100, lockTime, TimeUnit.SECONDS);
        if (success) {
            try {
                return worker.invokeAfterLockAquire();
            } finally {
                lock.unlock();
            }
        }
        throw new UnableToAquireLockException();
    }
}

3.修改 HelloController:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class HelloController {

    @Autowired
    private DistributedLocker distributedLocker;

    @RequestMapping("index")
    public String index()throws Exception{
        distributedLocker.lock("test",new AquiredLockWorker<Object>() {

            @Override
            public Object invokeAfterLockAquire() {
                try {
                    System.out.println("执行方法!");
                    Thread.sleep(5000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return null;
            }

        });
        return "hello world!";
    }
}

4.按照上节的测试方法进行测试,我们发现分布式锁也生效了。

Redlock 是 Redis 官方推荐的一种方案,因此可靠性比较高。

基于数据库的分布式锁
基于数据库表

它的基本原理和 Redis 的 SETNX 类似,其实就是创建一个分布式锁表,加锁后,我们就在表增加一条记录,释放锁即把该数据删掉,具体实现,我这里就不再一一举出。

它同样存在一些问题:

  1. 没有失效时间,容易导致死锁;
  2. 依赖数据库的可用性,一旦数据库挂掉,锁就马上不可用;
  3. 这把锁只能是非阻塞的,因为数据的 insert 操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作;
  4. 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据库中数据已经存在了。
乐观锁

基本原理为:乐观锁一般通过 version 来实现,也就是在数据库表创建一个 version 字段,每次更新成功,则 version+1,读取数据时,我们将 version 字段一并读出,每次更新时将会对版本号进行比较,如果一致则执行此操作,否则更新失败!

悲观锁(排他锁)

实现步骤见下面说明。

1.创建一张数据库表:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
  `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

2.通过数据库的排他锁来实现分布式锁。

基于 MySQL 的 InnoDB 引擎,可以使用以下方法来实现加锁操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean lock(){
    connection.setAutoCommit(false)
    while(true){
        try{
            result = select * from methodLock where method_name=xxx for update;
            if(result==null){
                return true;
            }
        }catch(Exception e){

        }
        sleep(1000);
    }
    return false;
}

3.我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void unlock(){
    connection.commit();
}
基于 Zookeeper 的分布式锁
ZooKeeper 简介

ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google Chubby 的一个开源实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

分布式锁实现原理

实现原理为:

  1. 建立一个节点,假如名为 lock 。节点类型为持久节点(Persistent)
  2. 每当进程需要访问共享资源时,会调用分布式锁的 lock() 或 tryLock() 方法获得锁,这个时候会在第一步创建的 lock 节点下建立相应的顺序子节点,节点类型为临时顺序节点(EPHEMERAL_SEQUENTIAL),通过组成特定的名字 name+lock+顺序号。
  3. 在建立子节点后,对 lock 下面的所有以 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,假如是最小节点,则获得该锁对资源进行访问。
  4. 假如不是该节点,就获得该节点的上一顺序节点,并监测该节点是否存在注册监听事件。同时在这里阻塞。等待监听事件的发生,获得锁控制权。
  5. 当调用完共享资源后,调用 unlock() 方法,关闭 ZooKeeper,进而可以引发监听事件,释放该锁。

实现的分布式锁是严格的按照顺序访问的并发锁。

代码实现

我们继续改造本文的工程。

1.创建 DistributedLock 类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class DistributedLock implements Lock, Watcher{
    private ZooKeeper zk;
    private String root = "/locks";//根
    private String lockName;//竞争资源的标志
    private String waitNode;//等待前一个锁
    private String myZnode;//当前锁
    private CountDownLatch latch;//计数器
    private CountDownLatch connectedSignal=new CountDownLatch(1);
    private int sessionTimeout = 30000;
    /**
     * 创建分布式锁,使用前请确认config配置的zookeeper服务可用
     * @param config localhost:2181
     * @param lockName 竞争资源标志,lockName中不能包含单词_lock_
     */
    public DistributedLock(String config, String lockName){
        this.lockName = lockName;
        // 创建一个与服务器的连接
        try {
            zk = new ZooKeeper(config, sessionTimeout, this);
            connectedSignal.await();
            Stat stat = zk.exists(root, false);//此去不执行 Watcher
            if(stat == null){
                // 创建根节点
                zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            throw new LockException(e);
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }
    /**
     * zookeeper节点的监视器
     */
    public void process(WatchedEvent event) {
        //建立连接用
        if(event.getState()== Event.KeeperState.SyncConnected){
            connectedSignal.countDown();
            return;
        }
        //其他线程放弃锁的标志
        if(this.latch != null) {
            this.latch.countDown();
        }
    }

    public void lock() {
        try {
            if(this.tryLock()){
                System.out.println("Thread " + Thread.currentThread().getId() + " " +myZnode + " get lock true");
                return;
            }
            else{
                waitForLock(waitNode, sessionTimeout);//等待锁
            }
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }
    public boolean tryLock() {
        try {
            String splitStr = "_lock_";
            if(lockName.contains(splitStr))
                throw new LockException("lockName can not contains \\u000B");
            //创建临时子节点
            myZnode = zk.create(root + "/" + lockName + splitStr, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(myZnode + " is created ");
            //取出所有子节点
            List<String> subNodes = zk.getChildren(root, false);
            //取出所有lockName的锁
            List<String> lockObjNodes = new ArrayList<String>();
            for (String node : subNodes) {
                String _node = node.split(splitStr)[0];
                if(_node.equals(lockName)){
                    lockObjNodes.add(node);
                }
            }
            Collections.sort(lockObjNodes);

            if(myZnode.equals(root+"/"+lockObjNodes.get(0))){
                //如果是最小的节点,则表示取得锁
                System.out.println(myZnode + "==" + lockObjNodes.get(0));
                return true;
            }
            //如果不是最小的节点,找到比自己小1的节点
            String subMyZnode = myZnode.substring(myZnode.lastIndexOf("/") + 1);
            waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes, subMyZnode) - 1);//找到前一个子节点
        } catch (KeeperException e) {
            throw new LockException(e);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
        return false;
    }
    public boolean tryLock(long time, TimeUnit unit) {
        try {
            if(this.tryLock()){
                return true;
            }
            return waitForLock(waitNode,time);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    private boolean waitForLock(String lower, long waitTime) throws InterruptedException, KeeperException {
        Stat stat = zk.exists(root + "/" + lower,true);//同时注册监听。
        //判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
        if(stat != null){
            System.out.println("Thread " + Thread.currentThread().getId() + " waiting for " + root + "/" + lower);
            this.latch = new CountDownLatch(1);
            this.latch.await(waitTime, TimeUnit.MILLISECONDS);//等待,这里应该一直等待其他线程释放锁
            this.latch = null;
        }
        return true;
    }
    public void unlock() {
        try {
            System.out.println("unlock " + myZnode);
            zk.delete(myZnode,-1);
            myZnode = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }
    public Condition newCondition() {
        return null;
    }

    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public LockException(String e){
            super(e);
        }
        public LockException(Exception e){
            super(e);
        }
    }
}

2.改造 HelloController.java:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
public class HelloController {

    @RequestMapping("index")
    public String index()throws Exception{
        DistributedLock lock   = new DistributedLock("localhost:2181","lock");
        lock.lock();
        //共享资源
        if(lock != null){
            System.out.println("执行方法");
            Thread.sleep(5000);
            lock.unlock();
        }
        return "hello world!";
    }
}

3.按照本文 Redis 分布式锁的方法测试,我们发现同样成功加锁了。

总结

通过以上的实例可以得出以下结论:

  • 通过数据库实现分布式锁是最不可靠的一种方式,对数据库依赖较大,性能较低,不利于处理高并发的场景。
  • 通过 Redis 的 Redlock 和 ZooKeeper 来加锁,性能有了比较大的提升。
  • 针对 Redlock,曾经有位大神对其实现的分布式锁提出了质疑,但是 Redis 官方却不认可其说法,所谓公说公有理婆说婆有理,对于分布式锁的解决方案,没有最好,只有最适合的,根据不同的项目采取不同方案才是最合理的。

最后的最后说明一下:

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

下面是从各个方面进行三种实现方式的对比

从理解的难易程度角度(从低到高)

数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

Zookeeper > 缓存 > 数据库

参考https://blog.csdn.net/wuzhiwei549/article/details/80692278

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java后端技术栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
开源项目 | 目前订阅微信公众号最优雅的技巧
描述:开源项目 wewe-rss 由 cooderl 作者开发,它是目前最优雅的微信公众号订阅方式,支持私有化部署、微信公众号RSS生成(基于微信读书)v2.x 。
全栈工程师修炼指南
2024/07/06
1.1K0
开源项目 | 目前订阅微信公众号最优雅的技巧
运维 Tips | DELL 企业服务器 R710 磁盘阵列配置指南
由于戴尔存储服务器控制器坏了,且已经过保其维修的金额都要赶上购买此机器的一半了,遂将戴尔存储服务器svc2000中的磁盘,装到DELL服务器R710中,并做RAID5的磁盘阵列,通过NFS方式提供网络共享存储。
全栈工程师修炼指南
2024/08/14
2410
运维 Tips | DELL 企业服务器 R710 磁盘阵列配置指南
运维实践 | 华为服务器使用iBMC带外管理快速安装国产操作系统
描述:为了合理利用公司中服务器资源,需将原本作为VMware EXSi使用的RH5885-V3服务器安装成物理机器,并且加入到Kubernetes集群中作为工作负载(Node),其次因为国产化需求的原因,需要将其安装国产化的服务器操作系统,UP这里就使用老生常谈的 Kylin Server V10 SP3 系统,不在做过多介绍,有兴趣的朋友,可以看看我前面的关于《国产化系统银行麒麟》的相关文章,本文主要记录华为RH5885-V3使用iBMC带外管理快速安装国产服务器操作系统,为有需要使用带外管理来进行操作系统安装的朋友提供参考,希望大家多多支持。
全栈工程师修炼指南
2024/05/09
1.7K0
运维实践 | 华为服务器使用iBMC带外管理快速安装国产操作系统
运维 Tips | 宝塔Linux面板安装使用及安全配置那些事,新手站长必知!
描述: 宝塔 Linux 面板服务器工具实际上UP很早就听说过,但一直没有使用过,可能是作为一名专业运维不太喜欢有界面的东西(PS: 开玩笑,主要是没时间去折腾),正好作者在腾讯云上买了五年的轻量服务器主机,日常主要用于个人博客以及工具站使用(PS: 欢迎访问 weiyigeek.top),日常运维以及修改文件都是通过SSH方式来进行,并且在防火墙做了IP访问限制,如果在外面想连接到服务器进行管理就不是很方便,遂想到 宝塔 Linux 面板可以通过浏览器界面进行服务器运维管理,所以简单的研究了一下,发现这个工具还是很好用,可以很方便的进行服务器管理,下面我们就把宝塔Linux服务器管理工具部署到这台主机,以及帮助新使用的朋友可以快速上手,除此之外也对宝塔Linux面板的安全配置等,避免在使用时的一些坑。
全栈工程师修炼指南
2024/06/19
2.3K0
运维 Tips | 宝塔Linux面板安装使用及安全配置那些事,新手站长必知!
运维Tips | 当同时忘记Kylin麒麟系统root与grub密码如何应对?
描述:今天接到同事电话说安装的一台国产服务器Kylin V10 SP3 系统的root登录密码忘记了,遂想着直接进入单用户模式更改root不就行了吗,谁想到被GRUB密码拦住去路,由于当时做等保主机安全模板的时候添加了grub认证,然后grub密码又忘记了,于是乎只能通过挂载KylinOS系统镜像,进入到救援模式修改(去掉)grub密码,然后重启进入单用户模式修改root密码,由于配置过程还是比较多,以下是作者的操作步骤,帮助遇到相同问题的道友。
全栈工程师修炼指南
2024/07/06
1.3K0
运维Tips | 当同时忘记Kylin麒麟系统root与grub密码如何应对?
硬件玩物 | 性价比超高的NAS,威联通【TS-464-C2】快速上手初体验!
根据《数据安全法》数据备份和容灾规定要求,需要建立健全的数据备份机制,确保个人信息数据的安全存储和备份。在数据备份方面,需要保证数据的完整性和可恢复性,以应对意外情况或灾难发生时的数据丢失情况。同时,还需要建立容灾预案,确保在突发事件发生时能够迅速有效地恢复数据和系统运行,保障个人信息的安全。与此同时,备份设备是公司IT信息化建设中非常重要的一环,用于保护公司重要数据免受意外损失的影响。
全栈工程师修炼指南
2024/04/30
5350
硬件玩物 | 性价比超高的NAS,威联通【TS-464-C2】快速上手初体验!
记一次在苹果Mac系统中使用BootCamp安装Windows双系统之旅
描述: 有一部分苹果电脑用户通常会其Mac系统上安装 Windows 系统,来兼容一些日常办公及其应用软件的运行环境, 但是实际上不建议,发挥不出Mac笔记本的实际性能,但是苹果公司为了让Mac进入广阔的市场,专门开发了Bootcamp用Mac的应用来帮助引导windows系统,使之可以在保留Mac系统的同时,安装Windows系统,这就是我们常说的苹果电脑双系统。
全栈工程师修炼指南
2024/04/30
1.9K0
记一次在苹果Mac系统中使用BootCamp安装Windows双系统之旅
跨界探索:在苹果系统M系列处理器上安装Windows 11系统的实践经历
描述:在上一篇,文章中,我们介绍了在旧版本苹果MacOS系统安装Windows的方法,当时也是一位同事需要将macOS笔记本安装成为双系统,当时由于是2019年前的型号,所以使用的是BootCamp的方式进行安装;昨天又受另外一个同事所托,需要将苹果笔记本电脑安装一个 Windows 10系统,结果到手后一看系统和处理版本,发现是苹果的 M1 处理器,而苹果的M系列处理器采用的是ARM架构的,不支持64位架构的,要用启动转换助理只能在英特尔芯片上用,所以传统的双系统是无望了,遂Google 上搜索了相关资料,发现有大佬分享了苹果 M1 / M2 处理器安装 Windows 11的方法,是使用虚拟机的方式来进行Windows11/10系统安装,在实践的过程中还是存在一些小坑,为了帮助有同样需要的小伙伴们,就在此记录一下,希望大家多多支持。
全栈工程师修炼指南
2024/05/09
5.4K0
跨界探索:在苹果系统M系列处理器上安装Windows 11系统的实践经历
机器学习筑基篇,Jupyter Notebook 精简指南
描述:前面我们已经在机器学习工作站(Ubuntu 24.04 Desktop + Geforce RTX 4070Ti SUPER)中安装 Anaconda 工具包,其中也包含了 Jupyter Notebook (/ˈdʒuːpɪtə(r)/ /nəʊtbʊk/)工具及其相关依赖项,接下来我们简单介绍一下 Jupyter Notebook 一个Web在线交互计算的工具集,及其安装、配置、使用方法,给各位初次学习机器的朋友做一个指引!
全栈工程师修炼指南
2024/07/16
5030
机器学习筑基篇,Jupyter Notebook 精简指南
机器学习筑基篇,​Ubuntu 24.04 快速安装 PyCharm IDE 工具,无需激活!
描述:虽然在之前我们安装了VScode,但是其对于使用Python来写大型项目以及各类配置还是比较复杂的,所以这里我们还是推荐使用PyCharm来编写构建Python项目,毕竟还是要使用专业的软件做专业的事,会让我们开发效率更高。
全栈工程师修炼指南
2024/07/16
6020
机器学习筑基篇,​Ubuntu 24.04 快速安装 PyCharm IDE 工具,无需激活!
运维 | 在企业环境中快速安装配置 Win Server 2022 服务器操作系统
描述:在上一篇文章中,我们提到过 Server 2022 发布于 2021 年 8 月,是迄今为止(2024年3月20日 09:15:07)Windows 在服务器操作系统中最新的版本(PS: 不过听说 Windows Server 2025 也快了),它建立在Windows Server 2019之上,带来了许多针对虚拟化、存储、安全性和Windows Admin Center 管理的改进以及Azure集成,使得它成为一个更加强大、安全和高效的服务器操作系统。
全栈工程师修炼指南
2024/03/25
1.9K0
运维 | 在企业环境中快速安装配置 Win Server 2022 服务器操作系统
硬件玩物 | 在超高性价比的NAS中打造个人知识笔记管理利器!
描述:作为一名IT工作者,一般都有一些自己从业的笔记与资料,作者也是本着好记性不如烂笔头,喜欢将一些东西通过笔记的形式记录下来,但是这些笔记都是保存在个人电脑中,如果想要将笔记分享给其他人,就需要将笔记导出为PDF格式,其次就是笔记内容的索引搜索,确实有些不是很方便。作者在最开始是使用Wiz为知云笔记购买了2年多的会员,也使用了两年,后面确实因为一些原因(想白嫖😳,咳咳咳,节约成本),就将笔记迁移到本地以Markdown笔记的形式保存,最近买了一台威联通(QNAP)TS-464C2NAS ,所以就萌生了在NAS中搭建私有化为知笔记的想法。
全栈工程师修炼指南
2024/05/09
1.4K0
硬件玩物 | 在超高性价比的NAS中打造个人知识笔记管理利器!
AIGC | Ubuntu24.04桌面版必备软件安装
描述:在进行使用Ubuntu 24.04 TLS Desktop系统进行机器学习前,我们需要在学习环境中安装一些后续可能会使用到的一些工具,如git、vim、tmux、htop等。
全栈工程师修炼指南
2024/07/06
1.8K0
AIGC | Ubuntu24.04桌面版必备软件安装
网安工具 | Windows便携式渗透测试环境PentestBox入门到进阶使用指南
本文为作者原创文章,为尊重作者劳动成果禁止非授权转载,若需转载请在【全栈工程师修炼指南】公众号留言,或者发送邮件到 [master@weiyigeek.top] 中我将及时回复。
全栈工程师修炼指南
2023/10/31
2.7K0
网安工具 | Windows便携式渗透测试环境PentestBox入门到进阶使用指南
Python3 | 练气期,函数创建、参数传递、作用域!
描述:上一章,我们学习了Python3编程中最基本而得流程控制语句,相信大家在作者的实践下也已经掌握了相关关键字了吧,这一章我们一起学习Python3编程入门中函数定义、函数调用、函数参数(传递、类型),匿名函数、递归函数。内嵌函数和闭包、装饰器函数,以及命名空间作用域的讲解,它也是Python编程中最基础且常用的部分,所以说也是需要我们掌握的。
全栈工程师修炼指南
2024/07/29
650
Python3 | 练气期,函数创建、参数传递、作用域!
Linux 命令:每日一学,一文说尽打包压缩工具实践
前面,我们介绍了Linux中文件查找find命令以及与之联用最勤的xargs命令,作者以一个个简单的实例给各位看友展示了在运维中两个命令的使用技巧,今天我们再来介绍一些打包、压缩解压等相关命令。
全栈工程师修炼指南
2024/10/14
3420
Linux 命令:每日一学,一文说尽打包压缩工具实践
虚拟化 | 使用VMware vCenter Converter快速将计算机从物理环境或其他虚拟化平台迁移到vSphere环境
初始,我想在vCenter中直接进行迁移,但是发现不能更改磁盘类型为精简置备以及磁盘空间不能缩小指定空间,想到可以使用 VMware vCenter Converter 缩小其分区大小且更改磁盘类型,说干就干,遂有了此文。
全栈工程师修炼指南
2023/11/20
4.9K18
虚拟化 | 使用VMware vCenter Converter快速将计算机从物理环境或其他虚拟化平台迁移到vSphere环境
运维必学 | 函数参数传递-从零开始学Windows批处理(Batch)编程系列教程
本文为作者原创文章,为尊重作者劳动成果禁止非授权转载,若需转载请在【全栈工程师修炼指南】公众号留言,或者发送邮件到 [master@weiyigeek.top] 中我将及时回复。
全栈工程师修炼指南
2023/10/31
1.6K0
运维必学 | 函数参数传递-从零开始学Windows批处理(Batch)编程系列教程
Linux 命令 | 每日一学,文本处理三剑客之grep命令实践
上一篇,我们学习了Shell脚本编程中的正则表达式【Linux 运维 | 6.从零开始,Shell编程中正则表达式 RegExp 速成指南】, 不知道各位初学的童鞋是否已经初步掌握了呢,接下来我们学习Linux中的文本处理三剑客之一grep命令,grep 命令是Linux系统中常用的文本搜索工具,它可以根据指定的字符串模式或者正则表达式对文件内容每行进行搜索、匹配等操作。
全栈工程师修炼指南
2024/09/29
1700
Linux 命令 | 每日一学,文本处理三剑客之grep命令实践
玩机技巧 | Windows 离座锁屏功能更新,再也不用担心忘记锁屏了!
针对有上述问题的朋友,这里 UP简单、快速的介绍一下 Windows 10 、11 系统有多种方式锁屏。
全栈工程师修炼指南
2024/05/11
8370
玩机技巧 | Windows 离座锁屏功能更新,再也不用担心忘记锁屏了!
推荐阅读
开源项目 | 目前订阅微信公众号最优雅的技巧
1.1K0
运维 Tips | DELL 企业服务器 R710 磁盘阵列配置指南
2410
运维实践 | 华为服务器使用iBMC带外管理快速安装国产操作系统
1.7K0
运维 Tips | 宝塔Linux面板安装使用及安全配置那些事,新手站长必知!
2.3K0
运维Tips | 当同时忘记Kylin麒麟系统root与grub密码如何应对?
1.3K0
硬件玩物 | 性价比超高的NAS,威联通【TS-464-C2】快速上手初体验!
5350
记一次在苹果Mac系统中使用BootCamp安装Windows双系统之旅
1.9K0
跨界探索:在苹果系统M系列处理器上安装Windows 11系统的实践经历
5.4K0
机器学习筑基篇,Jupyter Notebook 精简指南
5030
机器学习筑基篇,​Ubuntu 24.04 快速安装 PyCharm IDE 工具,无需激活!
6020
运维 | 在企业环境中快速安装配置 Win Server 2022 服务器操作系统
1.9K0
硬件玩物 | 在超高性价比的NAS中打造个人知识笔记管理利器!
1.4K0
AIGC | Ubuntu24.04桌面版必备软件安装
1.8K0
网安工具 | Windows便携式渗透测试环境PentestBox入门到进阶使用指南
2.7K0
Python3 | 练气期,函数创建、参数传递、作用域!
650
Linux 命令:每日一学,一文说尽打包压缩工具实践
3420
虚拟化 | 使用VMware vCenter Converter快速将计算机从物理环境或其他虚拟化平台迁移到vSphere环境
4.9K18
运维必学 | 函数参数传递-从零开始学Windows批处理(Batch)编程系列教程
1.6K0
Linux 命令 | 每日一学,文本处理三剑客之grep命令实践
1700
玩机技巧 | Windows 离座锁屏功能更新,再也不用担心忘记锁屏了!
8370
相关推荐
开源项目 | 目前订阅微信公众号最优雅的技巧
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 二、分布式锁应该具备哪些条件
  • 三、分布式锁的三种实现方式
    • 基于 Redis 的分布式锁
      • 利用 SETNX 和 SETEX
      • 利用 Redlock
    • 基于数据库的分布式锁
      • 基于数据库表
      • 乐观锁
      • 悲观锁(排他锁)
    • 基于 Zookeeper 的分布式锁
      • ZooKeeper 简介
      • 分布式锁实现原理
      • 代码实现
  • 总结
    • 下面是从各个方面进行三种实现方式的对比
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文