下载本文的PDF版本 PDF

关注你的状态,为了你的心态

存储和应用程序之间的交互可能既复杂又微妙。

Pat Helland

应用程序在向分布式和可扩展的世界发展过程中,经历了有趣的演变。同样,存储及其近亲数据库也与应用程序并肩发展。很多时候,存储和应用程序的语义、性能和故障模型都在微妙地变化,以支持不断变化的业务需求和环境挑战。规模的扩大真的让情况变得复杂起来。本文着眼于其中一些问题及其对系统的影响。

在数据库事务之前,更新数据存在复杂性,尤其是在发生故障时。即使系统是集中式的,避免了分布式带来的复杂性,情况也是如此。数据库事务极大地简化了应用程序开发人员的生活。这种美好时光曾持续了一段时间...

随着解决方案扩展到单个数据库之外,生活变得更具挑战性。首先,我们尝试使多个数据库看起来像一个数据库。然后,我们使用 SOA(面向服务的架构)将多个应用程序连接在一起。在 SOA 中,每个服务都有自己的离散数据库和自己的事务,但使用消息传递跨边界进行协调。很快,我们开始使用微服务,每个微服务可能没有自己的数据,而是直接访问跨多个独立服务共享的分布式存储。如果实现正确,这种方式可以更好地扩展。

不同类型的分布式存储提供不同的平均速度、响应变化、容量、可用性和持久性。不同的应用程序模式出于不同的目的使用存储的数据。它们主要基于对存储的使用,向用户提供各种保证。来自应用程序的不同保证有时会显示用户在语义、响应时间、持久性等方面的差异。虽然这些可能令人惊讶,但可能是可以接受的。重要的是满足业务需求和明确的期望。

本文提供了分布式集群上可用的各种存储解决方案的部分分类。其中一部分是对存储不同功能之间交互的探索。然后,本文考虑了不同的应用程序模式如何随着时间的推移而发展,以利用这些存储及其满足的业务需求。这可能具有令人惊讶的含义。

状态、存储和计算的演变...至少到目前为止

本节首先考察存储和计算方面发生的一些深刻变化。然后重点讨论持久状态和会话状态,以及它们如何随时间演变。最后,简要回顾了经典数据库内部和外部数据在跨越信任和事务边界时如何被不同对待。

存储和计算的趋势

存储和计算的变化对如何访问存储以及这样做的预期行为提出了要求。当工作分散在称为微服务的小型计算池中时,这一点尤其有趣。

存储已经发展。 过去,存储只能直接连接到你的计算机。然后出现了共享设备,例如 SAN(存储区域网络)。这些是大型、昂贵的设备,配备了大量复杂的软件和硬件,可以为连接到它们的一组服务器提供高可用性存储。这导致了网络中包含的商品服务器的存储集群。

计算已经发展。 几十年前,它只是单台服务器上的单个进程。多年过去了,人们才开始担心单台服务器上多个进程之间的通信。然后,世界带着极大的兴奋转向了跨小型服务器集群的 RPC(远程过程调用)。当时,我们没有考虑信任,因为每个人都在同一个信任区域。我们都是一家人!

在 2000 年代,服务或 SOA 的概念开始出现,有时使用不同的名称。6 服务的基本方面是信任隔离。这自然导致应用程序和应用程序代码封装数据,以便不信任的外部人员不能随意修改数据。

随着行业开始大规模运行东西,它了解到将服务分解为更小的微服务有几个很大的优势

• 更好的工程。将你的服务(即信任边界)分解成更小的部分,可以实现更好的工程灵活性,因为小型团队可以更快地进行更改。

• 更好的可操作性。使这些更小的部分无状态且可重启,可以实现更具弹性的操作,因为可以动态处理故障、版本的滚动升级以及针对不同需求的调整。

微服务成为软件工程和运营领域的重要组成部分。

计算对存储的使用已经发展。 随着应用程序对存储的使用不断发展,应用程序的变化经历了一段狂野的历程

• 直接文件 I/O 使用仔细替换来实现可恢复性。仔细替换 是一种至少与 20 世纪 60 年代一样古老的技术。它涉及对持久存储的更改进行周到的排序,以便可以容忍故障。

仔细替换的变体

• 写入可能会破坏先前的值... 首先写入其他位置。

• 客户端崩溃可能会中断写入序列... 仔细计划。

• 事务性更改为应用程序开发人员提供了支持,这是一个巨大的改进。这意味着应用程序开发人员在处理存储时不需要那么小心。它还允许对记录的更改进行分组,以便原子地更新一批记录。这容易得多。 SAN 为硬件存储实现了所需的仔细替换,从而允许更大更好的数据库。数据库不断发展,以支持使用事务性更新的两层和 N 层应用程序。

• 键值存储提供了更大的规模,但用于处理应用程序数据的声明性功能较少。随着规模的扩大,多记录事务丢失了。

计算风格、存储以及这些应用程序模式如何用于访问存储都发生了并将继续发生重大变化。

这只是存储和计算模型的部分列表。它并非旨在完整。

现代基于微服务的应用程序中的挑战

如今,微服务为许多可扩展的应用程序提供动力。微服务是在一组服务器上运行的相同或等效服务的池。传入的请求在池中进行负载均衡。

当请求等待微服务时,来自同一池中的任何一个都可以完成这项工作。有时,系统会实现亲和性,后续请求很可能会转到同一个特定的微服务。尽管如此,如果你落在任何微服务上,结果都必须是正确的。

微服务在两个广泛的方面帮助可扩展系统

• 改进的软件工程。构建由小型且独立的微服务组成的系统可以提高敏捷性。拥有微服务的团队必须负责任,并具有独立性和所有权。当需要更改某些内容时,就更改它。当某些东西损坏时,拥有团队负责。

• 改进的操作。健康介导的部署允许将新版本缓慢地部署到运行的系统中。通过监控系统的健康状况,可以回滚新版本。这些微服务的滚动升级可能对故障区域敏感,因此在不稳定的升级期间发生的独立故障不会造成太大损害。拥有大量独立且等效的微服务意味着一个或多个微服务的故障会自动修复。

持久状态通常不保存在微服务中。相反,它保存在后端数据库、键值存储、缓存或其他东西中。本文的其余部分将介绍其中一些。

微服务不容易跨池中的所有微服务更新状态。当它们来来往往时尤其如此。通常的做法是将最新状态放在微服务无法访问的地方,并提供可在可扩展缓存中访问的较旧版本状态。有时,这会导致可扩展缓存向持久状态发出直读请求,而持久状态无法直接寻址到调用微服务。

这现在正成为一种经过验证的模式。图 1 取自 DeCandia 等人 2007 年关于亚马逊 Dynamo 的论文。2 虽然命名法略有不同,但它显示了访问不同存储后端层的三层微服务。

Mind Your State for Your State of Mind

持久状态和会话状态

持久状态 是跨请求记住并在故障中持久存在的东西。这可以捕获为数据库数据、文件系统文件、键值等等。持久状态以多种不同的方式更新,主要取决于保存它的存储类型。它可以通过对键值或文件的单次更新来更改,也可以通过数据库或其他存储实现的事务或分布式事务来更改。

会话状态 是在会话中的请求之间记住但在故障中不记住的东西。会话状态存在于与会话关联的端点内。多操作事务使用一种会话状态。7

当会话分散在服务实例中时,会话状态很难实现。如果池中不同的微服务处理事务中的后续消息,则会话状态很难实现。当发送到池的下一条消息可能落在不同的服务实例时,很难在实例中保留会话状态。

外部数据与内部数据

2005 年的论文“外部数据与内部数据”5 讨论了保存在锁定事务性存储(例如,关系数据库)中的数据与其他表示形式的数据之间的根本区别。

内部数据是指锁定事务性更新的数据。它存在于一个地方(例如,数据库)和一个时间,即事务时间点。

外部数据是未锁定的且不可变的,尽管它可以用一系列版本进行版本控制,这些版本本身是不可变的。外部数据始终具有某种形式的唯一标识符,例如 URI(统一资源标识符)或键。标识符可以隐式地存在于会话或环境中。外部数据通常表现为消息、文件或键值对。

持久状态语义的演变

存储系统和数据库在过去几十年中不断发展,更新其状态的语义也是如此。本节从我刚开始构建系统的糟糕的旧时代开始。在 70 年代和 80 年代,必须小心更新磁盘存储,以避免破坏磁盘块。从那时起,我们向前发展到原子记录更新以及事务之前出现的挑战。当事务出现时,很多事情都变得容易得多——如果 你在一个地方和一个时间进行更改。添加跨数据库和跨时间的行为导致了与更原始的存储系统相同的挑战。使用消息传递子系统将东西粘合在一起对此有所帮助。

然后,存储方面发生了一个有趣的发展。一些存储速度很快,但有时会返回陈旧的值。另一些存储始终返回最新值,但有时会在其中一台服务器速度较慢时停顿。本节展示了可预测的答案如何导致不可预测的延迟。10 最后,它考察了不可变数据在支持具有可预测答案和某些业务功能响应时间的大型系统中所能发挥的作用。

磁盘块的仔细替换

在 70 年代和 80 年代,磁盘写入可能会导致数据无法读取。写入经历了从旧 V1 版本到无法读取的垃圾,再到新 V2 版本的许多状态变化。当磁盘头写入一个块时,块中位的磁性表示会在更新到新版本的过程中变成糊状。电源故障会导致你丢失旧值(见图 2)。

Mind Your State for Your State of Mind

在实现可靠的应用程序时,至关重要的是不要丢失数据的旧值。例如,如果你要为数据库实现事务系统,那么丢失最近提交的事务是非常糟糕的,因为事务日志的部分已满的最后一个块正在被重写。避免这种情况的一个技巧是轮流写入不同磁盘上的镜像日志。只有在确定镜像 A 具有新块后,你才将其写入镜像 B。崩溃后,你将日志的最后一个块重写到两个镜像上,以确保答案一致。

另一种众所周知的技术,特别是对于日志的尾部,称为乒乓4 在这种方法中,日志的最后一个(和不完整)块保留在其在日志末尾的位置。该块的下一个版本(包含先前的内容和更多内容)被写入后面的块。只有在扩展的内容在后面的块上持久后,新版本才会覆盖较早的版本。通过这种方式,在任何窗口中都不会发生电源故障导致日志内容丢失的情况(见图 3)。

Mind Your State for Your State of Mind

记录写入的仔细替换

在数据库时代之前,对记录的更新没有事务。假设每个记录写入都是原子的,你仍然无法更新两个记录并获得它们都将被更新的任何保证。通常,你会写入记录 X,等待知道它是永久性的,然后再写入记录 Y。

那么,如果发生崩溃,你能解开这个烂摊子吗?

通常,存在一种依赖于应用程序的模式,该模式提供了对你需要写入的顺序的洞察力。在崩溃和重启后

• 如果记录 A 已更新但记录 B 未写入,则应用程序可以清理烂摊子。

• 如果记录 B 已更新但记录 A 未写入,则应用程序无法应对且无法恢复。

记录仔细替换的一个示例是消息队列。如果应用程序写入并确认消息队列中存在消息(称之为记录 A),并且处理该消息的工作是幂等的,那么应用程序可以基于记录的仔细替换来应对崩溃。幂等意味着如果重新启动它是正确的。4,7

事务和仔细替换

事务捆绑并解决仔细记录替换。可以在单个事务中更新多个应用程序记录,它们要么全部成功,要么全部失败。数据库系统确保记录更新是原子的。

• 数据库自动处理仔细存储替换 的任何挑战。用户不知道系统崩溃或电源故障时可能发生的奇怪故障行为。如果存在,数据库还支持少量私有数据库服务器上的分布式事务。

• 跨时间的工作(即,工作流)需要仔细的事务替换。虽然事务中的记录集在数据库的帮助下被原子地更新,但长时间运行的工作流3,4 对于在一段时间内完成正确的工作至关重要。故障、重启和新工作可以按事务推进应用程序事务的状态。跨时间的工作利用消息处理。

• 跨空间的工作(即,跨边界)也需要仔细的事务替换。不同的系统、应用程序、部门和/或公司具有单独的信任边界,并且通常不会跨边界进行事务。跨空间的工作需要跨时间的工作,逐个事务。这导致了消息传递语义。

消息传递语义

在事务性消息传递中,事务对其数据进行一系列更改,然后表达发送消息的意愿。此意愿与事务原子地记录在一起。事务可以原子地消费传入的消息。这意味着事务的工作(包括对应用程序数据的更改)只有在传入消息被消费时才会发生。

可以支持精确一次交付的语义。发送意愿与发送事务原子地提交。已提交的发送消息的意愿会导致一次或多次传输。系统会重试,直到目标确认它已在其队列中收到消息。消息必须在接收方最多处理一次。这意味着它必须是幂等处理的(见图 4)。

Mind Your State for Your State of Mind

在目标端进行最多一次处理存在挑战。为了实现这一点,你需要记住你已处理的消息,这样你就不会处理两次。但是你如何记住消息呢?你必须检测重复项。你需要记住多久?目标会分裂吗?目标会移动吗?如果你搞砸了,应用程序会多次处理消息吗?如果要将消息传递到基于微服务的应用程序,该怎么办?已处理消息集的知识保存在哪里?

读取你所写的内容?是?否?

过去,如果你写了东西,你就可以读取它。现在,情况并非总是如此简单。考虑以下情况

线性化存储 提供读取你所写内容的行为。在线性化存储中,每次更新都会创建一个新版本的value,并且存储永远不会返回旧值或不同的值。它始终返回线性value系列中的最新值。

线性化存储有时会延迟很长时间。

为了确保它们始终给出正确的值,它们将始终更新每个副本。

如果服务器速度慢或已死,并且包含其中一个副本,则可能需要数十秒才能决定该怎么做... 与此同时,用户等待。

非线性化存储 不提供读取你所写内容的行为。非线性化存储意味着不保证写入会更新所有副本。有时,读取可能会找到旧值。读取和写入非线性化存储具有非常一致的响应时间,概率更高。读取或写入可以跳过不正常或已死的服务器。有时,这会导致从跳过的服务器返回较旧的值。但是,嘿,它速度很快——而且是可预测的。

假设一个键/值存储,其中键 K 的值为 V1,并且存储将其保存在服务器 S1、S2 和 S3 上。你决定将值更新为 V2。存储尝试更改其三台服务器上的值,但 S2 没有响应,因为它已关闭。因此,存储决定将 V2 写入 S1、S3 和 S4,以便始终将新值写入三台服务器。稍后,当 S2 启动时,读取可能会找到旧值 V1。这有以下权衡

• 三个存储的写入始终快速发生。

• 存储不是线性化的,有时会返回旧值。

这种非常有用的技术是许多可扩展存储系统(例如 Dynamo2 和 Cassandra11)的基础。

缓存数据以出色的响应时间提供可扩展的读取吞吐量。键值对驻留在许多服务器中,并通过传播新版本进行更新。每次读取都会命中其中一台服务器并返回其中一个版本(见图 5)。

Mind Your State for Your State of Mind

不同用途的不同存储

可以接受读取时停顿吗?

可以接受写入时停顿吗?

可以接受返回陈旧版本吗?

你不可能拥有一切!

不变性:一块坚实的立足点

当你存储不可变数据时,每次查找总是返回相同的结果。8 不可变存储永远不会表现出更新异常,因为你永远不会更新它们。你所能做的就是为一个标识符存储一个全新的值,然后在以后删除它。许多应用程序模式都基于不可变项。

想象一个你只是记录你所看到的东西的系统。你所知道的一切都基于观察。过去永远不会改变——有点像会计师的分类账,其中没有任何内容被更新。你可以为每个工件放置一个唯一的 ID,并在以后查看它,但永远不要更改它。这是一种极其常见的模式。

当在键/值存储中保留不可变对象或值时,你永远不会得到陈旧的答案。唯一键只有一个不可变值。这意味着非线性化存储提供唯一正确的答案。所有存储类型都给出正确的答案,只是读取和写入延迟的特性不同(见图 6)。存储不可变数据意味着你永远不会得到陈旧的版本,因为根本不存在陈旧的版本。

Mind Your State for Your State of Mind

渐行渐远...

本节着眼于许多正在消失的保证。每个人都希望他们拥有像冯·诺依曼机12 这样的计算模型,该模型提供计算、存储和可预测的线性行为。然而,一旦分布式开始发挥作用,那真的只是一种愿望。

约翰·冯·诺依曼构想的单进程计算已经发展为使用会话和会话状态的多进程和多服务器计算。这些有状态会话支持可组合事务,这些事务跨越多个记录和多个协同工作的服务器。然而,随着工作开始分解为微服务,以他们过去使用事务的方式使用事务变得困难。

为了应对可扩展的环境,数据必须被分解为键值。可扩展存储非常适合一次更新单个键,但不适合跨键的原子事务。这些可扩展键值存储中的大多数都确保对其单个键进行线性化、强一致性的更新。不幸的是,这些线性化存储偶尔会导致用户看到的延迟。这导致了非线性化存储的构建,其巨大优势在于它们具有出色的读取和写入响应时间。作为交换,它们有时会给读取器一个旧值。

最后,本节指出,某些数据用途认为正确的答案足够重要,以至于可以使用仔细替换存储的值。这些用途不是非线性化存储的最佳选择。

老实说,它已今非昔比。

同一进程演变为不同进程

应用程序和数据库过去在同一进程中运行。对数据库代码的库调用允许访问数据。有时,多个应用程序一起加载。

后来,数据库和应用程序被拆分为由会话连接的不同进程。会话描述了会话状态,并包含有关用户、正在进行的事务、正在运行的应用程序以及光标状态和返回值的信息。

再后来,应用程序和数据库移动到不同的服务器。会话状态使这成为可能。

有状态会话和事务

有状态会话是共享进程的自然结果。你知道你在和谁说话,你可以记住关于对方的事情。

有状态会话非常适合经典的 SOA。在与服务对话时,你期望在每一侧都有一个长期会话和状态。有状态会话意味着应用程序可以在事务内进行多次交互。在许多情况下,即使跨多个使用分布式事务的后端数据库,也可能在 N 层环境中发生丰富而复杂的事务。

事务、会话和微服务

在会话状态方面,微服务还有很多不足之处。请求通过路由器进行负载均衡,并选择多个微服务实例之一。通常,后续流量会发送到同一实例,但并非总是如此。你不能指望回到你之前的位置。

在没有会话状态的情况下,你无法轻松创建跨请求的事务。通常,微服务环境支持单个请求内的事务,但不支持跨多个请求的事务。

此外,如果微服务在处理单个请求时访问可扩展的键值存储,则可扩展的键值存储通常只支持对单个键的原子更新。虽然它不会像旧的文件系统那样在更新键的中间失败而破坏数据,但在更改与多个键关联的值时,程序员只能靠自己。

键、版本和非线性历史

每个键都由某个数字、字符串、键或 URI 表示。该键可以引用一些不可变的东西。例如,“2018 年 6 月 1 日,《纽约时报》旧金山湾区版”在空间和时间上是不可变的。键也可以引用随时间变化的东西——例如,“今天的《纽约时报》”。

当键引用变化的东西时,可以理解为引用一系列版本,每个版本都是不可变的。通过首先将键的变化值绑定到键的唯一版本(例如,[键, 版本-1]),你可以将该版本视为不可变数据。每个版本都成为要保留的不可变事物。使用扩展的 [键, 版本],你可以在存储中引用不可变数据。

版本历史可能是线性的,这意味着一个版本取代了前一个版本。这是通过使用线性化存储 实现的。版本历史可能是 DAG(有向无环图)。当写入非线性化存储 时,就会发生这种情况。

想象一下你有一个便签本,你可以在上面乱写东西。但你实际上有多个便签本。你会在当时离你最近的便签本上乱写东西。当你想读取信息时,你会查看最近的便签本,即使它不是你最近写入的便签本。有时,你会得到两个相邻的便签本,同时查看两者,并在两者中写入一些内容以整合涂鸦。这就是来自非线性化存储的行为类型。更新不会以线性顺序前进。

仔细替换和读取你所写的内容

在仔细替换中,你需要小心更新内容的顺序。这对于处理一些故障至关重要,正如前面讨论的那样。在与其他公司合作时,需要跨信任边界的可预测行为。在进行长时间运行的工作流时,它也至关重要。

仔细替换基于读取你所写内容的行为,而这又取决于线性化存储。线性化存储几乎总是具有偶尔在等待不良服务器时停顿的属性。

一些示例应用程序模式

让我们看看一些应用程序模式以及它们如何影响持久状态的管理(图 7)。

Mind Your State for Your State of Mind

使用仔细替换的键值存储上的工作流

此模式演示了当持久状态太大而无法放入单个数据库时,应用程序如何执行工作流。

对象由其键唯一标识。工作通过人机交互或消息传递从外部到达。工作流可以捕获在值中。新值替换旧值。消息作为数据包含在对象中。9

可扩展的工作流应用程序可以构建在键值存储之上。你必须具有单项线性化(读取你所写的内容)。(参见图 8。)对于线性版本历史,一个新版本始终取代较早的版本。非线性历史具有 DAG 版本历史。在这种情况下,存储的线性化行为也意味着,其中一个存储服务器中的停顿将导致写入存储的停顿。这就是“必须正确”,即使它不是“现在正确”的情况。

Mind Your State for Your State of Mind

如果你无法读取上次写入的值,则通过仔细替换实现的工作流将是一团糟。因此,这种使用模式将停顿 而不是 陈旧

事务性 Blobs-by-Ref

这是一种非常常见的应用程序模式。应用程序使用事务和关系数据库运行。它还存储大型 blob,例如文档、照片、PDF、视频、音乐等等。blob 可能很大且数量众多。因此,直接在关系数据库中实现这些是一个挑战。

这些 blob 中的每一个都是一组不可变的位。要修改 blob(例如,编辑照片),你始终会创建一个新的 blob 来替换旧的 blob。不可变 blob 通常以 UUID(通用唯一标识符)作为其在可扩展键值存储中的键。

在非线性化数据库中存储不可变 blob 不会遇到返回陈旧版本的问题。由于只有一个不可变版本,因此没有陈旧版本。

在非线性化存储中存储不可变数据可以兼得两全:它既正确现在正确

电子商务 — 购物车

在电子商务中,每个购物车都适用于单独的客户。无需或不需要跨购物车的一致性。每个购物车都有唯一的标识或键。

如果客户对其购物车的访问停顿,他们会非常不满意。大型电子商务网站可以衡量购物车和客户会话在速度变慢时被放弃的百分比。缓慢的购物车对应于业务的大幅下降。产品目录、评论等必须快速响应,否则客户会离开。

购物车应该立刻返回结果,即使结果不完全正确。如果能够快速返回结果,即使返回的是过时或不完全正确的答案,对业务和客户体验来说,也明显更好。用户在确认购买前会被要求验证购物车的内容。

在非线性化的存储系统中,有时在版本历史 DAG 中会存在同一购物车的多个旧版本。相对简单的购物车语义有助于合并同一用户的不同版本的购物车。2

电子商务 — 产品目录

大型电子商务网站的产品目录是在线下处理并加载到大型可扩展缓存中的。来自合作伙伴的馈送和网络爬虫抓取的数据经过处理,生成一个经过清理并尽可能一致的产品目录条目集合。

目录中的每个产品都有一个唯一的标识符。通常,标识符会将您定向到目录的一个分区。该分区有多个副本,每个副本都包含许多产品描述(见图 9)。可扩展产品缓存的一种典型实现方式是使用带有副本的分区。在此图中,列是分区,行是副本。后端处理生成新的产品描述,并通过发布-订阅 (pub-sub) 进行分发。传入的请求被发送到产品标识符的分区,然后在副本之间进行负载均衡。

Mind Your State for Your State of Mind

馈送和爬虫的后端处理,以及缓存更新的发布-订阅分发,对吞吐量敏感,而不是对延迟敏感。不同的副本可能会异步更新,这意味着读取到新版本的描述,重试,然后从尚未更新的缓存副本中获取旧版本,这并不奇怪。

用户查找对延迟非常敏感。正如购物车响应时间必须很快一样,产品目录查找也必须很快。客户端在显示产品描述时,通常会等待响应,超时,并在必要时重试到不同的副本,以确保响应的延迟很低。

请注意,对短延迟的管理取决于产品目录描述的任何版本都可以接受这一事实。这是业务需要立刻得到答案,而不是需要正确答案的另一个例子。

搜索

假设您正在构建一个用于搜索网络内容的搜索系统。网络爬虫为搜索索引器提供数据。每个文档都被赋予一个唯一的 ID。为每个文档识别搜索词。索引词被分配到一个分片。

对索引的更新对延迟不是非常敏感。大多数情况下,通过爬取网络观察到的更改对延迟不敏感。除了对时间敏感的新闻馈送外,更改不需要立即可见。当在世界某个偏远地区生成随机文档时,可能需要一段时间才能被看到。

然而,搜索结果对延迟很敏感。一般来说,来自用户的搜索请求会被发送到服务器,服务器会向所有分片请求匹配的结果。这看起来很像图 9 中描述的产品目录,但用户请求会命中所有分片,而不仅仅是一个分片。

搜索获得快速结果非常重要,否则用户会感到沮丧。需要从所有服务器获得响应加剧了这个问题。如果任何服务器滞后,响应就会延迟。Google 应对此问题的机制在 2013 年的文章《The Tail at Scale》1 中得到了精彩的描述。

在搜索中,可以接受过时的答案,但响应的延迟必须很短。这里没有线性化读取或读己之写的概念。搜索显然需要立刻返回答案,即使答案不完全正确

这关乎应用模式

每种应用模式都显示出不同的特性和权衡,如图 10 所示。

Mind Your State for Your State of Mind

结论

“状态”意味着不同的事物。“会话状态”捕获跨请求的数据,但不跨故障。“持久状态”记住跨故障的数据。

越来越多的可扩展计算由具有无状态接口的微服务组成。微服务需要分区、故障和滚动升级,这意味着有状态会话存在问题。微服务可以调用其他微服务来读取数据或完成任务。

微服务解决方案通常不支持跨无状态调用的事务。微服务及其负载均衡的服务池使服务器端会话状态变得困难,这反过来又使得跨调用和对象进行事务处理变得困难。在没有事务的情况下,跨持久状态对象的协调更改需要使用谨慎的替换技术,其中更新是有序的、已确认的和幂等的。这在编程上具有挑战性,但却是微服务的自然结果,微服务已成为支持可扩展应用程序的主要技术。

最后,不同的应用程序对持久状态有不同的行为需求。您想要正确的结果还是想要立刻得到结果?人类通常想要立刻得到答案,而不是正确的答案。许多基于对象身份的应用程序解决方案可能可以容忍过时的版本。不可变对象可以通过同时做到正确立刻得到结果,从而提供两全其美的方案。

仔细考虑您的应用程序的需求。如果您不小心,您的状态将会出现问题,而您肯定会在意这些问题。

参考文献

1. Dean, J., Barosso, L. A. 2013. The tail at scale. Communications of the 56 (2), 74-80.

2. DeCandia, G., Hastorun, D., Jampani, M., Kakulapati, G, Lakshman, A., Pilchin, A., Sivasubramanian, S., Vosshall, P., Vogels, W. 2007. Dynamo: Amazon's highly available key-value store. Proceedings of the 21st SIGOPS Symposium on Operating System Principals, 205-220.

3. Garcia-Molina, H., Salem, K. 1987. Sagas. Proceedings of the SIGMOD Conference on Management of Data, 249-259; https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf

4. Gray, J., Reuter, A. 1992. Transaction Processing: Concepts and Techniques, Burlington, MA, Morgan Kaufmann 508-509.

5. Helland, P. 2005. Data on the outside versus data on the inside. Proceedings of the Conference on Innovative Database Research.

6. Helland, P. 2002. Fiefdoms and emissaries; download.microsoft.com/documents/uk/msdn/architecture/.../fiefdoms_emissaries.ppt.

7. Helland, P. 2012. Idempotence is not a medical condition. acmqueue 10(4), 56-65.

8. Helland, P. 2016. Immutability changes everything. acmqueue 13(9); https://queue.org.cn/detail.cfm?id=2884038.

9. Helland, P. 2016. Life beyond distributed transactions. acmqueue 14(5); https://queue.org.cn/detail.cfm?id=3025012.

10. Helland, P. 2016. Standing on distributed shoulders of giants. acmqueue 14(2); https://queue.org.cn/detail.cfm?id=2953944.

11. Lakshman, A., Malik, P. 2010. Cassandra: a decentralized structured storage system. SIGOPS Operating Systems Review 44(2), 35-40.

12. von Neumann, J. 1993. First draft of a report on the EDVAC. IEEE Annals of the History of Computing 15(4), 27-75.

相关文章

非易失性存储 Mihir Nanavati 等。数据中心转移中心的启示 https://queue.org.cn/detail.cfm?id=2874238

网络应用程序是交互式的 Antony Alappatt 网络时代需要新的模型,用交互代替算法。 https://queue.org.cn/detail.cfm?id=3145628

存储系统:不再只是一堆磁盘 Erik Riedel 今天可用数据的庞大规模和范围给存储系统带来了巨大的压力,要求它们以过去无法想象的方式运行。 https://queue.org.cn/detail.cfm?id=864059

Pat Helland 自 1978 年以来一直从事事务系统、数据库、应用程序平台、分布式系统、容错系统和消息传递系统的实现工作。为了消遣,他偶尔撰写技术论文。他目前在 Salesforce 工作。

版权 © 2018 由所有者/作者持有。出版权已许可给 。

acmqueue

最初发表于 Queue vol. 16, no. 3
数字图书馆 中评论这篇文章





更多相关文章

Alex Petrov - 现代存储系统背后的算法
本文深入探讨了现代数据库中常用的两种存储系统设计方法(读优化的 B 树和写优化的 LSM(日志结构合并)树),并描述了它们的使用案例和权衡。


Mihir Nanavati, Malte Schwarzkopf, Jake Wires, Andrew Warfield - 非易失性存储
在大多数执业计算机科学家的整个职业生涯中,一个基本观察始终成立:CPU 的性能远高于 I/O 设备,且价格也更昂贵。CPU 能够以极高的速率处理数据,同时服务于多个 I/O 设备,这一事实对各种规模系统的硬件和软件设计产生了深远的影响,几乎从我们开始构建它们以来就是如此。


Thanumalayan Sankaranarayana Pillai, Vijay Chidambaram, Ramnatthan Alagappan, Samer Al-Kiswany, Andrea C. Arpaci-Dusseau, Remzi H. Arpaci-Dusseau - 崩溃一致性
数据的读取和写入是任何冯·诺依曼计算机最基本的方面之一,但它出人意料地微妙且充满细微差别。例如,考虑在具有多个处理器的系统中访问共享内存。虽然被称为强一致性的简单直观方法最容易被程序员理解,但许多较弱的模型也被广泛使用(例如,x86 总存储顺序);这些方法提高了系统性能,但也以使系统行为的推理更加复杂且容易出错为代价。


Michael Cornwell - 固态硬盘剖析
在过去的几年中,一种新型存储设备已进入笔记本电脑和数据中心,从根本上改变了人们对存储的功耗、尺寸和性能动态的期望。SSD(固态硬盘)是一种已经存在了 30 多年的技术,但由于价格过于昂贵而未能得到广泛采用。





© 保留所有权利。

© . All rights reserved.