原文转自我自己的个人公众号:https://mp.weixin.qq.com/s/iYOnSxO8XBi9LbxbDCdJcA
由于我是从公众号上直接复制粘贴过来的,排版上可能有问题。推荐使用上方连接查看原文。
该文是Salesforce的软件架构师Pat Helland于2016年12月发表的针对其在2007年CIDR(创新数据库研究会议)上首次发表的同名文章的更新和缩写版本。
2007年原文:http://www-db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf
2016年更新的原文:https://queue.acm.org/detail.cfm?id=3025012
注0:本人英语水平实在是...哎...,但尽量在尊重原文的基础上让表达更加易懂。
注1:文中提到的扩展(Scale)意思是部署多个应用实例。
注2:文中提到的实体可以简单理解为关系型数据库中的一行记录。
注3:替代键可以理解为非主建的能唯一标识记录的字段。替代索引则是替代键上创建的索引。
注4:文中提到的实体伙伴,我的理解就是与当前实体有关联的其它实体。
我对本文的理解:开始时是想直接看这篇论文的译文,网上搜索后发现译文中的问题很多,于是开始看英文原文,可能是因为我的英语水平太烂,感觉文章艰涩难懂,每一句都需要仔细琢磨,而琢磨后发现,说的都是现在已经是现实的理论。但再想想,文章的绝大部分内容都是2007年发布的,便会膜拜这篇文章的先见之明。文章讨论了不使用分布式事务如何构建可大规模水平扩展的应用系统的假设和思考,主要是对数据水平切分后的事务处理方式的讨论。看过之后,对当下广泛使用的一些分布式事务解决方案会有更深的理解。总而言之,我觉得这篇文章所描述的就是BASE理论。
下面正式进入原文内容。
事务是一个令人惊讶的强大的机制,我花费了近40年的职业生涯来研究它。在1982年,我的工作是为Tandem NonStop系统提供事务。这个系统的平均故障间隔事件以年为单位并且包含一个地理上被分布的两阶段提交,以此来提供极好的可用的强一致事务。
新的创新,包括Google的Spanner,在一个非常大的规模中提供强一致的事务环境以及极好的可用性。构建分布式事务来支持高可用的应用是一个非常大的挑战,它需要极大的创新和良好的技术来支撑。不幸的是,这两点在应用开发者中的可用性并不广泛。
大部分分布式事务系统,单个节点的错误会引起事务提交的异常。事务提交的异常反过来也导致应用异常。在这类系统中,系统越大就越容易出问题。当一个飞行中的飞机需要所有引擎都保持正常运转才能安全飞行,那么增加越多的引擎将导致飞机的可用性越低。除非是用特殊的机制来容忍宕机,否则在上千个节点上运行一个分布式事务系统是不切实际的。当应用开发人员构建的系统使用的是非高可用的分布式事务时,其解决方法都是易碎的并且需要被丢弃。自然而然就诞生了...
应用使用不提供事务保证的技术来构建,但保证符合业务需求。
这篇文章讨论和命名一些实践方法,用来在不使用分布式事务时实现大规模关键任务应用。主题包括应用数据细粒度的分片的管理,随着应用的成长,这些分片可能会被重新分区。一些设计模式支持在这些可重新分区的数据分片之间发送消息。
本文的目标是帮助读者减少在构建大规模应用时遇到的挑战。同时,通过观察这些设计模式,行业可能会推动平台的创建来让开发可扩展的应用变得更加容易。最后,尽管这篇文章的目标是可扩展的同质应用,但是这些技术对于可扩展的异构应用同样非常有用,例如对移动设备的支持。
目标
这篇文章关注于一个应用开发者,当他只有一个本地数据库或者本地事务系统可用时,如何构建一个成功的可扩展的企业应用。不解决可用性,目的仅仅是扩展性和正确性。
讨论可扩展应用
大多数可扩展应用的设计者都理解业务需求。有问题的是对于实际的问题、概念、对事务间交互的抽象以及可扩展的系统没有命名且没有清楚的理解。它们被不一致的使用并且有时会反咬设计者一口。这篇文章的一个目标是开启一个讨论,对这些概念提高意识,引导到一个通用的术语集和一个共识的方法来扩展程序。
思考几乎无限的应用扩展性
这篇文章提供一个关于几乎无限扩展的影响的非正式的思想实验。让我们假设有一些客户、可购买的实体、订单、运送的货物、关注健康的病人、纳税人、银行账户和所有其它由应用管理并且随着应用成长而显著增长的业务概念。通常,每个个体事物不会非常大,它们只是越来越多。如果只是CPU、DRAM、存储或者其它资源的第一次饱和,那么这没什么事。某一时刻,需求的增长会导致需要将原来运行在单台机器变为展开运行到许多机器上。这个思想实验使得我们需要考虑成千上万的机器。
几乎无限的扩展性是一个松散的、非精确的和刻意无定形的方式,它激发了人们非常清楚什么时候什么场合需要明白哪些适合于一个机器的知识,以及当你不能保证只有一个机器时怎么办。并且,你希望几乎线性的数据和计算负载的扩展。当然,一个拥有大对数的N-log-N的扩展将会非常大。
为可扩展的应用描述一些通用的模式
几乎无限扩展对于业务逻辑有什么影响?我断言扩展性意味着在编写程序时使用一个新的叫做实体(Entity)的抽象。一个实体在一个时刻存活在一个机器上,应用在一个时刻只能维护一个实体。几乎无限扩展的结果是这种编程抽象必须被导出到业务逻辑的开发人员。
通过命名和讨论这种尚未命名的概念,我们或许可以同意在一个一致的编程方法和一个对所涉及问题一致的理解上构建可扩展系统。
此外,实体的使用隐含了用于连接它们的消息模式的使用。这会导致状态机的创建,以此来处理无辜的开发人员试图为业务问题构建可扩展的解决方案时的消息传递不一致。
一些设想
考虑下面三个设想,它们被声称但没有正当的理由。根据经验假设这些都是真的。
应用的分层和规模不可知论
让我们假设每个可扩展的应用有至少两层,如下图所示。这些层对于扩展的感知不同。它们也可以有其它的不同,但是其它的不同不在我们的讨论中。
scale-agnostic code: 对扩展无感知的代码
scale-agnostic API: 对扩展无感知的API
scale-aware code: 对扩展有感知的代码
应用的底层了解添加更多的计算机会让系统扩展。另外,它管理着上层代码到物理机器的映射和它们的位置。底层是对扩展有感知的,它知道这种映射关系。我假设底层为上层提供一个对扩展无感知的编程抽象。现实中这种对扩展无感知的编程抽象有很多例子,包括MapReduce。
使用这个对扩展无感知编程抽象,应用的上层代码在编写时不需要考虑扩展时的问题。通过插入对扩展无感知的编程抽象,你可以编写不用担心当负载不断增加需要扩展部署的代码。
随着时间推移,这些应用的底层可能会进化成一个新的平台或者中间件,用来简化对扩展无感知的API的创建。
事务范围
很多关于在分布式系统中提供强一致事务概念的学术工作被完成。这包括2PC(两阶段提交)、Paxos和最近的Raft。经典的2PC将在一个机器失败时加锁,除非协调器和事务中的参与者是容忍错误的,例如Tandem NonStop系统。Paxos和Raft在节点失败时不会加锁,但会做额外的工作来协调,很像Tandem的系统。
这些算法可以被描述为在分布式系统中提供强一致事务。它们的目标是允许分布在许多机器上的数据进行任意的原子更新。更新存在于一个单独的事务范围中,跨越许多机器。
不幸的是,在许多情况中,这不是一个应用开发人员可以选择的。应用可能需要跨越可信边界、不同的平台、不同的操作和部署区。如果对分布式事务说“不”会发生什么?
在这篇论文首次发布后的10年中,每一天,真正的系统开发人员很少尝试在超过很少的机器之间使用强一致事务。取而代之的是,他们假设多个分离的事务范围。每个机器使用本地事务作为一个分离的事务范围。
大部分应用使用至少一次(At-Least-Once)的消息
如果你是一个短生命期的Unix风格的进程,那么TCP/IP是非常好的,但是考虑到一个应用开发人员所面临的困境时就不够了,开发人员的工作就是处理一个消息,并且修改数据库中的持久化数据。消息被消费还没有被应答。数据库被更新然后消息被应答。失败的时候,过程会重启,消息会再被处理一次。
困境源于以下事实:消息的传递是没有直接的被耦合到持久化数据的更新上的,而是通过应用的动作。然而,将消息的消费耦合到持久化数据的更新是是可能的,但这不是通常可用的。这种耦合的缺乏导致出现失败窗口,其中消息会被传递多次。消息管道至少发送一次它们而不是丢失消息。
这种行为的结果是,应用必须容忍消息的重试和无序的消息传递。
有待证明的选择
编写建议的好处是你可以发表疯狂的建议。这里列出本文尝试证明的几点。
可扩展的应用使用唯一标识的实体
本文讨论了每个应用的上层代码必须维护一个单一的被称作是实体的数据集。这里不限制一个实体的大小,但是它必须生存在一个单一的事务范围之内(例如,一个机器中)。
每个实体都有一个唯一标识或者键,如下图所示。一个实体键可以是任何形式、样式或者风格。不管怎么说,它必须唯一的识别一个实体以及实体包含的数据。
实体代表什么也不做限制。它可能是SQL记录、XML、JSON、文件或者其它。一个可能性是代表一个SQL记录的集合,可能跨越多个表,它的主键用实体键作为前缀。
实体代表了分散的数据集。每个数据都精确的位于一个实体中。
一个应用由许多实体组成。例如,一个订单处理应用封装了许多订单,每个订单通过一个唯一的订单ID来标识。为了要扩展订单处理应用,一个订单中的数据必须和其它订单分散开。
原子事务不能跨越实体
每个机器都被假设为一个分离的事务范围。本文后面会提出原子事务不能跨越实体的论点。程序员必须总是为每个事务提供一个单一实体中所包含的数据。
从程序员的视角来看,唯一标识的实体就是事务范围。这个概念对于应用的扩展性设计影响很大。一个隐含的需要被讨论的是,当设计一个几乎无限扩展性时,替代索引不能被事务的一致性保证。
消息用于实体间通信
大部分消息系统没有考虑数据的分区键,而是维护一个被无状态进程消费的队列。标准实践是在消息中包含一些数据来告知无状态的应用代码去哪里获取需要的数据。消息中包含的就可以是实体键。应用从一些数据库或者其它持久化存储中获取实体的数据。
几个有趣的趋势正在发生。首先第一个,实体集的大小不断增长最终超过一个单一机器可以承载的量。每个单独的实体通常适合在一个机器上,但是它们的集合则不然。越来越多的,无状态应用基于一些分区规则路由的获取实体。
第二个,获取和分区规则被分离到应用的底层。这是刻意的为了与上层代表的业务逻辑隔离开。
这个模式有效的通过实体键来路由目标实体。无状态的Unix风格进程和应用底层都只是作为对扩展无感知的API的一部分供业务逻辑使用。上层对扩展无感知的业务逻辑只是将实体键放入消息中,用来识别被称为实体的持久化状态。
实体管理每个伙伴的状态(活动)
对扩展无感知的消息实际上是实体到实体的消息。发送实体通过它的持久化状态进行显示并且通过实体键进行识别。它向另一个实体发送一个消息并且通过实体键来标识它。接收者实体由对扩展无关的上层业务逻辑和代表它状态的持久化数据组成。通过它的实体键进行标识。
回顾消息至少被传递一次的假设。接收者实体可能接收到重复的消息,这个消息必须被忽略。实际上,消息有两类:那些影响实体状态的和那些不影响实体状态的。不影响实体状态的消息是容易处理的——它们是幂等的。而改变状态的消息需要更多的关注。
为了保证幂等(也就是说,保证消息的重复处理是无害的),接收者实体通常被设计成可以记录哪些消息被处理过。一旦消息被处理了,重复的消息通常会匹配第一个消息的结果创建另一个响应。
被接受的消息中的信息创建了状态,这个状态在每个伙伴的基础上被包装。重要的观测是状态在每个伙伴的基础上被组织,并且每个伙伴都是一个实体。
术语活动被用于状态,它在关系的双方管理了每个伙伴的消息。每个活动存在一个特定的实体中。一个实体为每个伙伴实体都提供一个活动。
另外为了管理消息冲突,活动被用来管理松耦合的协定。这在原子事务的世界中是不可能发生的,暂时性的操作被用于协商出一个共享的结果。这些在实体间被执行并且被活动所管理。
为达到协定而建立工作流是充满挑战的,很多文章对其进行了描述。本文不会断言活动会解决这些挑战,而是提供了一个为了解决它们所需的存储状态的基础。几乎无限扩展性导致令人惊讶的细粒度的工作流风格的解决方案。参与者是实体,每个实体使用和其它实体有关的特定知识来管理它的工作流。在实体中维护双方的知识被称作一个活动。
活动的例子有时是微妙的。一个订单应用发送消息给运送应用。它包括了送货单ID和发送订单ID。消息类型可能用于在运送应用中激发状态变更来记录指定的订单已经准备被运送了。经常地,实现者没有为重试进行设计,直到bug出现。罕见却偶尔地,应用设计者会为活动而思考和计划。
实体
这一节更深入的审查实体的特性
分散事务范围
每个实体通过在一个单一事务范围内唯一的键被定义为一个数据集。原子事务可能总是在一个单一的实体内被执行。
有唯一键的实体
应用的上层代码自然的被设计成通过一个唯一键来处理数据集。客户ID、社会安全码、产品SKU以及其它唯一标识可以在应用中使用。它们作为键被用于定位引用的数据。保证事务原子性只会存在有唯一键的一个实体中。
重分区和实体
前面提到的一个假设是,上层是对扩展无感知的而底层决定了如何根据扩展的变更逐步的部署。特定实体的位置可能会随着部署的进展而改变。应用的上层不能假设实体的位置,因为那样就不是对扩展无感知的了。
如下图所示,实体使用散列或者根据key的范围来分区从而跨越事务范围。
原子事务和实体
在可扩展的系统中,你不能假设更新事务可以跨越不同的实体。每个实体有一个唯一键,并且每个实体都仅仅是放置到一个事务范围内。回忆几乎无限可扩展的起因是实体数量不可阻挡的增加,但是每个单独实体的大小保持足以在一个事务范围内(即,一个机器)的承诺。
你怎么知道两个不同的实体被同一个事务范围所保证呢,或者说可以原子性的更新呢?只有当同一个唯一键整合了它们两者时才可以这么说。那样,它就真的是一个实体。
如果对实体键散列用于分区,任何时候都无法判断当两个实体有不同的键时会不会分布在相同的区中。如果使用的是键的范围来分区,大部分时间,相邻的键值会在同一个机器中。但运气不好的时候,相邻的键也会分布在不同的机器中。
一个简单的测试用例是,在用键范围来分区时,依靠相邻实体的原子性在大部分时候都是正确的。之后,当重新部署导致实体移动到不同机器上时,潜在的bug就出现了;更新不再是原子的了。你永远都不能依靠不同的实体键的值会分布在同一个地方这种假设。
简单来说,应用的底层会确保每个实体键(和它的实体)都会放置在一个单独的机器上。不同的实体可能会在不同的机器上。
一个对扩展无感知的编程抽象必须有实体的概念,因为它会作为原子性的边界。理解实体,使用实体键,以及明确的承诺跨越实体缺乏原子性对一个对扩展无感知的编程极其重要。
在行业的今天,大规模应用隐含的做到了这些;仅仅是没有一个名字可以代表实体的概念。从应用的上层视角,它必须假设实体是事务的边界。假设当部署变化时会有更多的中断。
考虑替代索引
我们习惯于使用多个主键或者索引来处理数据的能力。例如,有时使用社会安全码来引用一个客户,另一些时候使用信用卡号,又或者是住址。假设极端的扩展规模,这些索引不会分布在相同的机器甚至是一个集群中。一个客户的数据并不知道是否在一个单一的事务范围内。实体自身是在一个单一的事务范围内。挑战在于,用于创建一个替代索引的信息副本必须被假设分布在一个不同的事务范围内。
考虑一下保证替代索引分布在同一个事务范围内。当开始几乎无限的扩展,实体的集合会分布在大量的机器上。主建和替代索引信息必须放在同一个事务范围内。保证这点的唯一方法是使用主键来定位替代索引。那样可以保证在同一个事务范围内。如果不使用主键,并且搜索所有的事务范围,那么每一次替代索引的检索都必须在几乎无限数量的范围上检查匹配的替代键。这最终会变得站不住脚。
逻辑上的改变是需要做两步检索。首先,检索替代键,得到实体键。然后,使用实体键访问实体。这非常像关系型数据库内部基于替代键访问记录的步骤。但是几乎无限扩展的承诺意味着两个索引(主建索引和替代键索引)不会在同一个事务范围内。如下图所示。
对扩展无感知的应用程序不能原子性的更新一个实体和它的替代索引。上层对扩展无感知的应用必须被设计为明白替代索引可能会和被访问实体的主键(即,实体键)不同步。如上图所示,不同的键(主键和替代键)不能原子的一起使用或更新。
如何处理过去自动管理的替代索引现在需要应用手动管理的情况。基于异步消息的工作流风格的更新可以用于这种情况。当使用一个替代索引读取数据时,你必须明白潜在的与实体本身不同步的情况。替代索引变得更加难处理。这是超大系统中必须面对的残酷世界。
跨越实体的消息
这一节考虑使用消息连接独立的实体。审查命名,事务和消息,消息传递语义和实体重分区的影响。
消息在实体间的通信
如果你不能在同一个事务中修改两个实体的数据,那么你需要一个机制在不同的事务中完成更新。实体间的连接基于消息。
异步发送事务
由于消息是跨实体的,数据和与其相关的发送消息的决策在一个实体中,消息的目的地是另一个实体。根据实体的定义,这些实体不能被原子性的更新。消息不能跨越这些不同的实体进行原子性的发送和接收。
对于一个应用开发人员来说,需要事务的时候发送一个消息,在消息发送后然后事务终止,这可能极其的复杂。这可能意味着你没有印象导致什么事情的发生但是这个事确实发生了。因此,事务性的消息入队是必须的,如下图。
如果消息不能被目标看到直到发送方事务提交之后,则消息对于发送方事务来说就是异步的。每个实体通过事务到达一个新的状态。由于事务,消息是促进因素,来自于一个事务并且因为事务又到达一个新的实体。
命名消息的目的地
考虑一个应用中对扩展无感知的编程,由于一个实体希望给另一个实体发送一个消息。目的地实体的位置对于对扩展无感知的代码是不知道的。只知道实体的键。
它会下沉到应用中对扩展有感知的部分来关联实体键和实体的位置。
重分区和消息传递
当应用中对扩展无感知的部分发送一个消息,底层的对扩展有感知的部分寻找目的地并至少一次的传递消息。
由于系统扩展,实体移动。这通常被成为重分区。因此,实体的位置,消息的目的地可能会变迁。有时候消息发送到旧的位置,但是发现实体已经被迁移到其它地方了。这时候消息会跟随实体。
由于实体移动,在发送方和目的地间原本清晰的先进先出队列偶尔会出现混乱。消息是重复的。后发送的比先发送的早到达。生活变得凌乱。
因此,对扩展无感知的应用进化到支持对所有应用可见消息的幂等处理。这也意味着消息传递中的重新排序。
活动:处理混乱的消息
这一节讨论在消息重试和从排序的挑战中的处理方法。这里介绍一个活动的概念,作为管理与一个伙伴实体间关系所需的本地信息。
重试和幂等性
由于任何消息都可能被传递多次,应用需要一个机制来处理重复的消息。有可能的是通过底层对消除重复消息提供支持,在一个几乎无限扩展的环境中,底层的支持需要了解实体。由于重分区,消息被传递到实体的知识必须在实体移动的时候跟着移动。实际上,这些知识的底层管理很少发生;消息可能会传递不止一次。
通常,应用中对扩展无感知的(高层级)部分必须实现一种机制来确保接收的消息是幂等的。这对于问题的本质不是必须的。消除重复可能可以构建到应用中对扩展有感知的部分。这个目前尚不可用。因此,让我们考虑对扩展无感知的应用的开发人员必须实现什么。
定义实际行为的幂等性
如果一个后续的处理的执行对实体没有发生一个实际的改变,那么消息的处理就是幂等的。这是一个没有定型的定义,可以让应用自己规范什么实际的行为什么不是。
如果一个消息没有改变所涉及的实体,只是读取信息,这个过程是幂等的。即便一条描述读取的日志记录被写入也是如此。实际记录不是实体的实际行为。定义什么是实际行为什么不是,需要应用来规范。
天生的幂等性
为了获得幂等性,本质上是消息不能引起实际的副作用。一些消息任何时候都不会引起实际的工作。这些是天生的幂等性。
一个消息只是从一个实体中读取一些数据是天生的幂等性。如果一个消息的处理会改变实体但是不是以实际的形式呢?同样,那些也是天生的幂等。
更困难的是。一些消息隐含的工作实际上引起了实质性的改变。这些消息就不是天生幂等的了。应用必须包含机制来确保这些消息的处理也是幂等的。这意味着使用某种方式记住消息已经被处理了,这样后续的尝试就不会发生实质上的改变。
下一节考虑非天生幂等消息的处理。
用状态记住消息
为了确保非天生幂等消息的幂等处理,实体必须记住它们处理过的消息。这就可以使用状态。状态作为消息被处理的结果。
另外,如果需要回应,必须返回相同的回应。毕竟,你不知道原发送方是否接收到了回应。
为每个伙伴管理状态
为了追踪关系和接收到的消息,每个在对扩展无感知的应用中的实体都必须以某种方式记住它的伙伴的状态信息。必须以基于伙伴到伙伴的方式获取状态。让我们将这个状态命名为活动。如下图所示,每个实体如果与多个实体交互那么可能有多个活动。活动追踪每个伙伴的交互。
每个实体由活动的集合组成,并且另一些数据可能会跨越活动。
考虑为购买而处理由许多物品组成的订单。为每个单独的物品预留库存将作为一个独立的活动。可能会由一个订单的实体和被仓库管理的许多独立的物品实体。事务不能假设可以跨越这些实体。
在订单中,每个库存的物品会被分别地管理。消息协议必须被分别管理。每个被包含在订单实体中的库存物品的数据是一个活动。虽然没有命名,但这种模式频繁的出现于大规模应用中。
在一个几乎无限扩展的应用中,你需要对关系非常清楚。你不能通过简单的查询就可以明白其中的关系。所有事情都必须使用双方的关系网被正式的编织在一起。编织的完成是通过实体键来进行的。由于伙伴之间存在距离,你必须正式的管理对伙伴关系的理解,当有新的伙伴加入时作为知识使用。本地信息通过一个活动了解远方伙伴的引用。如下图。
基于活动确保消息至多一次接纳
处理非天生幂等的消息需要确保每个消息被至多处理一次(即,消息的实际影响必须至多发生一次)。为了达到这一点,一些消息的唯一特征必须被记住,以此确保不会被处理多次。
实体必须持久化的记住从消息就绪到进入不会引起实际影响的状态的过渡。
通常,一个实体使用它的活动来实现这个基于伙伴到伙伴的状态管理。这本质上是因为有时一个实体支持多个不同的伙伴,每个伙伴都会通过消息模式关联与之有关的关系。每个伙伴使用一个状态集合让这种情况变为可能。
活动:非原子性处理
这一节介绍在不使用分布式事务的情况下可扩展的系统如何制定决策。
管理被分布的协定是很困难的。在一个几乎无限扩展的环境中,不确定性的表示必须以围绕每个伙伴关系的细粒度的方式完成。数据使用活动的概念在实体中被管理。
距离的不确定性
分布式事务的缺少意味着在尝试在跨越多个不同实体间统一决策时接收不确定性。不可避免地,跨越分布式系统的决策需要在一段时间内接收不确定性。当使用分布式事务时,这种不确定性表现为数据上的锁并且被事务管理器所管理。
在不依靠分布式事务的系统中,不确定性的管理必须在业务逻辑中被实现。结果的不确定性被业务语义持有,而不是记录锁。这可以是简单的工作流。它并不神奇。因为你不能使用分布式事务,所以就用工作流。
引出实体和消息的假设现在引出一个结论,对扩展无感知的应用必须使用工作流来管理自身的不确定性。这需要多个实体间达成一致。
考虑常见的跨业务的交互风格。业务间的协议包括时间承诺、取消条款、预留资源等等。不确定性被封装在业务功能行为中。尽管实现起来相比分布式事务更加复杂,但这却是真实世界中所发生的...
同样,这只是工作流的一个论据。
活动和不确定性管理
有时实体在与其它实体交互时会接收不确定性。这个不确定性必须基于伙伴到伙伴的方式被管理,并且可以对伙伴来说可以作为具体的活动状态被可视化。
很多时候,不确定性由关系所表示。它很有必要通过伙伴进行追踪。活动跟踪每个伙伴的状态改变。
如果一个订单系统从一个仓库中预留库存,仓库在分配的时候不知道库存是否将被使用。这就是接收不确定性。之后,如果预留的库存将被需要,仓库找出需要的库存。这就解决了不确定性。
仓库库存管理员必须为每个订单的物品保留关系数据。由于它连接了货物和订单,因此这些将通过货物来组织。每个货物保留关于需要这个货物的未完成的订单的信息。货物中的每个活动(一个订单一个)管理订单的不确定性。
执行暂定业务操作
为了跨实体达成一致,一个实体要求其它实体接收不确定性。一个实体向另一个实体发送一个请求,这个请求之后可能会被取消。这被称作暂定操作。在这一步的最后,一个实体同意遵守另一个实体的愿望。
暂定操作,确认和取消
暂定操作的本质是取消权。如果请求的实体决定不再向前继续时,它发布一个取消操作。如果它决定继续向前,就发布一个确认操作。
当一个实体同意执行一个暂定操作,它同意让另一个实体决定结果。它接收不确定性,这增加了困惑。当确认和取消到达,不确定性降低,减少了困惑。伴随着每一次增加和减少不确定性,旧的问题被解决,新的问题又出现,整个生命周期上是正常被处理的。
同样,这仅仅是个工作流,但它是将实体最为参与方的细粒度的工作流。
不确定性和几乎无限扩展性
不确定性的管理通常围绕双方的协定。这里可能存在很多双方协定。这些编织在一起形成一张细粒度的网,双方协定使用实体键最为连接,使用活动来追踪远方伙伴的已知状态。
考虑有第三方担保公司参与的房屋买卖。买方与第三方公司签订信任协定,同样的,与卖方、抵押公司和交易中的其它方签订协定。
当你准备签字购买房子时,你不知道交易的结果。你接收它,直到委托结束前都是不确定的。唯一能够控制决策的是委托公司。
这是一个中心辐射型的双方关系集合,被用于在大量相关方中不适用分布式事务达到一致。
当考虑几乎无限扩展性时,双方关系的思考是有趣的。通过建立双方的暂定操作/取消/确认(就像传统的工作流一样),你可以看到达成分布式一致的基础。就像委托公司一样,许多实体可能通过合成的方式参与到协定中。
由于双方的关系,一个活动的简单概念是“我记得那个伙伴的东西”变成一个管理庞大系统的基础——即便当数据存储在实体中并且你不知道实体在什么地方的时候。你必须假设它很远。仍然的,它可以被变成到一个对扩展无感知的方式中。
现实中的几乎无限扩展的应用喜欢全局事务范围这种奢侈品。不幸的是,当系统失败时,在不引入脆弱性的前提下,对我们大多数人来说这种可用性是很难得到的。
相反,暂定工作的不确定性的管理交给了对扩展无感知的应用的开发者。它必须向预留库存、信贷额度分配和其它特定应用概念一样被处理。
结论
像通常一样,计算机行业日新月异。
今天,新的设计压力被强加于原本只是像解决业务问题的程序员。他们的现实使他们进入一个几乎无限扩展的世界并且让他们面对大量与真实业务不相关的设计问题。这就是现在的实际情况,并且本文首次发布的2007年就是这样的。
不幸的是,程序员们努力解决如电子商务、供应链管理、金融和健康管理应用这些业务目标时,越来越多的需要考虑在没有分布式事务的情况下如何扩展。大多数开发人员根本无法访问提供可扩展的分布式事务的强大系统。
我们处在一个可以看到一些构建这些应用的模式,但是却还没有一致性的应用这些模式的时刻。本文讨论了这些初期的模式可以被更加一致的应用于手工开发的为几乎无限扩展设计的应用中。
本文介绍和命名了在大规模应用中出现的一些形式主义:
实体:命名(键)数据的集合,在实体范围内可以原子性更新,但跨多个实体永远不能原子性更新。
活动:由实体内部的状态集合组成,用于管理一个伙伴实体的消息关系。
工作流用于在实体的活动中达成一致性。当关注几乎无限扩展性时,工作流的细粒度特性会令人惊讶。
当今许多应用隐含的被设计成使用实体和活动。他们只是没有正规化,也没有一致性的被使用。当不一致的使用时,bug会出现,然后最终被修复。通过讨论和一致性的使用这些模式,可以更好的构建大规模的应用,并且作为一个行业,通过建立解决方案我们更加贴近于允许业务逻辑程序员集中在业务问题上而不是扩展问题。
更多内容请关注我的公众号: