一致性相关概念

Published 2021年10月05日 18:00 by james

TL;DR

  • 强一致性
  • [ ] 弱一致性
    • [x] 最终一致性
      • 单调写一致性
      • 单调读一致性
      • [ ] 因果一致性
        • [ ] 读你所写一致性
          • 会话一致性

一致性模型分类1

由于异常的存在,分布式存储系统设计时往往会将数据冗余存储多份,每一份称为一个副本(replica/copy)。这样,当某一个节点出现故障时,可以从其他副本上读到数据。可以这么认为,副本是分布式存储系统容错技术的唯一手段。由于多个副本的存在,如何保证副本之间的一致性是整个分布式系统的理论核心。

场景说明

我们以具体实例来说明各种一致性的具体含义。首先,定义以下场景及术语。

这个场景包含三个组成部分: - 存储系统: 存储系统可以理解为一个黑盒子,它为我们提供了可用性和持久性的保证。 - 客户端A: 客户端A主要实现从存储系统writeread操作。 - 客户端B客户端C: 客户端B客户端C是独立于客户端A,并且BC也相互独立的,它们同时也实现对存储系统的writeread操作。

相关术语说明如下: - ABC: 代表3个独立的进程,这些进程会对存储系统数据库里的数据进行读/写操作。 - x: 存储系统数据库中某条数据。 - v1v2v3: 数据x的不同取值。 - Write(Item,Value): 代表某进程的一次写操作,即将Item的值更新为Value。 - Read(Item)=Value: 代表某进程的一次读操作,即读出Item的值为Value。 - Notify(p1,p2,Item,Value): 代表进程p1通知进程p2 Item的值为Value

一致性分类

一致性关系图

一致性关系图

从客户端的角度来看,一致性包含如下三种情况:

  • 强一致性:

    假如A先写入了一个值到存储系统,存储系统保证后续ABC的读取操作都将返回最新值。当然,如果写入操作"超时",那么成功或者失败都是可能的,客户端A不应该做任何假设。

  • 弱一致性:

    假如A先写入了一个值到存储系统,存储系统不能保证后续ABC的读取操作是否能够读取到最新值。

  • 最终一致性:

    最终一致性是弱一致性的一种特例。假如A首先写入一个值到存储系统,存储系统保证如果后续没有写操作更新同样的值,ABC的读取操作"最终"都会读取到A写入的最新值。"最终"一致性有一个"不一致窗口"的概念,它特指从A写入值,到后续ABC读取到最新值的这段时间。"不一致性窗口"的大小依赖于以下的几个因素: 交互延迟,系统的负载,以及复制协议要求同步的副本数。

最终一致性包含如下常见的变体: - 读写(Read-your-writes)一致性:

如果客户端A写入了最新的值,那么A的后续操作都会读取到最新值。但是其他用户(比如B或者C)可能要过一会才能看到。 - 会话(Session)一致性: 要求客户端和存储系统交互的整个会话期间保证读写一致性。如果原有会话因为某种原因失效而创建了新的会话,原有会话和新会话之间的操作不保证读写一致性。 - 单调读(Monotonic read)一致性: 如果客户端A已经读取了对象的某个值,那么后续操作将不会读取到更早的值。 - 单调写(Monotonic write)一致性: 客户端A的写操作按顺序完成,这就意味着,对于同一个客户端的操作,存储系统的多个副本需要按照与客户端相同的顺序完成。

从存储系统的角度看,一致性主要包含如下几个方面:

  • 副本一致性: 存储系统的多个副本之间的数据是否一致,不一致的时间窗口等;
  • 更新顺序一致性: 存储系统的多个副本之间是否按照相同的顺序执行更新操作。

一般来说,存储系统可以支持强一致性,也可以为了性能考虑只支持最终一致性。从客户端的角度看,一般要求存储系统能够支持读写一致性,会话一致性,单调读,单调写等特性,否则,使用比较麻烦,适用的场景也比较有限。

图解分析

在上面场景下,我们分述各个一致性模型所体现的语义。

强一致性

强一致性

强一致性

最终一致性

最终一致性是一种弱一致性。它无法保证某个数值x做出更新后,所有后续针对x的操作能够立即看到新数值,而是需要一个时间片段,在这个时间片段之后可以保证这一点,而在这个时间片段之内,数据也许是不一致的,这个系统无法保证强一致性的时间片段被称为"不一致窗口"(Inconsistency Window)。

最终一致性

最终一致性

因果一致性

因果一致性

因果一致性

"读你所写"一致性

"读你所写"一致性

"读你所写"一致性

"读你所写"一致性是因果一致性的特例,可以在概念上理解为: 进程A把数据x更新为数值v2后,立即给自己发出了一条通知Notify(A,A,x,v2),所以进程A之后的操作都是以新数值v2作为基础。其他进程未受影响,在不一致窗口内仍旧可能会看到x的旧数值v1

会话一致性

会话一致性

会话一致性

"读你所写"一致性的一种现实版本变体即"会话一致性"。当进程A通过会话与数据库系统连接,在同一个会话内,可以保证其"读你所写"一致性。而在不一致窗口内,如果因为系统故障等原因导致会话终止,那么进程A仍旧可能读出x的旧值v1

单调读一致性

单调读一致性

单调读一致性

单调读一致性是最终一致性的另外一种变体。它保证如果某个进程读取到数据x的某个版本数据v2,那么系统所有后续的读取操作都不能看到比v2更老版本的数值,比如v1

单调写一致性

另外一种最终一致性的变体是"单调写一致性",对于某个进程来说,单调写一致性可以保证其多次写操作的序列化,如果没有这种保证,对于应用开发者来说是很难进行程序开发的。

在实际的存储系统中,可以综合使用以上的一致性模型,比如可以综合"单调读一致性"模型和"会话一致性"模型。对于实际存储系统来说,尽管同时满足以上两种一致性并非必需,但是如果系统能够具备这一点,对于使用存储系统的应用开发人员来说会大大简化其应用开发难度,与此同时,系统还可以在放松一致性要求情况下提供系统的高可用性。

附录

一致性读和半一致性读2

MySQL提供了一致性读和半一致性读,这些读数据的方式不会对被读的数据进行加锁,有效地提高了并发度。

一致性读

MySQL的官方文档,描述了一致性读如下:

A consistent read means that InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time.The query sees the changes made by transactions that committed beforethat point of time,and no changes made by later or uncommitted transactions.

这说明一致性读依赖于InnoDB实现的MVCC机制。一致性读需要一个时间点,以这个时间点的快照(MySQL中又称为read view,快照是一个活跃的读写事务列表)为读数据的依据,会忽略其他并发事务对于相同数据项的修改,只看到这个快照所限定的范围内的数据。

如果一个数据项被其他并发的事务改变,也不影响一致性读所能获取既定的数据(即一致性读的时间点那一刻确定的数据)。这是因为InnoDB使用了UNDO日志来获取被修改的数据项之前的符合一致性读的时间点的数据项。这样做得好处是,避免锁的使用从而提高并发事务的并发度。

对于一致性读,实际上包含了三层含义: - 在可重复读隔离级别,一致性读是对事务块内的所有查询语句有效。这是事务块的一致性。 - 在已提交读隔离级别,一致性读是对单个查询语句有效。这是语句级的一致性。 - 在未提交读隔离级别,读不加锁,读到的是索引上最新的记录。不存在一致性。

半一致性读

MySQL的官方文档,描述了半一致性读如下:

For UPDATE statements,if a row is already locked,InnoDB performs a“semi-consistent”read,returning the latest committed version to MySQL so that MySQL can determine whether the row matchesthe WHERE condition of the UPDATE.If the row matches(must be updated),MySQL reads the row again and this time InnoDB either locks it or waits for a lock on it.

这说明半一致性读只适用于更新操作,而且,半一致性读还受限于已提交读隔离级别,代码如下:

ha_innobase::try_semi_consistent_read(bool yes)  //决定是否可以进行半一致性读
{...    
    /* Row read type is set to semi consistent read if this was requested by the MySQL and either 
       innodb_locks_unsafe_for_binlog option is used or this session is using 
       READ COMMITTED isolation level. */    
    if (yes  //表扫描或索引扫描
        && (srv_locks_unsafe_for_binlog
        || m_prebuilt->trx->isolation_level <= TRX_ISO_READ_COMMITTED)) {
        //只有隔离级别小于等已提交读,才可进行半一致性读
        m_prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;
    } else {  //非表扫描或索引扫描,如全文检索时yes参数值为false
        m_prebuilt->row_read_type = ROW_READ_WITH_LOCKS;
    }
}

引入半一致性读,在更新操作时,可以减少更新引发的同一行记录的冲突,先允许读到元组,然后交给MySQL Server层判断是否满足WHERE条件,如果满足再让InnoDB去加锁,这样就避免了先加锁后释放的过程,提高了并发度。

相关术语

  • MONOTONICREADS: 单调读,如果一个会话进程成功读取了一个值v,则之后发生的其他读操作不会再读取到比v值还旧的值。
  • READYOURWRITES: 读你所写,同一个会话进程一定能够读到自己写过的值。
  • MONOTONICWRITES: 单调写,同一个会话中的所有写以同样的顺序写到多个副本中。
  • WRITESFOLLOWREADS: 又称session causality,即同一个会话中,在一个写操作发生后发生的读操作,所读到的值必是之前写操作影响的值。这意味着连续发生的先写后读在数据项上存在单调性。即会话上的偏序关系

以数据为中心的一致性模型

  • 严格一致性
  • 顺序一致性
  • 线性化一致性
  • 因果一致性
  • FIFO一致性
  • 弱一致性
  • 释放一致性
  • 入口一致性

以客户为中心的一致性模型

  • 最终一致性
  • 单调读
  • 单调写
  • 写后读
  • 读后写

0 comments

There are no comments yet.

Add a new comment