《设计数据密集型应用》第五章(2) 数据副本:Single-leader

上一节介绍的是数据副本中的Single-leader模型,并讨论了在节点出现故障时,如何保证系统正常工作。这节我们将主要讨论副本延迟的问题。

对于一个读多写少的系统来说,增加follower的数量是可以提高读的吞吐量的。如果是一个同步复制的系统,单个节点的故障会影响整个系统的写操作,因此follower数量的增加会使得系统更加脆弱。如果是异步复制的系统,由于leader和follower的数据状态可能是不一致的,follower数量的增加会增大客户端读取的数据不一致的可能性,即使它们符合最终一致性的特性。

下面介绍三个这类问题的例子,以及相应的解决方案概要。

  • 读取刚写入的数据

有一种场景是,用户在写入数据后,立即想读取到刚刚写入的数据。由于数据是异步复制的,写入和读取的节点不一致可能导致用户读不到刚写入的数据,用户会误以为数据未写入。

用户写入数据后立即读取

在该场景下,我们需要实现写后读(read-after-write)一致性,也称为read-your-writes一致性。对于写入数据的用户,能够立即读取到刚才写入的数据,而对于其他用户则不保证能立即读取到。实现的方式有以下几种:

  1. 在读取用户可能修改的数据时,始终从leader读取,而其他数据从follower读取。这种方法需要已知哪些数据是可能被修改的。比如用户简介信息,用户只能修改自己的简介信息,所以用户从leader读取自己的简介信息,从follower读取其他人的简介信息。
  2. 如果用户可以修改大部分的内容,使用上面的方法将导致leader的压力增大。这种情况下,可以定义一个从leader中读取数据的标准,比如写入在1分钟之内,从leader中读取,超过1分钟从follower中读取。
  3. 客户端记录最后一次更新的时间戳,并认为该时间戳之前的数据所有副本都已写入完成。如果某个副本的数据不是最新的,则从其他副本读取数据,或等待副本追上最新进度。这个时间戳可以是logical timestamp,或者是实际的时钟时间。
  4. 如果数据副本是多数据中心的,情况会更加复杂。每个请求都必须发送到对应的数据中心的leader上。

更复杂的情况是,用户使用多个设备,在一个设备上写入数据,在另一个设备上读取,我们需要保证跨设备的read-after-write一致性。这种情况需要考虑:

  1. 如果采用需要时间戳的方式,不同设备的时间戳需要由中心化的方法进行统一;
  2. 如果数据副本是跨多数据中心的,需要保证用户的所有数据从同一个数据中心的leader读取数据。
  • 单调读取

在使用异步复制时,每个follower的延迟可能不一样,这样就出现用户在读取刚写入的数据时,如果读取的是不同的follower,可能出现第一次读取到,但第二次读取不到的情况。

用户读取最新的数据,然后读取到过时的数据

单调读取是保证上述问题不会出现的一致性保证,它比强一致性保证要弱,比最终一致性要强。它的一种实现方式是,保证用户始终读取相同的数据副本,比如根据用户ID,为用户分配一个固定的数据副本。但当该数据副本出错时,需要将该用户的请求路由到其他的副本。

  • 一致顺序读

一些数据之间可能是有因果顺序的,比如一段对话的问答,如果先读取到答,后读取到问,就违背了这两条数据之间的因果关系。


问的延迟比答大,导致答出现在问之前

因此这里提出了一致顺序读一致性,保证读取的顺序和写入的顺序是一致的。一致顺序的问题当数据分区后是很常见的,不同的分区是独立操作的,没有全局有序的写入。

实现一致顺序读一致性的一种方法是,如果数据之间存在因果关系,则写入相同的分区,或者在外部跟踪并维护因果关系,在后面的happens-before部分会继续介绍。

小结

在使用最终一致性的系统时,需要假设副本延迟可能会有几分钟或者几小时。如果系统能够接受这个延迟,那使用最终一致性系统就可以了;如果最终一致性会带给用户不好的体验,那么就需要提供更强的保证。

之前讨论的各种更强的一致性保证,如果由应用代码实现的话会比较复杂。因此数据库提出事务的概念,可以由数据库保证上述的一致性,应用只需使用即可。

单节点的事务是已经可以实现的,但在分布式的环境下,事务的实现难度大大增加,后面我们会继续讨论这部分的内容。

你可能感兴趣的:(《设计数据密集型应用》第五章(2) 数据副本:Single-leader)