上一节介绍的是数据副本中的Single-leader模型,并讨论了在节点出现故障时,如何保证系统正常工作。这节我们将主要讨论副本延迟的问题。
对于一个读多写少的系统来说,增加follower的数量是可以提高读的吞吐量的。如果是一个同步复制的系统,单个节点的故障会影响整个系统的写操作,因此follower数量的增加会使得系统更加脆弱。如果是异步复制的系统,由于leader和follower的数据状态可能是不一致的,follower数量的增加会增大客户端读取的数据不一致的可能性,即使它们符合最终一致性的特性。
下面介绍三个这类问题的例子,以及相应的解决方案概要。
- 读取刚写入的数据
有一种场景是,用户在写入数据后,立即想读取到刚刚写入的数据。由于数据是异步复制的,写入和读取的节点不一致可能导致用户读不到刚写入的数据,用户会误以为数据未写入。
用户写入数据后立即读取在该场景下,我们需要实现写后读(read-after-write)一致性,也称为read-your-writes一致性。对于写入数据的用户,能够立即读取到刚才写入的数据,而对于其他用户则不保证能立即读取到。实现的方式有以下几种:
- 在读取用户可能修改的数据时,始终从leader读取,而其他数据从follower读取。这种方法需要已知哪些数据是可能被修改的。比如用户简介信息,用户只能修改自己的简介信息,所以用户从leader读取自己的简介信息,从follower读取其他人的简介信息。
- 如果用户可以修改大部分的内容,使用上面的方法将导致leader的压力增大。这种情况下,可以定义一个从leader中读取数据的标准,比如写入在1分钟之内,从leader中读取,超过1分钟从follower中读取。
- 客户端记录最后一次更新的时间戳,并认为该时间戳之前的数据所有副本都已写入完成。如果某个副本的数据不是最新的,则从其他副本读取数据,或等待副本追上最新进度。这个时间戳可以是logical timestamp,或者是实际的时钟时间。
- 如果数据副本是多数据中心的,情况会更加复杂。每个请求都必须发送到对应的数据中心的leader上。
更复杂的情况是,用户使用多个设备,在一个设备上写入数据,在另一个设备上读取,我们需要保证跨设备的read-after-write一致性。这种情况需要考虑:
- 如果采用需要时间戳的方式,不同设备的时间戳需要由中心化的方法进行统一;
- 如果数据副本是跨多数据中心的,需要保证用户的所有数据从同一个数据中心的leader读取数据。
- 单调读取
在使用异步复制时,每个follower的延迟可能不一样,这样就出现用户在读取刚写入的数据时,如果读取的是不同的follower,可能出现第一次读取到,但第二次读取不到的情况。
用户读取最新的数据,然后读取到过时的数据单调读取是保证上述问题不会出现的一致性保证,它比强一致性保证要弱,比最终一致性要强。它的一种实现方式是,保证用户始终读取相同的数据副本,比如根据用户ID,为用户分配一个固定的数据副本。但当该数据副本出错时,需要将该用户的请求路由到其他的副本。
- 一致顺序读
一些数据之间可能是有因果顺序的,比如一段对话的问答,如果先读取到答,后读取到问,就违背了这两条数据之间的因果关系。
问的延迟比答大,导致答出现在问之前
因此这里提出了一致顺序读一致性,保证读取的顺序和写入的顺序是一致的。一致顺序的问题当数据分区后是很常见的,不同的分区是独立操作的,没有全局有序的写入。
实现一致顺序读一致性的一种方法是,如果数据之间存在因果关系,则写入相同的分区,或者在外部跟踪并维护因果关系,在后面的happens-before部分会继续介绍。
小结
在使用最终一致性的系统时,需要假设副本延迟可能会有几分钟或者几小时。如果系统能够接受这个延迟,那使用最终一致性系统就可以了;如果最终一致性会带给用户不好的体验,那么就需要提供更强的保证。
之前讨论的各种更强的一致性保证,如果由应用代码实现的话会比较复杂。因此数据库提出事务的概念,可以由数据库保证上述的一致性,应用只需使用即可。
单节点的事务是已经可以实现的,但在分布式的环境下,事务的实现难度大大增加,后面我们会继续讨论这部分的内容。
网友评论