所有分类
  • 所有分类
  • 未分类

分布式锁的实现方案

简介

说明

本文介绍Java后端编程中分布式锁的实现方式。

分布式锁实现方式主要有:数据库、缓存(Redis)、Zookeeper。Redis缓存是分布式锁最常用的方案。

分布式锁,重点在于多个服务能够访问到同一个东西,然后这个东西可以保证同时只能有一个服务访问它。

分布式锁的实现方案也是Java后端面试中经常问到的问题。

单机锁与分布式锁

1.单机的锁

普通的锁,即在单机多线程环境下,当多个线程需要访问同一个变量或代码片段时,被访问的变量或代码片段叫做临界区域,我们需要控制线程一个一个的顺序执行,否则会出现并发问题。

如何控制呢?就是设置一个各个线程都能看的见的标志。然后,每个线程想访问临界区域时,都要先查看标志,如果标志没有被占用,则说明目前没有线程在访问临界区域。如果标志被占用了,则说明目前有线程正在访问临界区域,则当前线程需要等待。这个标志,就是锁。

在单机多线程的java程序中,我们可以使用堆内存中的变量作为标志,因为多线程是共享堆内存的,堆内存中的变量对于各个线程都是可见的。

2.分布式锁

在分布式环境下,即多台计算机,每个计算机上会启动jvm执行程序的运行环境下,如果不同计算机上的线程想访问临界区域时,该怎么办呢?

前面普通锁的使用堆内存中的变量的方式肯定不适用了。因为在多机环境下,某台计算机上的堆内存中的变量对于其他计算机上的线程肯定是不可见的。那么,根据锁的本质和原理,我们就要找到另外的对于多机上的线程都可见的标志,以它来作为锁,就可以了。这样的锁,就是分布式锁。

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

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

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

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 可重入
  • 具备锁失效机制,防止死锁(网络中断或宕机无法释放锁时,锁必须被清除,不然会发生死锁)
  • 具备阻塞/非阻塞锁特性(根据业务需求)
  • 高可用、高性能的获取锁与释放锁

分布式锁的实现方式

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

分类方案实现原理优点缺点
基于数据库基于mysql 表唯一索引1.表增加唯一索引
2.加锁:执行insert语句,若报错,则表明加锁失败
3.解锁:执行delete语句
实现简单1.锁无超时自动失效机制,有死锁风险。
2.不支持锁重入,不支持阻塞等待
3.性能差
基于MongoDB findAndModify原子操作1.加锁:执行findAndModify原子命令查找document,若不存在则新增
2.解锁:删除document
比MySQL唯一索引的方案性能好。锁无超时自动失效机制。
基于缓存基于Redis命令1. 加锁:执行setnx,若成功再执行expire添加过期时间
2. 解锁:执行delete命令
1.实现简单,2.性能好1.setnx和expire分2步执行,非原子操作;若setnx执行成功,但expire执行失败,就可能出现死锁
2.delete命令存在误删除非当前线程持有的锁的可能
3.不支持阻塞等待,不可重入
基于Redis Lua脚本1. 加锁:执行SET lock_name random_value EX seconds NX 命令
2. 解锁:执行Lua脚本
1.性能好 2.保证原子性1.不支持阻塞等待 2.不支持锁重入 3.不支持锁续期机制
基于Ridisson近乎完美的方案。 1.保证AP,可用性强 2.性能高 3.支持:阻塞/非阻塞、重入、续期机制等目前没发现啥缺点。 (如果是集群,可考虑采用Redisson的红锁解决)
基于分布式系统基于ZooKeeper临时有序节点1.保证CP,一致性强性能低于Redis。

方案1:基于数据库

方式1:基于表

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 now() ON UPDATE now() COMMENT '保存数据时间,自动生成',  
    PRIMARY KEY (`id`),  
    UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';

当我们想要锁住某个方法时,执行以下SQL:

insert into methodLock(method_name,desc) values ('method_name','desc')

因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。

当方法执行完毕之后,想要释放锁的话,需要执行以下Sql:

delete from methodLock where method_name ='method_name'

本实现方法的问题及解决方法:

问题解决方法
这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。搞一个while循环,直到insert成功再返回成功。
这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
这把锁是非公平锁,所有等待锁的线程凭运气去争夺锁。再建一张中间表,将等待锁的线程全记录下来,并根据创建时间排序,只有最先创建的允许获取锁

方式2:基于排他锁

简介

本方法用的比较少。

实现方法

除了可以通过增删操作数据表中的记录以外,其实还可以借助数据中自带的锁来实现分布式的锁。

我们还用刚刚创建的那张数据库表。可以通过数据库的排他锁来实现分布式锁。 基于MySql的InnoDB引擎(面试官经常问的存储类型),可以使用以下方法来实现加锁操作:

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;
}

在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。

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

public void unlock(){ connection.commit(); }

问题及解决方法

问题解决方法
阻塞锁for update语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。
无法释放锁服务宕机之后数据库会自己把锁释放掉
单点问题无法直接解决
可重入问题无法直接解决
公平锁无法直接解决

总结一下使用数据库来实现分布式锁的方式,这两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。

方案2:基于缓存

相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点。

目前有很多成熟的缓存产品,包括Redis,memcached等。这里以Redis为例来分析下使用缓存实现分布式锁的方案。

基于Redis实现分布式锁在网上有很多相关文章,其中主要的实现方式有:

  1. 使用Redisson的锁(推荐)。详见:Redisson-分布式锁的原理 – 自学精灵
  2. 使用Jedis.setNX方法(对应Redis的setnx命令)

优点

性能好

缺点

没有明显缺点。如果非要说一点,那就是:实现过于复杂,需要考虑的因素太多。但是,可以使用Redisson的锁,很简单,它内部给实现了续期、可重入等问题。Redisson的锁也是业界目前最常用的分布式锁。

使用方法

public boolean trylock(String key) {    
    ResultCode code = jedis.setNX(key, "This is a Lock.");    
    if (ResultCode.SUCCESS.equals(code))        
        return true;    
    else        
        return false; 
} 
public boolean unlock(String key){
    ldbTairManager.invalid(NAMESPACE, key); 
}

问题及解决方法

问题解决方法
单点问题通过集群来解决单点问题
这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在Redis中,其他线程无法再获得到锁。Redis的setExpire方法支持传入失效时间,到达时间之后数据会自动删除。
这把锁只能是非阻塞的,无论成功还是失败都直接返回。while重复执行。
这把锁是非重入的,一个线程获得锁之后,在释放锁之前,无法再次获得该锁,因为使用到的key在Redis中已经存在。无法再执行setNX操作。在一个线程获取到锁之后,把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。
这把锁是非公平的,所有等待的线程同时去发起setNX操作,运气好的线程能获取锁。在线程获取锁之前先把所有等待的线程放入一个队列中,然后按先进先出原则获取锁。

Redis集群的同步缺陷及其解决方法

Redis集群的同步策略是需要时间的,有可能A线程setNX成功后拿到锁,但是这个值还没有更新到B线程执行setNX的这台服务器,那就会产生并发问题。

Redis的作者Salvatore Sanfilippo,提出了Redlock算法,该算法实现了比单一节点更安全、可靠的分布式锁管理(DLM)。

Redlock算法假设有N个Redis节点,这些节点互相独立,一般设置为N=5,这N个节点运行在不同的机器上以保持物理层面的独立。

算法的步骤如下:

  • 1、客户端获取当前时间,以毫秒为单位。
  • 2、客户端尝试获取N个节点的锁,(每个节点获取锁的方式和前面说的缓存锁一样),N个节点以相同的key和value获取锁。客户端需要设置接口访问超时,接口超时时间需要远远小于锁超时时间,比如锁自动释放的时间是10s,那么接口超时大概设置5-50ms。这样可以在有redis节点宕机后,访问该节点时能尽快超时,而减小锁的正常使用。
  • 3、客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过3个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。
  • 4、客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。
  • 5、如果客户端获取锁失败了,客户端会依次删除所有的锁。
    使用Redlock算法,可以保证在挂掉最多2个节点的时候,分布式锁服务仍然能工作,这相比之前的数据库锁和缓存锁大大提高了可用性,由于redis的高效性能,分布式缓存锁性能并不比数据库锁差。

但是,有一位分布式的专家写了一篇文章《How to do distributed locking》,质疑Redlock的正确性。该专家提到,考虑分布式锁的时候需要考虑两个方面:性能和正确性。

  • 如果使用高性能的分布式锁,对正确性要求不高的场景下,那么使用缓存锁就足够了。
  • 如果使用可靠性高的分布式锁,那么就需要考虑严格的可靠性问题。而Redlock则不符合正确性。为什么不符合呢?专家列举了几个方面。

现在很多编程语言使用的虚拟机都有GC功能,在Full GC的时候,程序会停下来处理GC,有些时候Full GC耗时很长,甚至程序有几分钟的卡顿,文章列举了HBase的例子,HBase有时候GC几分钟,会导致租约超时。而且Full GC什么时候到来,程序无法掌控,程序的任何时候都可能停下来处理GC,比如下图,客户端1获得了锁,正准备处理共享资源的时候,发生了Full GC直到锁过期。这样,客户端2又获得了锁,开始处理共享资源。在客户端2处理的时候,客户端1 Full GC完成,也开始处理共享资源,这样就出现了2个客户端都在处理共享资源的情况。

专家给出了解决办法,如下图,看起来就是MVCC,给锁带上token,token就是version的概念,每次操作锁完成,token都会加1,在处理共享资源的时候带上token,只有指定版本的token能够处理共享资源。

然后专家还说到了算法依赖本地时间,而且redis在处理key过期的时候,依赖gettimeofday方法获得时间,而不是monotonic clock,这也会带来时间的不准确。比如一下场景,两个客户端client 1和client 2,5个redis节点nodes (A, B, C, D and E)。

  • 1、client 1从A、B、C成功获取锁,从D、E获取锁网络超时。
  • 2、节点C的时钟不准确,导致锁超时。
  • 3、client 2从C、D、E成功获取锁,从A、B获取锁网络超时。
  • 4、这样client 1和client 2都获得了锁。

总结专家关于Redlock不可用的两点:

  • GC等场景可能随时发生,并导致在客户端获取了锁,在处理中超时,导致另外的客户端获取了锁。专家还给出了使用自增token的解决方法。
  • 算法依赖本地时间,会出现时钟不准,导致2个客户端同时获得锁的情况。
    所以专家给出的结论是,只有在有界的网络延迟、有界的程序中断、有界的时钟错误范围,Redlock才能正常工作,但是这三种场景的边界又是无法确认的,所以专家不建议使用Redlock。对于正确性要求高的场景,专家推荐了Zookeeper,关于使用Zookeeper作为分布式锁后面再讨论。

Redis作者的回应

Redis作者看到这个专家的文章后,写了一篇博客予以回应。作者很感谢了专家,然后表达出了对专家观点的不认同。

I asked for an analysis in the original Redlock specification here: Distributed locks with Redis – Redis. So thank you Martin. However I don’t agree with the analysis.

Redis作者关于使用token解决锁超时问题可以概括成下面五点:

  • 观点1,使用分布式锁一般是在,你没有其他方式去控制共享资源了,专家使用token来保证对共享资源的处理,那么就不需要分布式锁了。
  • 观点2,对于token的生成,为保证不同客户端获得的token的可靠性,生成token的服务还是需要分布式锁保证服务的可靠性。
  • 观点3,对于专家说的自增的token的方式,redis作者认为完全没必要,每个客户端可以生成唯一的uuid作为token,给共享资源设置为只有该uuid的客户端才能处理的状态,这样其他客户端就无法处理该共享资源,直到获得锁的客户端释放锁。
  • 观点4,redis作者认为,对于token是有序的,并不能解决专家提出的GC问题,如上图所示,如果token 34的客户端写入过程中发送GC导致锁超时,另外的客户端可能获得token 35的锁,并再次开始写入,导致锁冲突。所以token的有序并不能跟共享资源结合起来。
  • 观点5,redis作者认为,大部分场景下,分布式锁用来处理非事务场景下的更新问题。作者意思应该是有些场景很难结合token处理共享资源,所以得依赖锁去锁定资源并进行处理。

专家说到的时钟问题 

Redis作者也给出了解释。客户端实际获得的锁的时间是默认的超时时间,减去获取锁所花费的时间,如果获取锁花费时间过长导致超过了锁的默认超时间,那么此时客户端并不能获取到锁,不会存在专家提出的例子。

总结专家的观点

第一个问题我概括为,在一个客户端获取了分布式锁后,在客户端的处理过程中,可能出现锁超时释放的情况,这里说的处理中除了GC等非抗力外,程序流程未处理完也是可能发生的。之前在说到数据库锁设置的超时时间2分钟,如果出现某个任务占用某个订单锁超过2分钟,那么另一个交易中心就可以获得这把订单锁,从而两个交易中心同时处理同一个订单。正常情况,任务当然秒级处理完成,可是有时候,加入某个rpc请求设置的超时时间过长,一个任务中有多个这样的超时请求,那么,很可能就出现超过自动解锁时间了。当初我们的交易模块是用C++写的,不存在GC,如果用java写,中间还可能出现FullGC(这种情况很少出现),那么锁超时解锁后,自己客户端无法感知,是件非常严重的事情。我觉得这不是锁本身的问题,上面说到的任何一个分布式锁,只要自带了超时释放的特性,都会出现这样的问题。如果使用锁的超时功能,那么客户端一定得设置获取锁超时后,采取相应的处理,而不是继续处理共享资源。Redlock的算法,在客户端获取锁后,会返回客户端能占用的锁时间,客户端必须处理该时间,让任务在超过该时间后停止下来。

第二个问题,自然就是分布式专家没有理解Redlock。Redlock有个关键的特性是,获取锁的时间是锁默认超时的总时间减去获取锁所花费的时间,这样客户端处理的时间就是一个相对时间,就跟本地时间无关了。

由此看来,Redlock的正确性是能得到很好的保证的。仔细分析Redlock,相比于一个节点的redis,Redlock提供的最主要的特性是可靠性更高,这在有些场景下是很重要的特性。但是我觉得Redlock为了实现可靠性,却花费了过大的代价。

  • 首先必须部署5个节点才能让Redlock的可靠性更强。
  • 然后需要请求5个节点才能获取到锁,通过Future的方式,先并发向5个节点请求,再一起获得响应结果,能缩短响应时间,不过还是比单节点redis锁要耗费更多时间。
  • 然后由于必须获取到5个节点中的3个以上,所以可能出现获取锁冲突,即大家都获得了1-2把锁,结果谁也不能获取到锁,这个问题,redis作者借鉴了raft算法的精髓,通过冲突后在随机时间开始,可以大大降低冲突时间,但是这问题并不能很好的避免,特别是在第一次获取锁的时候,所以获取锁的时间成本增加了。
  • 如果5个节点有2个宕机,此时锁的可用性会极大降低,首先必须等待这两个宕机节点的结果超时才能返回,另外只有3个节点,客户端必须获取到这全部3个节点的锁才能拥有锁,难度也加大了。
  • 如果出现网络分区,那么可能出现客户端永远也无法获取锁的情况。

分析了这么多原因,我觉得Redlock的问题,最关键的一点在于Redlock需要客户端去保证写入的一致性,后端5个节点完全独立,所有的客户端都得操作这5个节点。如果5个节点有一个leader,客户端只要从leader获取锁,其他节点能同步leader的数据,这样,分区、超时、冲突等问题都不会存在。所以为了保证分布式锁的正确性,我觉得使用强一致性的分布式协调服务能更好的解决问题。

失效时间设置多长时间为好?

如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。

这个问题使用数据库实现分布式锁同样存在。

解决方案:

对于这个问题目前主流的做法是每获得一个锁时,只设置一个很短的超时时间,同时起一个线程在每次快要到超时时间时去刷新锁的超时时间。在释放锁的同时结束这个线程。如Redis官方的分布式锁组件Redisson。就是用的这种方案。

方案3:基于ZooKeeper

简介

基于Zookeeper临时有序节点可以实现分布式锁。

大致思想即为:每个客户端对某个方法加锁时,在Zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

原理

Zookeeper原理–分布式锁 – 自学精灵

实现方法

可以直接使用Zookeeper第三方库客户端(Apache Curator),这个客户端中封装了一个可重入的锁服务。Curator提供的InterProcessMutex是分布式锁的实现。acquire方法用户获取锁,release方法用于释放锁。

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {    
    try {        
        return interProcessMutex.acquire(timeout, unit);    
    } catch (Exception e) {        
        e.printStackTrace();    
    }    
    return true; 
} 
 
public boolean unlock() {    
    try {        
        interProcessMutex.release();    
    } catch (Throwable e) {        
        log.error(e.getMessage(), e);    
    } finally {        
        executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS);    
    }    
    return true; 
}

问题及解决方法

问题解决方法
锁无法释放Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。
非阻塞锁Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。
不可重入Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。
单点问题Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。
公平问题Zookeeper可以解决公平锁问题,客户端在ZK中创建的临时节点是有序的,每次锁被释放时,ZK可以通知最小节点来获取锁,保证了公平。
一致性问题Zookeeper是一个保证了弱一致性即最终一致性的分布式组件。

优点

有效解决这些问题:单点,不可重入,非阻塞、锁无法释放(不靠超时时间释放锁)。

缺点

性能不如Redis。

Zookeeper的一致性

Zookeeper采用称为Quorum Based Protocol的数据同步协议。假如Zookeeper集群有N台Zookeeper服务器(N通常取奇数,3台能够满足数据可靠性同时有很高读写性能,5台在数据可靠性和读写性能方面平衡最好),那么用户的一个写操作,首先同步到N/2 + 1台服务器上,然后返回给用户,提示用户写成功。基于Quorum Based Protocol的数据同步协议决定了Zookeeper能够支持什么强度的一致性。

在分布式环境下,满足强一致性的数据储存基本不存在,它要求在更新一个节点的数据,需要同步更新所有的节点。这种同步策略出现在主从同步复制的数据库中。但是这种同步策略,对写性能的影响太大而很少见于实践。因为Zookeeper是同步写N/2+1个节点,还有N/2个节点没有同步更新,所以Zookeeper不是强一致性的。

用户的数据更新操作,不保证后续的读操作能够读到更新后的值,但是最终会呈现一致性。牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可。

Zookeeper是否满足最终一致性,需要看客户端的编程方式。

满足最终一致性编程的方法

  1. B进程监听Zookeeper上/z的数据变化
  2. A进程向Zookeeper的/z写入一个数据,成功返回前,Zookeeper需要调用注册在/z上的监听器,Leader将数据变化的通知告诉B
  3. B进程的事件响应方法得到响应后,去取变化的数据,那么B一定能够得到变化的值
  4. 这里的因果一致性提现在Leader和B之间的因果一致性,也就是是Leader通知了数据有变化

不满足最终一致性编程的方法

  1. A进程向Zookeeper的/z写入一个数据,成功返回
  2. A进程通知B进程,A已经修改了/z的数据
  3. B读取Zookeeper的/z的数据
  4. 由于B连接的Zookeeper的服务器有可能还没有得到A写入数据的更新,那么B将读不到A写入的数据
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录