《Designing Data-Intensive Applications》笔记:3:事务与一致性

1.事务及隔离级别

1.1.Read Committed

定义

一个事务只能看到其它事务已经提交的修改,不能看到其它事务进行中产生的修改。

实现方法

对任一事务修改的数据,在事务提交前均同时记录新值和旧值。其它事务读到此数据时,使用旧值;本事务读取时,使用新值。

一致性缺陷:不可重复读
有可能发生“不可重复读”的情况:即在一个事务中,一次读到的值和下次读到的值会不同,因为存在另一个事务修改并提交了相关的数据。

1.2.Snapshot or Repeatable Read

定义

在Read Committed的基础上,一个事务永远能读到相同的值

解决了Read Skew(不可重复读)的问题。

实现方法

对每个对象保存一个版本向量,即MVCC:Multi-Value Concurrency Control

对每个有事务读取对象保存多个版本:每个版本记录了创建此版本的事务id,此版本后续被哪个事务覆盖的事务id,以及当前版本的值。

当一个事务开启时,记录开启的时刻正在运行的事务id列表。事务列表中的所有事务,以及在当前事务之后开启的所有事务,产生的修改(包括删除与新增版本),均对当前事务不可见。

对于每一个版本而言,若没有进行中的事务与此事务有交叠,且当前版本不是最后的版本,那么此版本可以删除。

一致性缺陷:幻读 与 写交错

无法解决Write Skew(幻读):即两个事务读取了同一个集合中的数据,并修改此集合中的部分/全部数据。

当这个集合只有一个值时,即发生“写丢失”的情况。因为两个事务都读取了值,又在此基础上更新同一个对象,会造成两个事务相互覆盖的情况,只有最后一个事务的结果被保留。

当这个集合不止一个值时,可能发生两个事务同时查询并计算集合的结果,但又分别更新不同的部分,导致两个事务做出决策时的前提条件均被打破,影响不变性规则。

因此,写交错可以看成是写丢失的一种特殊情况。

注意,这个集合的数据可以是未插入的未来数据,或不存在的数据。泛指一切会影响决策的数据,而数据不存在也是一种决策的依据。

1.3.Serializable

定义

完全串行,能解决Write Skew。

实现方法1:两阶段锁

读时上“共享读”锁,若需要修改,则需要将锁升级为“排他写”锁。因此,可以若多个事务同时读一个集合的数据并依此做出决策,但当修改此集合的数据时,需要升级为写锁,必须等其它事务都结束,但每个事务都因为不能升级锁而无法结束,产生死锁情况。因此会强制中断其余事务,只有一个事务能正常提交。从而消除了写交错的问题。

当查询到还未存在的记录时,就无法对行上锁,只能对相关的索引页,甚至全表上锁。因此会极大地影响性能。

实现方法2:SSI(Serializable Snapshot Isolation)

在Snapshot的MVCC的基础上,在事务提交时,判断事务修改的数据,是否被其它事务读到,它会通知其它事务这些数据已经修改。当其它事务也进行了修改后,在提交时,会发现读到的数据已经被其它事务修改了,此时会终止事务。若被影响的事务是只读事务,不会commit,那么就不会被终止。

对于未存在的记录,也需要对索引页或表记录影响。

方法对比

两阶段锁属于悲观锁,而SSI属于乐观锁。

SSI对于长时间运行的读事务更不敏感,而两阶段锁对长时间运行的读、写事务均很敏感。SSI解决了大部分的负面效果,性能优于两阶段锁,和单线程执行。

2.一致性与共识

2.1.线性一致

定义

对外就像是只有一份拷贝,一旦修改的新值对外界任何一个请求可见,那么之后这个新值对后续的所有请求都可见。即第一次出现新值后,永远返回新值。

对于符合线性一致的系统,所有操作生效的时间点能画出一条不断前进的线,表示所有写都依次生效且对外可见。

与事务Serializable的区别

线性一致性对事务并没有任何假设或要求。线性一致性是一种对单个对象的“最近”生效保证;而串行隔离级别则是一种事务级别,是对多个对象读写的操作的并发控制,而且事务执行的先后顺序和提交的顺序可以不同。

对于两阶段锁和单线程执行而言,这两个Serializable的方案同时也是线性的。但对于SSI则并非线性一致,因为SSI是通过快照的方式读取的数据,而不同时刻启动的事务,在分别进行的过程中读取同一对象,可能会读到不同的值。

应用场景

选主投票、唯一性约束

多渠道时间依赖:打破线性一致性的表现,只可能出现在除了当前渠道之外,还有其它的通信交互渠道的情况下。

2.2.全序广播

全序广播有两个条件:1. 可靠传输,消息会广播给所有节点。2. 全序保序,所有消息按相同顺序发送。

因果一致

因果一致指有前后关系的两个操作之间有顺序,但是没有因果的两个操作之间的顺序可以任意指定。因此因果一致比线性一致更弱:因为线性一致是全序可比,但因果一致是偏序可比。

对于因果一致性,要求系统保留写操作所关联的所有相关读操作(对应的写操作),以此来记录各个操作之间的因果顺序关系。

CAP虽然说明了在分区不可用的情况下可以实现CA,但没有提及性能因素,因此在实践中没有指导性。而研究证明,因果关系是在网络分区不可用、又不损耗性能的情况下,能实现的最强一致性。

但记录所有的因果关系是不现实的,因此换成记录一个全序关系,这个全序关系满足因果一致性。这个全序关系可以简单地通过单主的操作记录来实现。

而得到了这个全序关系之后,后续就需要将这个全序关系广播到所有节点。

使用全序广播来实现线性一致

将所有的写操作通过全序广播发送到所有节点,若直接读取,则可能读到不一致的情况。若能一致,则是顺序一致/时间性一致,是一种比线性关系稍弱的一致性。

但只要把读操作也作为全序的一部分发送出去,那么当你自身又接受到这条消息的时候,正是这条读操作真正发生的因果时间。此时进行读操作,就能让所有在某个写操作之后发生的读操作都读到新值,从而实现了线性一致。

使用线性一致实现全序广播

只需要使用一个分布式的线性一致整数寄存器就能实现全序广播。即对于每一条操作,递增整数寄存器获得一个新值。由于寄存器是线性一致的,因此在多线程分布式的情况下,每个请求都能分配到一个递增的新值。

再将序号与消息一起发送给所有节点。若发送失败则一直重试。当节点收到N消息时,若N之前的消息也都到达,则可以开始处理N消息,从而满足全序广播的保序和可靠传输这两个条件。

2.3.共识

共识要求所有活着的节点(至少存活半数以上)都对做出相同的决策值。

分布式事务

两阶段事务的流程如下:

首先由协调方对参与成员提交写请求,然后发起准备投票,等待所有参与成员的回复。若所有参与成员均回复yes,即确认可以提交,则由协调方再发起commit的命令,让所有成员一起提交;有成员回复no,则整个事务回滚。所有成员在commit执行成功之后,还需要再向协调方回复成功的消息。若有成员提交失败,则必须不断重试直到成功。

在此过程中,当协调方收到所有的yes请求后,会将这个事务是提交还是回滚的决策记录到本地硬盘中,若此时协调方崩溃或下线,则所有成员必须等待协调方再次上线。协调方重启后读取硬盘上的日志,若在决策写入日志之后,则能恢复事务,继续发出commit的命令;若在写入日志之前崩溃,则此事务回滚。相当于各个成员在回复yes后,就放弃了回滚此修改的权力,必须提交成功。

缺点:

a. 协调方本身是一个单点故障

b. 功能有限,只有在同构产品中的支持比较好;而异构的XA transaction则必须要求组件支持相应的接口。

c. 一旦承诺必须执行,否则只能人工介入,灵活性欠缺。

共识算法

由于全序广播相当于是所有节点对log里的每一项进行共识,决定log中下一个消息的内容。因此,使用共识算法可以实现全序广播。一般开源系统都直接实现了全序广播而非共识算法,但效果是一样的。

可以通过共识算法选出主节点,然后由主节点产生全序广播,就能解决共识问题了。在目前的选主协议中,大致流程如下:

在主能够执行操作之前,共有两轮投票。第一轮是选主投票,第二轮是主(启动之后,操作之前)需要再进行确认的投票,以排除当自己下线再上线时,已经有其它主被选出的情况。每次选主的时候都有一个递增的序号,在第二轮投票时,主会向所有人寻问每个人认为的主是谁,若不存在比自己序号更大的主,则自己就能开始工作。

和两阶段事务的区别在于:1. 两阶段事务的协调方是指定的,而不是系统自动推举的。2. 两阶段事务需要所有的参与者都成功提交才算成功,而选主算法则只要求大多数成员认可。 3. 两阶事务在单点故障时会一直卡住,而选主过程还考虑了主节点在下线之后能够重新回到正确状态。

大部分共识算法都在静态的成员集合中进行投票,而动态的成员增减算法则非常复杂。而且共识算法对网络抖动比较敏感,有可能发生频繁的主切换过程,使整个集群处于无法工作的状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《设计数据密集型应用》(Designing Data-Intensive Applications)是一本由Martin Kleppmann所撰写的计算机科学书籍。这本书主要讨论了设计和开发数据驱动型应用程序时所面临的挑战和问题。 本书通过深入探讨数据系统的各个方面,帮助读者理解如何设计可靠、可扩展和高效的数据系统。首先,书中介绍了不同类型的数据存储和处理技术,如关系数据库、分布式数据库、队列系统等。其次,书中详细讲解了数据的复制、容错、一致性和并发控制等相关概念和技术。此外,本书还讨论了数据流处理、批处理和流处理系统以及数据分析和搜索引擎等数据系统的关键问题。 在《设计数据密集型应用》中,作者以实际的案例和实验为基础,深入探讨了如何在实际应用中应对数据系统的各种挑战。读者可以了解到不同数据系统的特点、适用场景以及常见问题,并学习到在设计、部署和维护数据系统时的最佳实践。此外,对于正在构建大型数据系统的软件工程师或架构师来说,本书提供了宝贵的指导,帮助他们更好地理解和解决面临的挑战。 总而言之,《设计数据密集型应用》是一本全面而深入的关于数据系统设计的书籍。无论是对于学术界还是工业界的从业人员,都可以从中获得有关数据系统设计和实践的重要知识。同时,本书的内容易懂且实用,对于初学者和有经验的开发人员来说都具有很高的参考价值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值