DDIA读书笔记【一致性与共识,一致性模型】

173 阅读8分钟

这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战

本章将讨论如何构建容错式分布式系统的相关协议与算法,在上一篇文章中已经阐述了可能会出现的问题

  • 网络问题
  • 时钟问题
  • 节点失效

容错式分布式系统可以将底层的容错系统给提取出来,做成通用的一层抽象,在其他分布式系统中就不需要进行重复构建

而实现容错的最重要的一个部分就是分布式节点之间需要一种协议来达成共识

一致性的保证

如数据库中由于多节点存在,查询和写入数据到达不同节点的时间不一样,可能会出现数据不一致的情况

无法完全保证所有节点在同一瞬间的一致性,因此大部分情况下提供的是最终一致性

在等待一段时间后所有节点都会返回一样的数据,不一致是暂时的,但是需要等待的时间是未知的
——最终一致性

出现问题的时机以及原因是难以发现的,出线的时候往往只是一个契机

越强的一致性意味着更多的消耗,更多的性能的降低或者是容错降低

  • 越多的准确性要求、一致性要求都像熵一样,需要外界施加一定的能量才可以减少

分布式一致性模型与数据库的事务性质有点像,都是要解决可能会出现的数据不一致的情况,但是两者出现数据不一致的原因不太一样

  • 数据库事务强调的是并发下同一数据临界区带来的数据冲突问题,不受多事务影响数据的正确性
  • 分布式系统更多的是强调多副本多节点下如何维护数据的一致性,防止上一章上所说的原因导致系统的数据出现问题,所有的客户端都可以实时对数据进行共同的修改,不受外界因素影响出现服务不一致的问题

我们会介绍以下几个方面

  • 线性化
  • 事件顺序问题,不同事务之间的因果与全局顺序问题
  • 分布式事务以及共识问题

都需要考虑几种不同的分布式系统模型

  • 主从复制
  • 多主节点
  • 无主节点

可线性化

能让系统看上去只有一份一样,让客户端都有一份一样的视图,也被称为强一致性、原子一致性,所有的操作都是原子的,定义比较模糊

注意区分可串行化与可线性化

如果数据是多维的,可线性化不仅仅要保证每个维度是可线性化的,同时需要保证不同维度之间的关系也是没有出错的

具体要求

  • 当一个客户端获得返回的一个新值后,其他所有的客户端也应该获得新的值

使用场景

在一些对一致性要求不高的场景实际上是不需要实现可线性化的,不会造成多大的实质性的影响

但是某一些场景就需要强一致性,否则就会出现很大的问题,如:

  • 分布式锁
  • 向外的唯一性约束:ID、主节点
  • 对信息关系有要求的场景:发送邮件的时间信息与邮件内容的关系应该是正确的(时间信息较小,处理会更快,不能出现时间信息与邮件内容对不上的情况)

如何实现

  • 只使用一个节点:无法实现容错

  • 复制机制\

    • 主从复制(部分可支持可线性化)\

      • 只有主节点进行数据写入,如果是写入后只能从完成同步的节点获取数据,能么是可线性化的,会损失一部分的可用性

\

    • 共识算法(可线性化)\

      • 经过专门的设计后是可以支持可线性化的

\

    • 多主复制(不可线性化)\

      • 每个节点写入的东西以及顺序都不一样,没有办法保证客户端能获取一样的数据,本身就需要额外的机制进行数据的同步以及解决冲突

\

    • 无主复制(可能不可线性化)\

      • 不讨论

那么代价是什么

CAP

也即一致性、可用性、分区容错性

  • 更加准确的称呼是,再出现网络问题的情况下是选择一致性还是可用性,分区容错性实际上在分布式系统中是一个前提

CAP有着较大的局限性,其考虑的一致性模型仅限于强一致性,而造成在可用性与一致性中进行选择的原因也只有网络问题

但是在我的理解中,一致性与可用性本身也是有各种程度的,一致性模型也也不仅限于强一致性模型。而一致性的程度就可以指各种一致性模型,可用性和一致性的程度可以相互协调

\

实际让我们放弃保证强一致性的是性能的损耗

顺序保证

其实我们想保证的一致性在大部分情况下是要维护数据之间的因果关系

而这种因果关系实际上也是数据库系统中事务隔离级别所要维护的东西,只不过两者出现这种问题的原因不太一样,一个是高并发多事务带来的问题,一个是多节点之间的网络时钟等问题

因果顺序并非全序

需要注意一个是可比较的,偏序与全序,可线性化、可串行化是全序的

在分布式系统中这两种关系也有比较明显的体现

  • 全序:所有的事件操作我们都是可以指出哪一个在前哪一个在后,进行时间上的比较
  • 偏序:同时发生的两个事件是有因果关系的因此可以进行比较的,否则是不可比较的

也就是说在可线性化中所有的操作都可以看作是没有多节点、没有并发的

而在并发、多节点的情况下需要进行偏序分支的合并

可线性化确实可以保证因果顺序,但是对性能会有较大的损耗,还存在着其他的一致性模型,因果一致性可以保证因果顺序的同时增加了系统的可用性(容错)

如何获取事件之间的因果关系

进行操作的时候需要知道当前操作是基于哪一个版本进行的,也就是操作的时候需要客户端将自己的数据版本进行回传,通过版本号的顺序可以知道时间的因果关系

序列发生器

主从复制

可以由系统提供一个简单的单调增序号生成器,或者是一个特定的算法

非因果序列发生器(多主节点、无主节点)

如果不存在主节点进行统一的序号分配,如多主节点系统或无主数据库,可以采用如下方式进行生成

  • 每个节点都会产生自己的序号,但是每个节点产生的序号必须有自己的规则,相互之间不能冲突
  • 时间戳或物理时钟进行标记,需要足够高的分辨率,或者采用LWW
  • 每个节点拥有自己的区间

但是以上方式都没有办法进行跨节点的因果关系维护,但是我们需要的是整个系统的因果关系

Lamport时间戳(因果序列发生器)

可以解决上述非因果序列发生器的跨节点问题

  • 每个节点有自己唯一的标识符,并且拥有记录自己请求数的计数器
  • 请求会获得标识符与计数,再进行访问的时候需要回传这两个参数

在进行跨界点访问的时候,节点计数会变成请求中技术或本节点计数中较大的那一个,并且加1,并且返回的也是这个最大值

需要注意的是,Lamport时间戳可以保证原有的因果顺序,但是不意味着可以由Lamport时间戳进行判断两个事件是否具有因果关系

真的OK了吗?

由于操作本身是有时间长度的,有可能在一个请求还没有返回的时候,另外一个客户端发起了一样的请求,这个时候计数器可能并没有发生更新,导致数据出错

在这种情况下可能就需要其他节点是否在进行同样的操作

请问如何解决?