Replication(下):事务,一致性与共识

在上一篇中,我们主要介绍了分布式系统中常见的复制模型,并描述了每一种模型的优缺点以及使用场景,同时阐述了分布式系统中特有的一些技术挑战。首先,常见的分布式系统复制模型有3种,分别是主从复制模型、多主复制模型以及无主复制模型。此外,复制从客户端的时效性来说分为同步复制&&异步复制,异步复制具有滞后性,可能会造成数据不一致,因为这个不一致,会带来各种各样的问题。

在上一篇的最后,我们对分布式系统系统模型做了一些假设,这些假设对给出后面的解决方案其实是非常重要的。首先针对部分失效,是我们需要对系统的超时进行假设,一般我们假设为半同步模型,也就是说一般情况下延迟都非常正常,一旦发生故障,延迟会变得偏差非常大。另外,对于节点失效,我们通常在设计系统时假设为崩溃-恢复模型。最后,面对分布式系统的两个保证Safty和Liveness,我们优先保证系统是Safety,也就是安全;而Liveness(活性)通常在某些前提下才可以满足。

说到事务,相信大家都能简单说出个一二来,首先能本能做出反应出的,应该就是所谓的“ACID”特性了,还有各种各样的隔离级别。是的,它们确实都是事务需要解决的问题。

在这一章中,我们会更加有条理地理解下它们之间的内在联系,详细看一看事务究竟要解决什么问题。在《DDIA》一书中有非常多关于数据库事务的具体实现细节,但本文中会弱化它们,毕竟本文不想详细介绍如何设计一款数据库,我们只需探究问题的本身,等真正寻找解决方案时再去详细看设计,效果可能会更好。下面我们正式开始介绍事务。

系统中可能会面临下面的问题:

假设上述问题都会出现在我们对于存储系统(或者数据库)的访问中,这样我们在开发自己应用程序的同时,还需要额外付出很大代价处理这些问题。事务的核心使命就是尝试帮我们解决这些问题,提供了从它自己层面所看到的安全性保证,让我们在访问存储系统时只专注我们本身的写入和查询逻辑,而非这些额外复杂的异常处理。而说起解决方式,正是通过它那大名鼎鼎的ACID特性来进行保证的。

这四个缩写所组成的特性相信大家已形成本能反应,不过《DDIA》一书中给出的定义确实更加有利于我们更加清晰地理解它们间的关系,下面将分别进行说明:

A:原子性(Atomicity):原子性实际描述的是同一个客户端对于多个操作之间的限制,这里的原子表示的是不可分割,原子性的效果是,假设有操作集合{A,B,C,D,E},执行后的结果应该和单个客户端执行一个操作的效果相同。从这个限制我们可以知道:

因此,对于原子性而言,书中描述说的是能在执行发生异常时丢弃,可以直接终止,且不会对服务端产生任何副作用,可以安全的重试,原子性也成为“可终止性”。

C:一致性(Consistency):这个名词有太多的重载,也就是说它在不同语境中含义会截然不同,但可能又有联系,这就可能让我们陷入混乱,比如:

书中强调,这个里面的一致性更多需要用户的应用程序来保证,因为只有用户知道所谓的不变式是什么。这里举一个简单的小例子,例如我们往Kafka中append消息,其中有两条消息内容都是2,如果没有额外的信息时,我们也不知道到底是客户端因为故障重试发了两次,还是真的就有两条一模一样的数据。

如果想进行区分,可以在用户程序消费后走自定义的去重逻辑,也可以从Kafka自身出发,客户端发送时增加一个“发号”环节标明消息的唯一性(高版本中Kafka事务的实现大致思路)这样引擎本身就具备了一定的自己设置“不变式”的能力。不过如果是更复杂的情况,还是需要用户程序和调用服务本身共同维护。

这里给一个具体的实例来直观感受下,如下图展示了两个客户端并发的修改DB中的一个counter,由于User2的getcounter发生的时刻在User1更新的过程中,因此读到的counter是个旧值,同样User2更新也类似,所以最后应该预期counter值为44,结果两个人看到的counter都是43(类似两个线程同时做value++)。

一个完美的事务隔离,在每个事务看来,整个系统只有自己在工作,对于整个系统而言这些并发的事务一个接一个的执行,也仿佛只有一个事务,这样的隔离成为“可序列化(Serializability)”。当然,这样的隔离级别会带来巨大的开销,因此出现了各种各样的隔离级别,进而满足不同场景的需要。后文会详细介绍不同的隔离级别所解决的问题。

D:持久性(Durability):这个特性看似比较好理解,就一点,只要事务完成,不管发生任何问题,都不应该发生数据丢失。从理论上讲,如果是单机数据库,起码数据已被写入非易失性存储(至少已落WAL),分布式系统中数据被复制到了各个副本上,并受到副本Ack。但实际情况下,也未必就一定能保证100%的持久性。这里面的情况书中有详细的介绍,这里就不做重复的Copy工作了,也就是说事务所保证的持久性一般都是某种权衡下的结果。

上面四个特性中,实际上对于隔离性的问题,可能是问题最多样的,也是最为复杂的。因为一味强调“序列化”可能会带来不可接受的性能开销。因此,下文将重点介绍一些比可序列化更弱的隔离级别。

在介绍后面内容前,有两件事需要事先做下强调,分别是事务操作的对象以及事务的提交与重试,分为单对象&&多对象。

单对象写入:这种书中给出了两种案例。

这种事务的解决方法一般是通过日志回放(原子性)、锁(隔离性)、CAS(隔离性)等方式来进行保证。

另一个需要特别强调的点是重试,事务的一个核心特性就是当发生错误时,客户端可以安全的进行重试,并且不会对服务端有任何副作用,对于传统的真的实现ACID的数据库系统,就应该遵循这样的设计语义。但在实际实践时,如何保证上面说的能够“安全的重试”呢?书中给出了一些可能发生的问题和解决手段:

事务隔离要解决的是并发问题,并发问题需要讨论两个问题时序与竞争,往往由于事物之间的操作对象有竞争关系,并且又因为并发事务之间不确定的时序关系,会导致这些所操作的有竞争关系的对象会出现各种奇怪的结果。

所谓不同的隔离级别,就是试图去用不同的开销来满足不同场景下对于时序要求的严格程度。我们可能不一定知道具体怎么实现这些事务隔离级别,但每个隔离级别解决的问题本身我们应该非常清晰,这样才不会在各种隔离级别和开销中比较轻松的做权衡。这里,我们不直接像书中一样列举隔离级别,我们首先阐述并发事务可能产生的问题,然后再去介绍每种隔离级别分别能够解决那些问题。

所谓脏读,指的就是用户能不能看到一个还没有提交事务的结果,如果是,就是脏读。下图展示了没有脏读应该满足什么样的承诺,User1的一个事务分别设置x=3、y=3,但在这个事务提交之前,User2在调用getx时,需要返回2,因为此时User1并没有提交事务。

防止脏读的意义:

如果一个客户端覆盖了另一个客户端尚未提交的写入,我们就称这样的现象为脏写。

这里同样给个实例,对于一个二手车的交易,需要更新两次数据库实现,但有两个用户并发的进行交易,如果像图中一样不禁止脏写,就可能存在销售列表显示交易属于Bob但发票却发给了Alice,因为两个事务对于两个数据的相同记录互相覆盖。

直接上例子,Alice在两个银行账户总共有1000块,每个账户500,现在她想从一个账户向另一个账户转账100,并且她想一直盯着自己的两个账户看看钱是否转成功了。不巧的是,他第一次看账户的时候转账还没发生,而成功后只查了一个账户的值,正好少了100,所以最后加起来会觉得自己少了100元。

如果只是这种场景,其实只是个临时性的现象,后面再查询就会得到正确的值,但是如果基于这样的查询去做别的事情,那可能就会出现问题了,比如将这个记录Select出来进行备份,以防DB崩溃。但不巧如果后面真的崩溃,如果基于这次查询到的数据做备份,那这100元可能真的永久的丢失了。如果是这样的场景,不可重复读是不能被接受的。

这里直接把之前那个两个用户同时根据旧值更新计数器的例子搬过来,这是个典型的更新丢失问题:

这种问题描述的是,事务的写入需要依赖于之前判断的结果,而这个结果可能会被其他并发事务修改。

实例中有两个人Alice和Bob决定是否可以休班,做这个决定的前提是判断当前是否有两个以上的医生正在值班,如果是则自己可以安全的休班,然后修改值班医生信息。但由于使用了快照隔离(后面会介绍)机制,两个事务返回的结果全都是2,进入了修改阶段,但最终的结果其实是违背了两名医生值班的前提。

造成这个问题的根本原因是一种成为“幻读”的现象,也就是说两个并发的事务,其中一个事务更改了另一个事物的查询结果,这种查询一般都是查询一个聚合结果,例如上文中的count或者max、min等,这种问题会在下面场景中出现问题。

上面我们列举了事务并发可能产生的问题,下面我们介绍各种隔离级别所能解决的问题。

事务用它的ACID特性,为用户屏蔽了一些错误的处理。首先,原子性为用户提供了一个可安全重试的环境,并且不会对相应的系统产生副作用。一致性能够在一定程度上让程序满足所谓的不变式,隔离性通过不同的隔离级别解决不同场景下由于事务并发导致的不同现象,不同的隔离性解决的问题不同,开销也不同,需要用户按需决策,最后持久性让用户安心的把数据写进我们设计的系统。

总体而言,事务保证的是不同操作之间的一致性,一个极度完美的事务实现,让用户看上去就只有一个事务在工作,每次只执行了一个原子操作。因此,我们称事务所解决的是操作的一致性。这一章中,我们更多谈论的还是单机范围的事务。接下来,我们会把问题阈扩大,实际上分布式系统也有这样的问题,并且分布式系统还有类似的复制滞后问题,导致就算看似是操作的是一个对象,也存在不同的副本,这会使得我们所面对的问题更加复杂。下一章,我们重点介绍另一种一致性问题以及解决。

这里我们首先回到上一篇中讲的复制的滞后性,滞后性所带来的的一个最直观的问题就是,如果在复制期间客户端发起读请求,可能不同的客户端读到的数据是不一样的。这里面书中给了三种不同类型的一致性问题。我们分别来看这些事例:

除了这两种情况外,还有一种情况,如下图所示:

这个问题会比前面的例子看上去更荒唐,这里有两个写入客户端,其中Poons问了个问题,然后Cake做出了回答。从顺序上,MrsCake是看到Poons的问题之后才进行的回答,但是问题与回答恰好被划分到了数据库的两个分区(Partition)上,对于下面的Observer而言,Partition1的Leader延迟要远大于Partition2的延迟,因此从Observer上看到的是现有答案后有的问题,这显然是一个违反自然规律的事情,如果能避免这种问题出现,那么可称为系统实现了“前缀读一致性”。

在上一篇中,我们介绍了一可以检测类似这种因果的方式,但综上,我们可以看到,由于复制的滞后性,带来的一个后果就是系统只是具备了最终一致性,由于这种最终一致性,会大大的影响用户的一些使用体验。上面三个例子虽然代表了不同的一致性,但都有一个共性,就是由于复制的滞后性带来的问题。所谓复制,那就是多个客户端甚至是一个客户端读写多个副本时所发生的的问题。这里我们将这类一致性问题称为“内部一致性(内存一致性)”,即表征由于多个副本读写的时序存在的数据不一致问题。

实际上,内部一致性并不是分布式系统特有的问题,在多核领域又称内存一致性,是为了约定多处理器之间协作。如果多处理器间能够满足特定的一致性,那么就能对多处理器所处理的数据,操作顺序做出一定的承诺,应用开发人员可以根据这些承诺对自己的系统做出假设。如下图所示:

每个CPU逻辑核心都有自己的一套独立的寄存器和L1、L2Cache,这就导致如果我们在并发编程时,每个线程如果对某个主存地址中变量进行修改,可能都是优先修改自己的缓存,并且读取变量时同样是会先读缓存。这实际上和我们在分布式中多个客户端读写多个副本的现象是类似的,只不过分布式系统中是操作粒度,而处理器则是指令粒度。在多处理器的内存一致性中,有下面几种常见的模型。

可以看到,这些一致性约束的核心区分点就是在产生并发时对顺序的约束,而用更专业一点的词来说,线性一致性需要的是定义“全序”,而其他一致性则是某种“偏序”,也就是说允许一些并发操作间不比较顺序,按所有可能的排列组合执行。

如下图所示:

分布式中的内部一致性主要分为4大类:线性一致性–>顺序一致性–>因果一致性–>处理器一致性,而从偏序与全序来划分,则划分为强一致性(线性一致性)与最终一致性。

满足线性一致性的系统给我们这样一种感觉,这系统看着只有一个副本,这样我就可以放心地读取任何一个副本上的数据来继续我们的应用程序。这里还是用一个例子来具体说明线性一致性的约束,如下图所示:

这里有三个客户端同时操作主键x,这个主键在书中被称为寄存器(Register),对该寄存器存在如下几种操作:

如图中所示,在C更新x的值时,A和B反复查询x的最新值,比较明确的结果是由于ClientA在ClientC更新x之前读取,所以第一次read(x)一定会为0,而ClientA的最后一次读取是在ClientC成功更新x的值后,因此一定会返回1。而剩下的读取,由于不确定与write(x,1)的顺序(并发),因此可能会返回0也可能返回1。对于线性一致性,我们做了下面的规定:

比如用户上传图片,类似后端存储服务可能会根据全尺寸图片生成低像素图片,以便增加用户服务体验,但由于MQ不适合发送图片这种大的字节流,因此全尺寸图片是直接发给后端存储服务的,而截取图片则是通过MQ在后台异步执行的,这就需要2中上传的文件存储服务是个可线性化的存储。如果不是,在生成低分辨率图像时可能会找不到,或读取到半张图片,这肯定不是我们希望看到的。

线性化不是避免竞争的唯一方法,与事务隔离级别一样,对并发顺序的要求,可能会根据场景不同有不同的严格程度。这也就诞生了不同级别的内部一致性级别,不同的级别也同样对应着不同的开销,需要用户自行决策。

说明了线性化系统的用处,下面我们来考虑如何实现这样的线性化系统。

但是其实CAP理论的定义面还是比较窄的,其中C只是线性一致性,P只代表网络分区(彻底断开,而不是延迟),这里面实际有相当多的折中,就可以完全满足我们系统的需求了,所以不要迷信这个理论,还是需要根据具体的实际情况去做分析。

从对线性一致性的定义我们可以知道,顺序的检测是实现线性化系统的关键,这里我们跟着书中的思路一步步地来看:我们怎么能对这些并发的事务定义出它们的顺序。

上面被动地不加任何限制的捕捉因果,会带来巨大的运行开销(内存,磁盘),这种关系虽然可以持久化到磁盘,但分析时依然需要被载入内存,这就让我们有了另一个想法,我们是否能在操作上做个标记,直接定义这样的因果关系?

最最简单的方式就是构建一个全局发号器,产生一些序列号来定义操作间的因果关系,比如需要保证A在B之前发生,那就确保A的全序ID在B之前即可,其他的并发操作顺序不做硬限制,但操作间在处理器的相对顺序不变,这样我们不但实现了因果一致性,还对这个限制进行了增强。

上面的设想虽然比较理想,但现实永远超乎我们的想象的复杂,上面的方式在主从复制模式下很容易实现,但如果是多主或者无主的复制模型,我们很难设计这种全局的序列号发号器,书中给出了一些可能的解决方案,目的是生成唯一的序列号,比如:

如果只有这两个比较,还不能解决上面的因果偏序被打破的问题,但是这个算法不同的是,它会把这个Node的Counter值内嵌到请求的响应体中,比如图中的A,在第二次向Node2发送更新max请求时,会返回当前的c=5,这样Client会把本地的Counter更新成5,下一次会增1这样使用Node上的Counter就维护了各个副本上变量的偏序关系,如果并发往两个Node里写就直接定义为并发行为,用NodeId定义顺序了。

假设只有单核CPU,那么天然就是全序的,但是现在我们需要的是在多核、多机、分布式的情况下实现这个全序的广播,就存在这一些挑战。主要挑战是两个:

对于多机,实际上实现全序广播最简单的实现方式使用主从模式的复制,让所有的操作顺序让主节点定义,然后按相同的顺序广播到各个从节点。对于分布式环境,需要处理部分失效问题,也就是如果主节点故障需要处理主成员变更。下面我们就来看看书中是怎么解决这个问题的。

这里所谓的全序一般指的是分区内部的全序,而如果需要跨分区的全序,需要有额外的工作。

对于全序广播,书中给了两条不变式:

实现层面

我们对着上面的不变式来谈谈简单的实现思路,首先要做到可靠发送,这里有两层含义:

其中消息不能丢意味着如果某些节点出现故障后需要重试,如果需要安全的重试,那么广播操作本身失败后就不能对系统本身有副作用,否则就会导致消息发送到部分节点上的问题。上一章的事务的原子性恰好就解决的是这个问题,这里也就衍射出我们需要采用事务的一些思路,但与上面不同,这个场景是分布式系统,会发到多个节点,所以一定是分布式事务(耳熟能详的2PC一定少不了)。

现在假设我们已经有了全序广播,那么我们继续像我们的目标–线性化存储迈进,首先需要明确一个问题,线性化并不等价于全序广播,因为在分布式系统模型中我们通常采用异步模型或者半同步模型,这种模型对于全序关系何时成功发送到其他节点并没有明确的承诺,因此还需要再全序广播上做点什么才真正能实现线性化系统。

书中仍然举了唯一用户名的例子:可以采用线性化的CAS操作来实现,当用户创建用户名时当且仅当old值为空。实现这样的线性化CAS,直接采用全序广播+Log的方式。

而这些日志条目会以相同的顺序广播到所有节点,如果出现并发写入,就需要所有节点做决策,是否同意,以及同意哪一个节点对这个用户名的占用。以上我们就成功实现了一个对线性CAS的写入的线性一致性。然而对于读请求,由于采用异步更新日志的机制,客户端的读取可能会读到旧值,这可能需要一些额外的工作保证读取的线性化。

上面我们在实现线性化系统时,实际上就有了一点点共识的苗头了,即需要多个节点对某个提议达成一致,并且一旦达成,不能被撤销。在现实中很多场景的问题都可以等价为共识问题:

实际上,为以上任何一个问题找到解决方案,都相当于实现了共识。

书中直接以原子提交为切入点来聊共识。这里不过多说明,直接介绍两阶段提交,根据书中的描述,两阶段提交也算是一种共识算法,但实际上在现实中,我们更愿意把它当做实现更好共识算法的一个手段以及分布式事务的核心实现方法(Raft之类的共识算法实际上都有两阶段提交这个类似的语义)。

这个算法实际上比较朴素,就是两个阶段,有一个用于收集信息和做决策的协调者,然后经过朴素的两个阶段:

这里一个看似非常简单的算法,平平无奇,无外乎比正常的提交多了个准备阶段,为什么说它就可以实现原子提交呢?这源于这个算法中的约定承诺,让我们继续拆细这个流程:

正是由于上面的两个承诺保证了2PC能达成原子性,也是这个范式存在的意义所在。

看完了一个特例,书中总结了共识算法的几个特性:

如果我们用这几个特性对比2PC,实际上却是可以认为它算是个共识算法,不过这些并不太重要,我们重点还是看这些特性会对我们有什么样的启发。

前三个特性规定了安全性(Safety),如果没有容错的限制,直接人为指定个StrongLeader,由它来充当协调者,但就像2PC中的局限性一样,协调者出问题会导致系统无法继续向后执行,因此需要有额外的机制来处理这种变更(又要依赖共识),第四个特性则决定了活性(Liveness)之前的分型中说过,安全性需要优先保证,而活性的保证需要前提。这里书中直接给出结论,想让可终止性满足的前提是大多数节点正确运行。

实际在最终设计算法并落地时,并不是让每一条消息去按照上面4条特性来一次共识,而是直接采用全序广播的方式,全序广播承诺消息会按相同的顺序发送给各个节点,且有且仅有一次,这就相当于在做多轮共识,每一轮,节点提出他们下面要发送的消息,然后决定下一个消息的全序。使用全序广播实现共识的好处是能提供比单轮共识更高的效率(ZAB,Raft,Multi-paxos)。

这里面还有一些事情可以拿出来做一些讨论。首先,从实现的角度看,主从复制的模式特别适用于共识算法,但在之前介绍主从复制时,但光有主从复制模型对解决共识问题是不够的,主要有两点:

同样的,在主节点做决策之前,也需要判断有没有更高Epoch的节点同时在进行决策,如果有,则代表可能发生冲突(Kafka中低版本只有Controller有这个标识,在后面的版本中,数据分区同样带上了类似的标识)。此时,节点不能仅根据自己的信息来决定任何事情,它需要收集Quorum节点中收集投票,主节点将提议发给所有节点,并等待Quorum节点的返回,并且需要确认没后更高Epoch的主节点存在时,节点才会对当前提议做投票。

详细看这里面涉及两轮投票,使用Quorum又是在使用所谓的重合,如果某个提议获得通过,那么投票的节点中一定参加过最近一轮主节点的选举。这可以得出,此时主节点并没有发生变化,可以安全的给这个主节点的提议投票。

另外,乍一看共识算法全都是好处,但看似好的东西背后一定有需要付出的代价:

虽然,可以根据上面的描述自己来实现共识算法,但成本可能是巨大的,最好的方式可能是将这个功能外包出去,用成熟的系统来实现共识,如果实在需要自己实现,也最好是用经过验证的算法来实现,不要自己天马行空。ZK和etcd等系统就提供了这样的服务,它们不仅自己通过共识实现了线性化存储,而且还对外提供共识的语义,我们可以依托这些系统来实现各种需求:

本章花费了巨大力气讲解了分布式系统中的另一种一致性问题,内部一致性,这种问题主要是因为复制的滞后性产生,首先我们介绍了这种问题的起源,然后映射到分布式系统中,对不同一致性进行分类。

面对这么多问题,如果一个理想的分布式数据系统,如果不考虑任何性能和其他的开销,我们期望实现的系统应该是这样的:

所以我们知道,线性一致性和串行化是两个正交的分支,分别表示外部一致性中的最高级别以及内部一致性的最高级别。如果真的实现这个,那么用户操作这个系统会非常轻松。但很遗憾,达成这两方面的最高级别都有非常大的代价,因此由着这两个分支衍生出各种的内部一致性和外部一致性。

但是需要注意的是,在分布式系统中,这两种一致性也并非完全孤立,我们一般采用共识算法来实现线性一致,而在实现共识算法的过程中,同样可能涉及单个操作涉及多个对象的问题,因为分布式系统的操作,往往可能是作用在多个副本上的。也就是说,类似2PC这样的分布式事务同样会被用来解决共识问题(虽然书中把它也成为共识,但其实还是提供了一种类似事务原子性的操作),就像Java并发编程中,我们在synchronize方法中也可能会使用一些volatile变量一样。

而2PC不是分布式事务的全部,可能某些跨分区的事务同样需要用基于线性一致性的操作来满足对某个对象操作的一致性。也就是说想完整的实现分布式的系统,这两种一致性互相依赖,彼此互补,只有我们充分了解它们的核心作用,才能游刃有余地在实战中应用这些看似枯燥的名词。

用户首先通过配置acks先大体知道复制模式,如果ack=1或者0,则表示完全的异步复制;如果acks=all则代表完全的同步复制。而如果配置了异步复制,那么单分区实际上并不能保证线性一致性,因为异步复制的滞后性会导致一旦发生Leader变更可能丢失已经提交的消息,导致打破线性一致性的要求。

而如果选择ack=-1,则代表纯的同步复制,而此时如果没有min.insync.replicas的限制,那样会牺牲容错,多副本本来是用来做容错,结果则是有一个副本出问题系统就会牺牲掉Liveness。而min.insync.replicas参数给了用户做权衡的可能,一般如果我们要保证单分区线性一致性,需要满足多数节点正常工作,因此我们需要配置min.insync.replicas为majority。

而针对部分失效的处理,在实现复制时,kafka将成员变更进行了外包,对于数据节点而言,托管给Controller,直接由其指定一个新的主副本。而对于Controller节点本身,则将这个职责托管给了外部的线性存储ZK,利用ZK提供的锁于租约服务帮助实现共识以达成主节点选举,而在高版本中,Kafka去掉了外部的共识服务,而转而自己用共识算法实现Controller选主,同时元数据也由原来依赖ZK变为自主的Kraft实现的线性化存储进行自治。

一个大致的实现流程基本如下:首先向协调者获取事务ID(后文统称TID),然后向参与者发送请求准备提交,带上这个TID,参与者现在本地做append,如果成功返回,协调者持久化决策的内容,然后执行决策,参与者将消息真正写到Log中(更新LSO,与HW高水位区分)。但是上文也讲了2PC实际上是有一些问题的,首先2PC协调者的单点问题,Kafka的解决方法也比较简单,直接利用自己单分区同步复制保证线性一致性的特性,将协调者的状态存储在内部Topic中,然后当协调者崩溃时可以立刻做转移然后根据Topic做恢复,因为Topic本身就单分区而言就是个线性存储。

另外,就是2PC的协调者本质是个主从复制的过程,由于TransactionCoordinator本来就挂靠在Broker上,所以这个选举依然会委托给Controller,这样就解决了2PC中的比较棘手的问题。而对于事务的隔离级别,Kafka仅实现到了“读已提交(RC)”级别。

Jepsen主要有下面几个模块构成:

我们团队使用Jepsen测试了Kafka系统的一致性,其中Kafka客户端与服务端的配置分别为:同步复制(ack=-1),3复制因子(副本数),最小可用副本为2(min.insync.isr)。在该配置下,Jepsen内置的故障注入最后均通过了验证。

THE END
1.什么是算法?算法的概念算法(Algorithm)是指解决特定问题的一系列明确、有限且可执行的步骤或规则。算法是计算机科学的核心概念之一,用于指导计算机完成各种任务,从简单的数学运算到复杂的数据处理和决策。 一、算法的基本定义 从广义上讲,算法可以定义为: 有穷性:一个算法必须在有限的步骤内完成。也就是说,算法不能无限循环,它必须在合理https://blog.csdn.net/weixin_48579910/article/details/141722210
2.算法是指什么?算法概述算法是指解题方案的准确而完整的描述,是一系列解决问题、高度符合逻辑性、可执行性的指令集合,代表运用系统方法描述解决问题的策略机制。算法能够对一定规范的输入在有限时间内运行得到输出。 算法中的指令描述的是计算过程,当其运行时能从初始状态和初始输入(初始输入可能为空的)开始,经过一系列有限而清晰定义的状态,https://m.elecfans.com/article/2008707.html
3.c语言程序设计知识点第二章算法 1、算法的定义 一个程序应包括①数据结构即对数据的描述;②算法也就是操作步骤。计算机算法可分为两大类:数值运算算法和非数值运算算法。 2、算法的特性(P19) 1.有穷性; 2.确定性; 3.有零个或多个输入; 4.有一个或多个输出; https://www.jianshu.com/p/d36002b8e9d7
4.把这些计算机基础知识学完后,我飘了!压缩算法的定义 上面介绍了文件的集合体其实就是一堆字节数据的集合,那么我们就可以来给压缩算法下一个定义。 压缩算法(compaction algorithm)指的就是数据压缩的算法,主要包括压缩和还原(解压缩)的两个步骤。 其实就是在不改变原有文件属性的前提下,降低文件字节空间和占用空间的一种算法。 https://maimai.cn/article/detail?fid=1400649709&efid=VmhIiOBVBH0ua86U1tJoEA
5.blood我如何治疗复发难治性急性髓系白血病R/R AML的定义和治疗算法 2022年ELN建议中关于复发难治性AML的缓解标准,不仅基于血液学标准,还基于可测量残留病变(MRD)的评估(多参数流式细胞术或分子学检测)(表1),由MRD触发的治疗(包括异基因HCT)最近已发展成预防明显血液学复发的方案。 诊断R/R AML患者时需进行重复突变分析,因为突变水平的克隆演变经常发生https://www.medsci.cn/article/show_article.do?id=bff4803010de
6.二叉树遍历数据结构腾讯云开发者社区(1) 访问根结点; (2) 遍历左子树; (3) 遍历右子树。 上图所示二叉树的遍历结果是:ABDECF 2.中序遍历的递归算法定义:若二叉树非空,则依次执行如下操作: (1)遍历左子树; (2)访问根结点; (3)遍历右子树。 上图所示二叉树的遍历结果是:DBEAFC https://cloud.tencent.com/developer/article/1981885
7.Darkhotel组织渗透隔离网络的Ramsay组件分析1. 算法重叠Ramsay在数据落地前使用的自定义加密算法逻辑,同奇安信之前披露过【2】的,Darkhotel组织多次用过的算法一致:图4-9 Ramsay的算法样例图4-10早前奇安信披露的算法以及两种算法的组合选择,其中的第二种与曾经披露过的仅多了个加法步骤:图4-11此次样本的算法样例https://www.antiy.cn/research/notice&report/research_report/20200522.html
8.《计算方法》课程教学大纲本课程主要介绍应用计算机求解或数值模拟各类数学问题的基本方法,帮助学生掌握最基本的数值算法,构造数值算法的主要思想方法和工具,以及在应用数值算法时应注意的问题:算法的计算效率、收敛性、数值稳定性、误差估计和算法的适用范围等。 具体要求如下: 1.数值方法的基本问题 (1)了解浮点数系的定义、性质,浮点数系中http://math.xjtu.edu.cn/info/1036/3033.htm
9.硬盘基本知识大全(1)“簇”是DOS进行分配的最小单位。 (2)不同的存储介质,不同容量的硬盘,不同的DOS版本,簇的大小也不一样。 (3)簇的概念仅适用于数据区。 3.扇区编号定义:绝对扇区与DOS扇区 由前面介绍可知,我们可以用柱面/磁头/扇区来唯一定位磁盘上每一个区域,或是说柱面/磁头/扇区与磁盘上每一个扇区有一一对应关系,https://www.yjbys.com/edu/yingjianweihu/448825.html
10.PBL视野下初中信息科技计算思维培育的探索——以《人工智能入门1.为PBL活动做准备 (1)寻找核心知识 明确活动起点 PBL的设计的起点是从概念到知识点的一整套知识体系,这具体包括核心概念,以及概念下的知识点[11]。教师可以从大概念出发,寻找大概念下对应的知识点,结合教材、学生的认知水平构建学习活动的主题。例如“重用”是程序设计基础中的一个重要概念,涉及“自定义函数”、https://tpd.xhedu.sh.cn/cms/app/info/doc/index.php/91964
11.第一章算法的概念1.下列关于算法的说法正确的是() A.一个算法的步骤是可逆的 B.描述算法可以有不同的方式 C.算法可以看成是按照要求设计好的、有限的、确切的计算序列,并且这样的步骤或序列只能解决当前问题 D.算法只能用一种方式显示 ★答案★B 解析由算法的定义知A,C,D错. 2.下列叙述中: ①植树需要运苗、挖坑、栽苗、https://wenku.baidu.com/view/c8d30824ad1ffc4ffe4733687e21af45b207fe6b.html
12.第三章卡尔曼滤波3.2算法和模型1定义51CTO博客第三章 卡尔曼滤波3.2 算法和模型-1定义,3.2.1定义离散时间卡尔曼滤波中所有误差的时变特性可归为以下三种假设中的一种:系统误https://blog.51cto.com/u_15754466/5585786