【问题标题】:Why is Cassandra not linearizable when quorum based read and writes are used为什么在使用基于仲裁的读写时 Cassandra 不能线性化
【发布时间】:2019-11-09 17:22:07
【问题描述】:

您能否解释一下为什么即使使用基于仲裁的读写,Cassandra 也不能线性化?

线性化定义为

如果操作 B 在操作 A 成功完成后开始,则操作 B 必须看到系统处于与操作 A 完成时相同的状态,或者更新的状态。

【问题讨论】:

    标签: database cassandra


    【解决方案1】:

    考虑 Cassandra 前台读取修复进行编辑:

    由于仅更新部分副本集而失败的写入可能会导致两个不同的读取器看到两个不同的数据值。这是因为在简单的基于仲裁的一致性方法中缺乏回滚。这种行为打破了单键读取的线性化保证。正如discussion 中所述,Raft 或 Paxos 等分布式共识协议是此类保证的必备条件。

    此外,clock driftleap second 等其他现象可能会破坏 Cassandra 会话的一致性。

    早期答案(不考虑 Cassandra 前台读取修复):

    总结: 在 Cassandra 中编写可能感觉不到原子性。一些节点的写入速度比其他节点快,因此即使我们依赖 quorum,结果也取决于返回值的节点集以及它们当时持有的值。

    另外,解释线性化的定义添加到粗体定义

    如果操作 B 在操作 A 成功完成后开始,则 操作 B 必须看到系统处于与启动时相同的状态 操作 A 完成,或更新的状态(但不再是旧状态)

    抄自 Martin Klepmann 的数据密集型应用程序书

    线性化和仲裁 直观地说,似乎严格的仲裁读取和写入在 Dynamo 风格的模型中应该是可线性化的。然而,当我们有可变的网络延迟时,就有可能出现竞争条件,如图 9-6 所示。

    在图 9-6 中,x 的初始值为 0,写入器客户端通过将写入发送到所有三个副本(n = 3,w = 3)将 x 更新为 1。同时,客户端 A 从两个节点 (r = 2) 的仲裁中读取,并在其中一个节点上看到新值 1。同样在写入的同时,客户端 B 从两个节点的不同仲裁中读取,并从两个节点取回旧值 0。

    满足仲裁条件 (w + r > n),但此执行仍然不是线性化的:B 的请求在 A 的请求完成后开始,但 B 返回旧值,而 A 返回新值。 (这又是图 9-1 中的 Alice 和 Bob 的情况。)

    有趣的是,可以以降低性能为代价使 Dynamo 风格的 quorums 线性化:读取器必须在将结果返回到应用程序之前同步执行读取修复(参见第 178 页的“读取修复和反熵”)[23 ],并且写入者必须在发送其写入之前读取节点法定人数的最新状态 [24, 25]。然而,由于性能损失 [26],Riak 不执行同步读取修复。 Cassandra 确实在仲裁读取 [27] 上等待读取修复完成,但如果有多个并发写入同一个键,它会失去线性化,因为它使用了最后写入胜出冲突解决方案。

    而且,这种方式只能实现可线性化的读写操作;线性化的比较和设置操作不能,因为它需要一个共识算法 [28]。

    总之,假设具有 Dynamo 式复制的无领导系统不提供线性化是最安全的。

    还有一点关于线性化与序列化的解释:

    【讨论】:

    • 很好的发现,卖给我买了这本书,谢谢!带有多个客户端(1 位作者 2 位读者)演示的图表很不错。
    • 您能将此标记为正确答案吗?我觉得这是一个更简洁的解释,我的问题并没有完全正确地回答。
    • 如果我错了,请纠正我,但图中的场景并不能证明 Cassandra 不是线性化的,因为 Cassandra 确实在 quorum 读取中进行了读取修复,并且会修复 Replica 2 中的不一致当阅读器 A 读取 x 时。
    • @erictan 感谢您指出读取修复案例。我进行了编辑以包含该场景。
    • 在“写入失败后读取不一致”示例中,第一个读取器(看到更新节点的那个)不会执行前台读取修复吗?如果执行修复,第二个读取器将看到更新后的值(假设第二个读取在第一个读取完成后开始)
    【解决方案2】:

    编辑:事后看来,这不是最好的解释。我建议阅读下面更简洁的 Anurag 的答案。

    由于正常的 Cassandra 操作不会观察到它正在发生变化的现有状态,因此不能仅将仲裁一致性视为“线性化”。

    例如,如果您要调整银行账户的余额,您需要知道当前余额才能进行调整。考虑一个执行以下操作的客户端:

    A. SELECT balance FROM account WHERE id='x' (assume this returns 5.12)
    B. UPDATE account SET balance=4.12 WHERE id='x' (subtract 1$ from balance)
    

    问题是,从 Cassandra 的角度来看,操作 B 并没有有效地“看到”A,因为它没有考虑数据的现有状态或可能发生的任何其他操作。在提交B 期间,另一个客户可能正在更新同一帐户的余额。

    Lightweight Transactions in Cassandra 2.0 描述了轻量级事务如何通过提供结构来提供“可线性化一致性”,以确保对给定分区按顺序执行操作并且不会被其他操作中断。因此,您现在可以执行以下操作,而不是我之前的示例:

    A. SELECT balance FROM account WHERE id='x' (assume this returns 5.12)
    B. UPDATE account SET balance=4.12 WHERE id='x' IF balance=5.12
    

    IF balance=5.12 的使用指示 Cassandra 开始轻量级事务,该事务使用 paxos consesus protocol 进行领导选举并确保按顺序应用操作。如果余额状态不满足条件,则不会应用更新(通过was_applied 布尔列指示成功响应)。如果 C* 在某个超时时间内(由于争用或其他一些因素)无法实现这一点,则操作将失败,将不会被应用,并且客户端将出现超时。

    【讨论】:

    • 您能否通过一些示例解释一下在 C* 的情况下,Serialzability 和 Linearizability 之间的区别是什么
    • Peter Bailis 关于线性化和可序列化的文章是这两者的一个很好的参考:bailis.org/blog/linearizability-versus-serializability。在 cassandra 的上下文中,无法实现可串行化,因为它没有涉及多个对象的事务的概念。 LWT 在单个对象(行)上提供可线性化的一致性。有一个称为“SERIAL”的一致性级别适用于读取和 LWT,这有点令人困惑,因为它可能导致一些人将其与可序列化性混为一谈。
    猜你喜欢
    • 2017-07-27
    • 2020-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 1970-01-01
    • 2016-08-16
    • 2013-10-28
    相关资源
    最近更新 更多