acm-header
登录

ACM通信

实践

幂等性不是一种医学状况


幂等力不是一种医学状况,说明

图片来源:Studio公吨

回到顶部

的定义分布式计算可能会让人困惑。有时,它指的是紧密耦合的计算机集群一起工作,看起来像一台更大的计算机。然而,更多时候,它指的是一堆松散相关的应用程序在一起聊天,没有大量的系统级支持。

这种在分布式计算环境中缺乏支持的情况使得编写协同工作的应用程序变得困难。在系统之间发送的消息没有明确的传递保证。它们可能会丢失,因此,在一个暂停之后,它们会被重新尝试。通信另一端的应用程序可能会看到多个消息到达预期的位置。这些消息可以重新排序,并与不同的消息交织在一起。确保应用程序按预期的方式运行可能很难设计和实现。测试就更加困难了。

在一个充斥着重复信息的世界里,幂等性是可靠系统的基本属性。幂等性是一个数学术语,意思是将一个操作执行多次与只执行一次具有相同的效果。当消息彼此相关并且可能有顺序限制时,就会出现这种问题。消息是如何关联的?会出什么问题?应用程序开发人员如何才能在不失去他或她的魔力的情况下构建一个正常运行的应用程序?

回到顶部

面向服务世界中的消息传递

本文考虑在一个由一组共享工作的服务器组成的面向服务的环境中进行消息传递。我们希望当需求增加时,可以通过增加更多的服务器来进行扩展。这就产生了一个问题,即以后的消息如何定位记得以前发生的事情的服务(运行在服务器上)。

这里,通过观察服务以及长时间运行的工作如何在服务之间运行来描述所考虑的应用程序类型,并通过考虑在共享相对简单标准的世界中独立发展的应用程序之间的消息传递需求,来框定问题。

回到顶部

消息和长时间运行的工作

有时,相关信息会晚到很多时候,可能是几天或几周之后。这些消息是试图跨多个机器、部门或企业工作的应用程序所执行的同一项工作的一部分。通信应用程序需要具有关联相关消息所需的信息。

可预测和一致的行为对于消息处理是必不可少的,即使在一些参与者崩溃和重新启动时也是如此。要么之前的信息不重要,要么参与者需要记住它们。这意味着某种形式的持久性捕获了早期消息中重要内容的本质,以便长时间运行的工作可以继续。有些系统确实需要来自早期消息的信息来处理后期消息,但没有为在系统崩溃时记住这些东西做准备。

当然,当系统崩溃介入时,对于不寻常的行为有一个技术术语;它被称为错误

随着时间的推移,应用程序的形状和形式继续发展,从大型机过渡到小型机,再到个人电脑,再到部门网络。现在,可扩展的云计算网络提供了实现应用程序的新方法,以支持数量不断增加的消息传递合作伙伴。

随着实现应用程序的机制的变化,消息传递的微妙之处也在变化。大多数应用程序都使用半稳定的Internet标准来包含与多个合作伙伴的消息传递。这些标准支持不断变化的环境,但不能消除一些可能的异常。

回到顶部

想象两个服务之间的对话

在这里,我们考虑到双方之间沟通的挑战。(其他消息传递模式,如发布-订阅,在这里不讨论。)它想象在相互关联的双方之间有一个任意的消息通信序列,并假设这两个程序对这个对话所完成的工作有一个概念。即使有最好的“消息传递管道”支持,两个服务之间的两方消息传递对话框也有复杂的问题需要解决。

应用程序通常运行在某种形式的“管道”之上。在其他服务中,应用程序的管道提供消息传递方面的帮助。它可能在命名、交付和重试方面提供帮助,并可能提供将消息关联在一起的抽象(例如,请求-响应)。

作为应用程序设计人员,您只能了解当您与客户端、服务器或移动设备上的管道进行交互时,请查看。你可以预测或推断接下来会发生什么,但所有这些都是由你的本地管道(参见图1).

当本地应用程序(或服务)决定与另一个服务进行对话时,它必须使用预期合作伙伴的名称。第一个消息的合作伙伴服务的标识指定了与正在执行应用程序工作但尚未参与某些联合项目的服务聊天的愿望。这与连接到处理了前面消息的同一服务实例确实不同。

当消息在对话框中发送时,关系就建立了。处理后续消息意味着合作伙伴记住先前的消息。这种关系意味着在对话中间的伙伴的身份不同于对话开始时的新伙伴。

中间对话框标识的表示,以及如何将后面的消息路由到正确的位置,都是管道的一部分。有时管道非常薄弱,以至于应用程序开发人员必须提供这些机制,或者管道可能不是在所有通信的应用程序上都普遍可用,因此应用程序必须钻研解决这些问题。现在,让我们假设管道是相当聪明的,并看看应用程序可以期望的最佳情况行为。

回到顶部

消息、数据和事务

当应用程序有一些它正在操作的数据(可能在具有事务性更新的数据库中)时会发生什么?考虑以下选项:

  • 记录消息消耗之前处理。这是罕见的,因为构建可预测的应用程序非常困难。有时消息将被记录为已使用,应用程序开始执行该工作,而事务失败。当这种情况发生时,消息实际上根本没有被传递。
  • 消息作为数据库事务的一部分使用。这是应用程序最简单的选项,但并不常用。消息传递系统和数据库系统常常是分开的。有些系统,如SQL Service Broker4提供此选项(参见图2).有时,管道将以事务方式将传入消息的消费与应用程序数据库中的更改和传出消息联系起来。这使应用程序更容易,但需要消息传递和数据库之间的紧密协调。
  • 消息已被使用处理。这是最常见的情况。应用程序必须设计成每条消息在其处理过程中都是幂等的。存在一个失败窗口,在此窗口中工作将成功应用到数据库,但失败将阻止消息传递系统知道消息已被使用。消息传递系统将重新驱动消息的传递。

传出消息通常作为提交处理工作的一部分进行排队。它们也可以与数据库更改紧密耦合,或者允许分离。当数据库和消息传递更改与一个公共事务绑定在一起时,消息的消费、对数据库的更改和传出消息的入队都绑定在一个事务中(参见图2).只有将传出的消息排队将使该消息离开发送系统。允许它在事务提交之前离开可能会导致消息被发送但事务中止的可能性。

回到顶部

订单交付:历史还是货币?

在两个合作伙伴之间的通信中,有一个向每个方向发送命令的明确概念。当我向你发送消息时,你可能正在向我发送消息,这意味着消息之间的顺序是模糊的,但每次只有一条消息会向特定的方向发送。


没有什么比管道工更能减轻你的烦恼,让一切“正常工作”。如果那个水管工对你和你的需求有同样的理解,那就太好了。


侦听器可以很容易地指定它不需要无序的消息。如果你派我来n不同的消息以特定的顺序,我真的想看到消息n1当我已经看到信息的时候n?如果管道允许应用程序看到这种重新排序,那么应用程序很可能必须添加一些额外的协议和处理代码来应对这种疯狂的情况。

假设消息总是请按顺序来。有两种合理的应用行为:

  • 历史(无空白).不仅消息将按顺序交付,而且管道将尊重顺序,不允许有间隙。这意味着管道将不会传递消息n在对话框序列的应用程序if消息n1不见了。获取信息可能有用n1但这对应用程序是透明的。这对于业务流程工作流和许多其他情况非常有用。丢失消息会使应用程序的设计非常非常困难,所以应用程序不希望出现空白。
  • 货币(交付最新的缺口或没有缺口).有时,填补空白的延迟比空白本身更是个问题。在观察特定库存的价格或工厂中化学过程的温度计时,你可能乐意跳过一些中间结果,以获得更及时的读数。尽管如此,为了避免因股价或温度的变化而发生时间倒退,人们还是希望保持秩序。

回到顶部

在发送信息时知道你不知道什么

当一个通信应用程序想要请求它的合作伙伴完成一些工作时,有几个阶段需要考虑:

  • 在发送请求之前.在这一点上,您非常确信工作还没有完成。
  • 在你发送之后但在你收到回复之前.这就是混淆的地方。你完全不知道对方做了什么。工作可能很快就会完成,可能已经完成,也可能永远不会完成。发送请求会增加您的困惑。
  • 在你收到答案之后.现在,你知道。要么成功,要么失败,但你不会那么困惑了。

在松散耦合的伙伴之间传递消息本质上是一种混乱和不确定的操作。对于应用程序程序员来说,理解消息传递中涉及的歧义是很重要的(参见图3).有趣的语义发生在应用程序与其盒子上的本地管道对话时。这是它所能看到的。

允许每个应用程序感到厌烦并放弃参与工作。让消息管道跟踪从接收到最后一条消息以来的时间是很有用的。通常,应用程序希望指定它只愿意等待一定的时间,直到放弃。如果管道能帮上忙,那就太好了。如果不是,应用程序将需要自己跟踪它需要的任何超时。

一些应用程序开发人员可能要求不要超时,并认为可以无限期地等待。我通常会建议他们将超时时间设置为30年。反过来,这会产生一个反应,我需要理性,而不是愚蠢。为什么30年是愚蠢的,而无限年是合理的?我还没有看到一个消息传递应用程序真正想要等待无限长的时间。

回到顶部

当你的水管工不了解你

没有什么比管道工更能减轻你的烦恼,让一切“正常工作”。如果那个水管工对你和你的需求有同样的理解,那就太好了。

许多应用程序只想在两个服务之间有一个多消息对话框,其中每个服务完成部分工作。我刚刚描述了在最好的情况下,当你和你的水管工分享清楚的概念:

  • 你该怎么跟你的水管说话。
  • 你在和另一边的人说话。
  • 事务如何处理您的数据(以及传入和传出消息)。
  • 无论是发送消息还是跳过消息来获取最新消息。
  • 当发送方知道消息已被传递,并且消息是不明确的时候。
  • 服务什么时候可能会放弃通信,伙伴如何知道它。
  • 如何测试超时。

这些挑战假设您已经遇到了理想的水管工,他为您的消息传递环境实现了很好的支持。很少有这么干净和简单的。相反,应用程序开发人员需要注意大量的问题。


在考虑底层消息传输的行为时,最好记住承诺了什么。每条消息都保证被传递0次或更多次!这是你可以指望的保证。


一些消息传递系统提供有保证的传递。这些系统(例如,MQ-Series)1通常将在基于磁盘的队列中记录消息,作为接收消息发送的一部分。消息的使用(以及从队列中删除消息)要么作为消息刺激的事务的一部分发生,要么仅在事务工作完成之后发生。在后一种情况下,如果出现故障,工作可能被处理两次。

当应用程序收到无法处理的消息时,经典的保证交付队列系统会面临一个挑战。有保证的传递意味着消息传递系统传递了消息,但是不能保证消息是格式良好的,或者即使是格式良好的,也不能保证汹涌的应用程序对消息做了合理的处理。在我妻子开始处理我们的家庭账单(这是我的责任)之前,把电费账单可靠地送到我们家与电力公司收钱的情况只有松散的联系。

在考虑底层消息传输的行为时,最好记住承诺了什么。每条消息都保证被传递0次或更多次!这是你可以指望的保证。有一个可爱的概率峰值表明大多数消息都是一次性传递的。

如果您不假定底层传输可能会删除或重复消息,那么您的应用程序中将存在潜在的错误。更有趣的问题是,在运输系统之上分层的管道系统能给您带来多大的帮助。如果通信应用程序运行在共享消息传递的公共抽象的管道之上,那么可能会有一些帮助。在大多数环境中,应用程序必须自己处理这个问题。

回到顶部

为什么TCP还不够?

TCP对统一我们执行数据通信的方式产生了重大影响。5它提供精确一次的按顺序字节传输在两个通信过程之间.一旦连接终止或其中一个进程完成或失败,它不提供任何保证。这意味着它只涵盖了在松散耦合的分布式系统中构建可靠应用程序的开发人员可见的一小部分。实际上,应用程序层位于TCP之上,必须重新解决许多相同的问题。

请求会丢失,因此几乎每个消息传递系统都会重新尝试传输。消息传递系统通常使用TCP,它有自己的机制来确保字节在进程之间的可靠传递。TCP的保证是真实的,但只适用于一个进程与另一个进程之间的通信。当长寿的参与者参与时,挑战就出现了。

例如,考虑HTTP Web请求。HTTP通常会关闭请求之间的TCP连接。当使用持久化HTTP连接时,TCP连接通常保持活动状态,但不能保证。这意味着任何在TCP之上使用HTTP的行为都可能导致HTTP请求的多次发送。由于这个原因,大多数HTTP请求都是幂等的。3.

在可伸缩的web服务世界中,我们不断地重新实现相同的滑动窗口协议2这在TCP中是如此普遍,在TCP中端点是运行的进程。任何一个进程的失败都意味着TCP连接的失败。在由一组服务器实现的长时间运行的消息传递环境中,端点的语义更加复杂。随着端点及其状态的表示的发展,(希望)由管道管理的消息传递异常也在发展。更有可能的是,它们由应用程序作为令人惊讶的bug的补丁逐步解决。无论如何,即使是应用程序开发人员也需要一本安德鲁·塔南鲍姆的经典著作,计算机网络,触手可及。2

回到顶部

定义幂等性

回顾一下,幂等性意味着对某些工作的多次调用与一次调用完全相同。

  • 扫地是幂等的。如果你多次清扫,你还是会得到一个干净的地板。
  • 提取10亿美元不是幂等的。结巴和重新尝试可能会很烦人。
  • 处理$1,000,000,000的取款'XYZ'(如果尚未处理)是幂等的。
  • 烤蛋糕不是幂等的。
  • 从购物清单开始烤蛋糕(如果你不在乎钱的话)是幂等的。
  • 阅读记录X是等幂的。即使值发生了变化,任何合法的值X在发出读和返回答案之间的窗口中,是正确的。

在计算机使用中幂等的定义是:“表现得好像只被使用过一次,即使被使用过多次。”虽然这是事实,但多次尝试往往会产生副作用。让我们考虑一些通常不被认为与语义相关的副作用:

  • .想象一个在处理请求期间使用堆的系统。您自然会期望堆在多个请求时变得更加碎片化。
  • 日志记录和监控.大多数服务器系统都维护日志,允许对系统进行分析和监视。重复的请求将影响日志的内容和监视统计信息。
  • 性能.重复的请求可能会消耗计算、网络和/或存储资源。这可能是对系统吞吐量的征税。

这些副作用与应用程序行为的语义无关,因此,即使存在副作用,对等幂请求的处理仍然被认为是等幂请求。

回到顶部

多消息交互的挑战

任何消息都可能多次到达,甚至在一个时间。可以将消息传递系统想象为包含一群马基雅维利式的小矮人,他们监视着您的消息,以便在您的应用程序最糟糕的时刻插入消息的副本(参见图4).在大多数松散耦合系统中,消息可能多次到达。此外,相关消息可能会被乱序发送。

解决这个问题的典型方法是使用请求-响应,然后确保处理的消息是幂等的。这样做的好处是,应用程序可以通过积极响应看到消息的传递。如果应用程序从预期的合作伙伴那里得到响应,那么它就确信消息实际上已经到达。由于多次重试,接收应用程序可能多次接收该消息。

当一个较长时间运行的工作涉及到多个消息时,这一挑战将进一步加剧。在下列情况下,必须考虑工作的持续时间和工作允许的故障:

  • 轻量级流程状态.有时,共享计算的任何一端的进程发生故障都可能导致整个工作流被放弃。如果是这种情况,那么中间状态可以保留在正在运行的进程中。
  • 长时间运行的持久状态.在这种情况下,即使其中一个合作伙伴系统崩溃并重新启动,长时间运行的工作也必须继续。对于可能持续数天或数周但仍然有意义的消息传递交互的业务流程来说,这是常见的。
  • 无状态应用程序服务器.许多体系结构将计算与状态分离。状态可以保存在单个服务器上的持久数据库中,可以跨一堆持久数据存储进行复制,也可以使用许多其他新的存储状态的方法。

在所有这些情况下,系统必须正确地将状态(这是来自早期消息的记忆的结果)与新消息组合在一起。消息可能以不同的顺序合并到状态中,而这些顺序可能受到一个或多个系统故障的影响。

许多系统使用负载均衡的服务器池实现目标应用程序。当传入的消息不假设以前的通信和以前的状态时,这种方法很有效。第一条消息被路由到其中一个服务器并得到处理(参见图5).当与service - foo通信时,可能有多台机器正在实现该服务。这给Service-Foo的实现带来了有趣的负担,并可能表现为通信伙伴可见的异常行为。

当第二条(或更晚的)消息到达并期望伴侣不要失忆时,挑战可能会出现。当你和多条信息聊天时,你会认为对方记得之前的信息。多消息对话框的整个概念就是基于此。

回到顶部

什么叫工作不是在办公场所完成的?

当服务A与服务B通话时,您不知道工作真正在哪里完成。服务A认为它正在与服务B一起工作,而服务B实际上可能将所有工作分包给服务C(参见图6).这本身不是一个问题,但它会放大服务a看到的失败可能性。

您不能假定所做的工作实际上是由与您聊天的系统完成的。您所知道的只是您有一个消息传递协议,如果一切正常,根据协议的定义,将从指定的合作伙伴返回适当的消息。你根本不知道幕布后面发生了什么。

只有当做这项工作的伙伴返回答案时,您才知道消息已经被传递了。跟踪中间路径点无助于知道工作是否会完成。知道联邦快递的包裹已经到达孟菲斯,并不会告诉你你的祖母会收到她的巧克力盒。

当消息传递传输向发送方发送确认(ACK)时,这意味着下一台计算机已经接收到该消息。它没有说明消息到目的地的实际交付,更没有说明应用程序可能对消息进行的任何处理。如果有一个中间应用程序将工作分包给另一个应用程序服务(参见图7).应用程序不能看到传输和管道确认,否则在重新配置目标服务时可能会引入错误。ACK告诉服务A的管道,服务B的管道拥有消息,但没有告诉服务A任何关于服务C收到消息的信息。服务A不能对ACK进行操作。

ACK表示再次发送消息将不起作用.如果发送应用程序知道了ACK并根据该知识采取行动,那么在实际工作无法实现时可能会导致错误。

回到顶部

管道可以掩盖合作关系的模糊性(但很少这样)

消息传递管道可能有一个形式化的长时间运行对话框的概念。管道必须定义并实现以下内容:

  • 状态.应用程序如何表示来自部分完成的对话框的状态信息?记住,要么状态必须在组件失败后仍然存在,要么对话框必须在失败后彻底失败——仅仅忘记早期的消息是糟糕的。
  • 路由.之后的消息如何找到能够定位状态并正确处理新消息的服务(和服务器),而不会忘记之前的消息?
  • 对话框的语义.对话框如何提供正确的语义,甚至当对话框的真正工作被分包给遥远的某人?这样做的一部分是适当地屏蔽传输问题(如ACKs),这些问题只有在应用程序看到时才会引起问题。

因此,消息传递语义似乎与命名、路由和状态管理密切相关。这不可避免地意味着,当数据库来自与消息传递系统不同的管道供应库时,应用程序设计人员将面临挑战。一个提供清晰对话框语义的系统是SQL Service Broker。4它通过在SQL数据库中保存消息传递和对话框状态来实现这一点。

回到顶部

与服务对话

服务被设计成黑箱。您知道服务地址,开始使用它,来回交谈,然后结束。事实证明,即使有一些管道为您提供对话框的支持,也会出现消息重复和消息丢失的问题。当以可伸缩的方式实现目标服务时,这些挑战会更加复杂。当然,这意味着您可能一开始就与一个尚未可伸缩的服务进行交互,而且随着它的发展,会出现某些新的障碍。

对话在其生命周期中经历三个阶段:

  • 初始化.消息从对话发起者发送到对话的目标。启动器不知道目标是作为单个服务器实现的还是作为负载平衡池实现的。
  • 建立了.消息可以以全双工方式双向流动。消息传递系统现在已经确保多个消息将落在负载平衡池中的同一服务器上,或者处理消息的服务器将准确地访问从以前的消息中记住的状态(并且行为就像它是同一台服务器一样)。
  • 关闭.发送最后一条消息(或向同一方向流动的消息)。

每一个交流阶段都有挑战,尤其是开始和结束阶段。

当一个对话框中的第一条消息被发送时,它可能需要重试,也可能不需要重试。在负载平衡的服务器环境中,两次重试可能落在不同的后端服务器上,并被独立处理。

从发送方的角度来看,应用程序将带有某个名称的消息扔到管道中,希望将其发送给所需的合作伙伴。在听到响应之前,您无法判断是收到了消息、消息丢失了、响应丢失了,还是工作仍在进行中。请求必须是幂等的图8).

发送到服务的第一个消息必须是幂等的,因为它可能会被重试以处理传输失败。后续消息可以依靠一些管道提供帮助(前提是应用程序运行在管道之上)。在第一个消息之后,管道可以充分了解消息的目的地(在可伸缩系统中),从而执行自动重复消除。在处理第一条消息期间,重试可能落在可伸缩服务的不同部分,然后不可能自动消除重复。

有时,服务器从一个对话框接收到第一个消息(或多个消息),然后将消息(或消息序列)的重试重新路由到负载平衡池中的另一个服务器。这可能以两种方式出错:

  • 消息传递协议的设计目的是将状态保存在目标服务器中,并使用池中服务器的更详细地址响应发起者,以用于后续消息。
  • 会话状态与负载平衡服务器保持分离,协议中的后续消息包括会话标识信息,允许无状态负载平衡器获取会话状态并继续进行协议中断的地方。

这两种方法同样受到重试的挑战。第一次尝试将与作为重试结果而发生的第二次尝试不相关。这意味着对话框协议中的启动消息一定是幂等的(见图9).

当启动服务向负载平衡服务发送一系列消息时,第一个消息必须是幂等的。考虑以下事件:

  1. 一个消息序列(1、2、3)被发送到服务Foo,负载均衡器选择Foo A。
  2. 消息的工作在Foo A执行。
  3. 返回的答案表明未来的消息应该以Foo A为解析名称。不幸的是,由于网络传输不稳定,回复丢失了。
  4. 发生对服务Foo的重试,消息被发送到服务器Foo N。
  5. 消息1、2和3的工作在服务器Foo N上执行。
  6. 响应被发送回发起者,发起者现在知道要继续与Foo N聊天。

无论如何,我们必须确保在Foo A执行的冗余工作不是一个问题。

回到顶部

准确地结束起始阶段

当从本地管道返回消息时,应用程序就知道它已经到达了特定的合作伙伴。如果其他服务已对对话框作出响应,则该对话框存在特定的服务器或绑定的会话状态。虽然应用程序可能无法直接看到到合作伙伴中特定资源的绑定,但在看到应用程序响应时,它们必须已经连接好。

在应用程序从其本地管道接收到合作伙伴应用程序的消息之前,所发送的任何消息都可能被重新路由到合作伙伴的一个新的(容易忘记的)实现。

只有在你听到另一边的应用程序的声音后,你才能退出对话的启动阶段图10).应用程序只能看到其管道所显示的内容。当一个应用程序开始向它的合作伙伴发送消息时,它具有初始阶段歧义,必须确保所有消息都具有等幂处理的语义。当本地管道从合作伙伴返回一条消息时,本地应用程序可以确信管道已经解决了初始阶段的模糊性,现在可以发送消息,而不必确保它们是幂等的。

回到顶部

保证第一个信息的幂等性

在第一个回答之前,你在一个对话框中说的每句话都可以被重试。如果早期的信息导致一些严重的(非幂等的)工作发生,这可能会导致大量的麻烦。应用程序开发人员应该做什么?

有三种方法可以确保你在初始化阶段没有bug:

  • 琐碎的工作.您可以简单地来回发送一条消息,它什么也不做,只是在可伸缩服务器中与特定的合作伙伴建立对话。这就是TCP对其SYN消息所做的事情。
  • 只读工作.初始化应用程序可以从可伸缩服务器读取一些内容。这不会留下一个混乱,如果有重试。
  • 等待工作.初始化应用程序可以发送一堆东西,而合作伙伴将积累这些东西。只有当后续的往返发生时(并且您知道哪个特定的伙伴和对话框的实际状态已经连接),累积的状态才会被永久应用。如果服务器累积状态并超时,则可以丢弃累积的状态,而不会引入bug。

使用这三种方法中的一种,应用程序开发人员(而不是管道)将确保没有bug等待对不同后端合作伙伴的重试。为了完全消除这种风险,管道可以使用TCP的技巧,发送一组简单的往返消息(TCP中的SYN消息),这将连接伙伴,而不打扰顶层的应用程序。另一方面,允许应用程序通过往返完成有用的工作(例如,读取一些数据)是很酷的。

回到顶部

最后阶段的模糊性

在任何互动中,不能保证从一个应用程序服务发送到另一个应用程序服务的最后一条消息.知道是否收到的唯一方法是发送消息说收到了。这意味着它不再是最后一条信息。

应用程序必须以某种方式处理这样一个事实,即向同一方向发送的最后一条或多条消息可能会在网络中消失。这可能是在简单的请求-响应中,也可能是在复杂的全双工颤振中。当最后的消息被发送时,它们是否真的被发送只是一个运气的问题图11).在两个伙伴之间的每次交互中,不能保证向同一方向发送的最后消息。它们可能会消失。

可以保证倒数第二个消息(通过在最终消息中接收通知)。最终的信息必须是尽最大努力。这种复杂性通常是设计应用程序协议的一部分。应用程序一定不会真正关心是否接收到最后一条消息,因为您无法真正判断是否接收到。

正如我们所看到的,大多数松散耦合系统都依赖于应用程序设计人员来考虑请求的重复处理。在服务的可伸缩实现中,这种复杂性更加严重。应用程序设计人员必须学会在日常生活中接受幂等性的现实。

回到顶部

结论

分布式系统可能会给发送消息的应用程序带来挑战。消息传递传输可能是彻头彻尾的恶作剧。消息的目标可能是由一组工蜂实现的伙伴的幻象。反过来,它们在协调您的工作状态时可能会遇到挑战。还有,系统你认为实际上,您正在讨论的可能是将工作分包给其他系统。这也会增加困惑。

有时,应用程序具有管道,可以捕获通信合作伙伴的模型、合作伙伴的生命周期、可伸缩性、故障管理以及通信应用程序组件之间良好的双方对话所需的所有问题。即使有强大的支持管道,消息传递仍然存在语义上的挑战。

本文概述了一些白发苍苍的老人们用来提供弹性的原则,即使“事情发生了”。在大多数情况下,当生产中出现罕见异常时,这些编程技术被用作应用程序的补丁。总的来说,它们不会经常被提及,也很少在测试中突然出现。它们通常发生在应用程序压力最大的时候(这可能是意识到您有问题的最昂贵的时间)。

一些基本原则是:

  • 每个消息都可以重试,因此必须是幂等的。
  • 消息可以重新排序。
  • 由于失败、管理不善的持久状态或负载平衡切换到其邪恶的孪生兄弟,您的合作伙伴可能会经历失忆。
  • 最后一条消息不可能保证送达。

记住这些原则可以使应用程序更加健壮。

虽然管道或平台可以从应用程序中删除这些问题,但这只有在通信的应用程序共享公共管道时才会发生。这种共同环境的出现并不是迫在眉睫的(也可能永远不会发生)。与此同时,开发人员在构建应用程序时需要考虑这些潜在的功能障碍。

回到顶部

致谢

感谢Erik Meijer和Jim Maurer的评论和编辑改进。

ACM队列的q戳相关文章
queue.acm.org

碱:一种酸的替代品
丹普里切特
http://queue.acm.org/detail.cfm?id=1394128

面向大型共享数据库的数据协同关系模型
Erik Meijer和Gavin Bierman
http://queue.acm.org/detail.cfm?id=1961297

可测试系统管理
马克·伯吉斯
http://queue.acm.org/detail.cfm?id=1937179

回到顶部

参考文献

1.IBM。WebSphere MQ;http://www-01.ibm.com/software/integration/wmq/

2.Tanenbaum,响亮的计算机网络4thPrentice Hall出版社,2002年出版。

3.万维网联盟,网络工作组,1999年。超文本传输协议http1.1;http://www.w3.org/Protocols/rfc2616/rfc2616.html

4.Wolter, R. SQL Server服务代理介绍(2005);http://msdn.microsoft.com/en-us/library/ms345108 (v = sql.90) . aspx

5.传输控制协议;http://www.ietf.org/rfc/rfc793.txt

回到顶部

作者

帕特Helland自1978年以来,一直在分布式系统、事务处理、数据库和类似领域工作。在20世纪80年代的大部分时间里,他是串联计算机公司TMF(事务监视设施)的首席架构师,该公司为不间断系统提供分布式事务。除了在亚马逊工作了两年,Helland从1994年到2011年在微软公司工作,在那里他是微软事务服务器和SQL服务代理的架构师。他还为Cosmos做出了贡献,这是一个分布式计算和存储系统,为必应提供后端支持。

回到顶部

数据

F1图1。应用程序与它们的本地管道通信。

F2图2。有时,管道将以事务方式将传入消息的消费与应用程序数据库中的更改和传出消息联系起来。

F3图3。有趣的语义发生在应用程序与其盒子上的本地管道对话时。

F4图4。在大多数松散耦合系统中,消息可能多次到达且顺序混乱。

F5图5。当与Service Foo通信时,多个机器可能实现该服务。

F6图6。服务行为取决于旁观者的看法。

F7图7。传输和管道确认不应该对应用程序可见。

F8图8。发送到服务的第一条消息必须是幂等的。

F9图9。发送到可伸缩服务的第一个消息必须是幂等的

F10图10。当应用程序收到合作伙伴的消息时,它就知道初始化阶段已经完成。

季图11。不能保证向同一方向发送的最后消息。

回到顶部


©2012 acm 0001-0782/12/0500 $10.00

允许为个人或课堂使用部分或全部作品制作数字或硬拷贝,但不得为盈利或商业利益而复制或分发,且副本在首页上附有本通知和完整的引用。除ACM外,本作品的其他组件的版权必须受到尊重。允许有信用的文摘。以其他方式复制、重新发布、在服务器上发布或重新分发到列表,都需要事先获得特定的许可和/或费用。请求发布的权限permissions@acm.org传真(212)869-0481。

数字图书馆是由计算机协会出版的。版权所有©2012 ACM, Inc.

Baidu
map