受昨天“无边界一致性”的启发,以及我们在“激进的并发控制”中所学到的一些东西的影响,我将试图从我们研究的许多论文中总结出一个更大的图景,在我所能看到的范围内。在这里,我要说的话比任何一篇论文的评论都多!

    数据危机
    我们遇到了“数据危机”。我们编写的大多数使用持久数据的应用程序都变得越来越差,而且情况似乎越来越糟。我们是怎么进入这种状态的?我们能做些什么?

    即使是使用传统RDBMS的应用程序也容易发生完整性冲突

    • 可信RDBMS中可序列化ACID事务的金标准要么不可用,要么在实际中不使用。(见高可用事务中的表2:各种常见数据库中默认和最大隔离级别的良好摘要的优点和限制)。例如,Oracle最强大的隔离级别是快照隔离,它仍然允许异常。数据库的默认隔离级别很少是可序列化的。不用说,任何弱于可串行化的隔离模型都不能保证可串行化,但性能优势被认为超过了异常的潜在成本。
    • 一个关系数据库要么不提供可序列化性,要么被配置为使用较低的隔离级别,将容易受到异常的影响,我们甚至可以构建模型来预测有多少异常。称它们为“异常”是一个很好的营销手段,当你说“会很容易受到违反应用程序完整性约束”时,听起来就不那么美好了。
    • 正如我们在激进的并发控制中看到的,程序员甚至没有利用关系数据库提供的保证。通过验证和关联注释以及很少使用事务来实现的应用级并发控制机制越来越普遍。在这种模型下,基本的完整性约束(如唯一性)会被破坏(即使您在后端有一个提供可序列化性的数据库)。

    我还没有遇到一个程序员,他仔细分析了由于选择的隔离级别和并发控制机制而可能出现的所有异常情况,并根据业务交易和完整性约束验证这些异常情况,以确保它们是可接受的,或者如果要发生的话,恢复策略是适当的。一般来说这很难做到,所以更常见的方法是假装不存在异常。

    当我们冒险进入最终一致的领域时,情况会变得更糟
    **
    当然,这只是关系数据库世界,我们的“安全地方”,我们喜欢认为一切都是一致的(不是这样的)。接着是云和帽定理。我们需要可用性、低延迟、分区容限和可伸缩性。因为我们无法避免分区,我们仓促进入了一个最终一致的世界。

    • 在最终的一致性下,我们将看到更多的异常(应用程序完整性违规):我们可以做多少建模
    • 那些试图解决这个问题的程序员发现很难,我怀疑很多人仍然遵循“假装他们没有发生”的策略,最后一个作家赢了,一切都会好起来的…
    • 由于分区容忍度是这种变化的关键驱动因素之一,许多实现根本不能很好地处理网络分区,这似乎是一个很大的遗憾——这将数据丢失添加到了我们的数据损坏问题中。(记住,分区会发生)。
    • 性能(低延迟)是另一个关键驱动程序——最好是开箱即用,所以为什么不默认为W=1异步写入?你可能不会丢失任何(很多)数据吧?(这似乎是与rdbms并行的NoSQL,默认情况下选择较小的隔离级别)。

    你的数据库是从所有的一致性保障的谎言中脱离出来。我知道你试过了,但你有道德感或自豪感吗?/你认为你建立了一个数据库,但写永远不会使用磁盘空间。
    这是D从ACID所拥有的。另一个坏消息是:许多最终一致的系统不支持多项目/分区事务,所以这是我们无法保证的另一组完整性约束。

    我们需要学会忍受两个限制
    我们不能有安全和性能吗?(以及可用性和网络分区规避?)。怎样才能摆脱这场混乱?如何在可以投入生产的堆栈上构建现代应用程序,而不会暴露在数据丢失和损坏的情况下?

    异常是不可避免的
    我们并没有通过假装失败而更好地处理失败。事实恰恰相反——我们意识到,系统中的组件越多,失败的可能性就越大,以至于我们需要明确地为它设计,以理解失败会发生。为了迫使我们动手,我们引进了混乱猴子和一支猿猴军队。

    在处理应用程序完整性违规时,我们不会假装它们没有发生。事实恰恰相反,我们拥有的并发操作越多(隔离级别越低),应用程序完整性违规的可能性就越大,到目前为止,我们需要为它们显式设计,因为我们知道应用程序完整性违规会发生。为了强迫我们的手,也许需要Chaos Monkey和Simian Army!活性失败很容易被发现(一个进程死亡,变得无响应,坏事发生)。安全故障潜伏在阴影中,我们需要努力使它们同样可见。

    针对应用程序完整性冲突进行设计意味着什么?首先,让我们假设我们已经做了所有实际的事情来避免尽可能多的异常,其次,让我们假设我们知道哪些异常仍然让我们容易受到攻击(稍后我将回到这些主题)。帕特·海兰给了我们一个模型,让我们根据记录、猜测和回退来思考这种情况。考虑到你对世界现状(记忆)的了解,你会尽你所能(猜测),如果结果发现你做错了什么,你会进行补救。

    注意,如果你不知道自己做错了什么,就不能补救!因此,我们需要一个程序,能够在诚信违规事件发生时发现,并启动补救机制。如果我们对此很聪明,我们可以自动检测违规行为并调用用户定义的补救回调,但补救的含义始终是特定于应用程序的,需要由开发人员进行编码。

    我们需要权衡性能和补救率,而不是权衡性能和数据损坏。

    我们无法建立一个更好的数据存储
    将I/O级别的一致性作为读写序列的推理级别太低,与应用程序程序员的关注点相去甚远。应用程序编程人员发现映射太模糊,无法实际使用,例如,找出可能发生的异常以及它们在应用程序术语中的含义。正如我们所看到的,只要我们坚持这个模型,程序员很可能会继续回避数据库提供的内容,并实现狂热的并发控制。同时,由于过于保守的协调,丢失关于应用程序真正要实现的目标的语义信息会导致效率低下。在不构建应用程序与数据存储交互的更好方式的情况下构建更好的数据存储并不能解决数据危机。

    一切从应用开始
    如果您回顾一下我们一直在看的一些论文,就会发现一个非常以应用程序为中心的有趣模式:

    • 我们通过最小化所需的协调量来获得分布式系统的最大性能。实现这一点的方法是通过应用程序级对应用程序不变量的理解。这是I-Confluence的教训。

    我们不能得到比因果+一致性更强的一致性。开箱即用这可以提供ALPS的ALP(可用性、低延迟、分区容忍度),但在默认的潜在因果关系模型下,它难以实现可伸缩性。为了恢复可伸缩性,我们需要转向一个明确的因果关系模型。换句话说,我们需要应用程序显式地告诉我们写操作的因果依赖关系。

    为了弥补确实发生的异常情况,我们需要一个回退机制。事务回滚只能在应用程序级别定义。
    程序员们用脚投票,告诉我们他们希望与域模型一起控制应用程序的完整性
    通过采用分布式数据类型(如CRDTs和云类型)减少对协调的需求需要应用程序程序员的合作。
    在第二部分中,我们将进一步探讨从应用程序向下解决数据危机的意义。