了解两阶段提交分布式事务的工作原理
当客户在网店购买商品时,您可能希望订单被快速处理和交付。作为银行客户,您希望确保自己的资金在转账期间没有神秘消失。在企业应用程序中,传统事务可以确保事务质量,比如一致性,以及与其他事务隔离。分布式事务可跨多种资源提供这些保证,比如跨商店数据库和企业资源规划 (ERP) 系统。事务质量通常由中间件基础架构提供,而且可简化普通程序员的工作。
但是,在云中,中间件通常无法确保这些质量;分布式事务通常不可用,与此同时,基础架构可能更加脆弱。甚至细微的错误也有可能在应用程序中迅速扩大,并导致相当大的支持成本,因此,在可扩展的云应用程序中避免或适当处理可能的事务管理错误至关重要。
这个系列分两部分,在第 1 部分中,我将介绍事务处理的一些背景知识,事务提供的质量保证,还将介绍云设置中的不同之处。在第 2 部分中,我将介绍调用非事务服务的传统事务处理的替代方案。
“事务的一个主要属性是原子性。在真实的分布式事务中,可能无法完全实现原子性,因此一致性可能也会遭到破坏。”
事务的一般属性
在 IT 领域,事务通常被视为对信息的单独操作,该操作必须作为一个完整的单元,要么完全成功,要么完全失败。事务绝不能部分完成。这是一个重要属性。例如,财务操作依靠事务来确保总金额不变:如果从帐户中取钱,您需要确保资金同时被放入另一个帐户。
更正式地讲,事务通常与“ACID” 4 个属性有关:
- 原子性 (Atomicity)— 事务只能完全成功或完全失败。如果事务成功了,则执行事务中的所有操作;如果事务失败了,则不执行任何操作。
- 一致性 (Consistency)— 事务必须将系统从一种一致状态过渡到另一种一致状态。例如,写入一个数据库的任何数据(在提交时)必须遵守该数据库中定义的所有约束条件。
- 隔离 (Isolation)— 多个并发事务相互隔离,它们的行为就好像它们是按顺序执行一样。
- 耐久性 (Durability)— 事务一旦提交,事务结果就会一直存在,甚至在断电或出现其他错误时也是如此。
尽管这些是事务在理论上的原则,但实际上,出于性能考虑,一些操作和优化措施可能偏离了这些原则。
例如,尽管原子性、一致性和耐久性是事务处理系统中的重要目标,但由于严格隔离对性能的潜在影响,对隔离的要求通常会更宽松。可以定义不同事务隔离级别来确定系统中允许出现哪些隔离失败。这些隔离级别包含在单个数据库中,所以这里不会重点介绍该主题。
事务通常由一个事务客户端 调用资源管理器 来发起,该资源管理器通常是一个数据库或消息队列。资源管理器负责管理资源,比如数据库中的数据库表或行。客户端打开一个事务,然后可通过多次调用在系统中的一个或多个资源上执行一些操作,然后尝试提交事务(参见图 1)。如果事务中只有一个资源管理器,它可以自行确定事务是否具有正确的原子性和一致性,并在提交时确保耐久性。
图 1. 包含客户端、资源管理器和资源的典型事务
自动提交
上述讨论使用 start() 和 commit() 操作对事务进行了明确划分,所以可将一个资源上的多个操作分组到一个原子更改中。在事务期间,会锁定受影响的资源,无法对其执行其他更改。然后根据其他操作的结果(可能还会根据其他资源)来决定是执行提交还是回滚操作。
事务客户端需要在某个资源上执行一项操作时,不需要多次调用资源管理器。单个资源上的单次更改可以自动提交— 也就是说,更改会立即生效,如图 2 所示。
图 2. 显式提交允许等待其他操作的结果,但会锁定资源
下面将会介绍,在用于多个资源中的更改的错误处理模式中,决定对单个资源使用自动提交、显式提交还是回滚也有一定的影响。
长期与短期事务
上述事务属性适合一般意义上的事务。例如,甚至对于通过信用卡购买商品这样的业务事务,您也希望具有这些属性。但是,一些事务可能花费很长的时间。例如,在登记入住某家酒店时,酒店通常以电子方式询问您的帐户是否有足够的资金(也就是说,对卡上的资金有限制)。但是,可能在几天后您退房时才会从该帐户扣费。这些事务被称为长期 事务 — 这些业务事务通常分为多个技术事务,以避免在事务和资源管理器中阻塞(技术)资源。
请注意,资源管理器通常无法跨多个技术事务来锁定资源。长期业务事务要能锁定资源,通常需要调整功能数据模型来存储资源的这一额外状态。
例如,当客户登记入住酒店时,一个短期技术性事务会在帐户中预扣一定的金额。预扣状态可存储在数据库中的单独一行中,而且在预扣时,可以确认总的预扣金额不会超出客户的信用限额。在退房时,另一个短期技术事务通过实际转账来交换该预扣金额。在退房时,另一个短期技术事务通过实际转账来交换该保留金额。每个技术事务可能(在上面提到的隔离限制内)具有 ACID 属性,但业务事务没有。保留资金没有与其他事务隔离,因为其他业务事务不能保留它。
可运用特殊技术来确保长期业务事务中的原子性和一致性。例如,信用卡事务的保留资金基本上采用了一种悲观锁定形式。本文后面将讨论如何在云设置中使用这些技术。
(有关此主题的更多讨论,请参阅 SYS-CON Media 网站上的 “Web 服务事务”。)
本地与分布式事务
本地事务仅涉及一个资源管理器。但是在一次事务需要两个或更多资源管理器时,会发生什么?在需要管理两个数据库中的数据,或者需要根据数据库中的某个状态来发送消息队列中的消息时,可能出现这种情况。在这些情况下,仅仅是先提交第一个资源管理器而后提交第二个资源管理器将不起作用。如果第二个资源管理器由于违背约束条件而回滚,而且第一个资源管理器包含在已提交的事务中,您该如何回滚它?
常用且已得到非常成功地使用的方法是,使用一个事务管理器 来协调事务中涉及的资源管理器。然后,事务管理器使用一个分布式事务协议(通常采用两阶段提交)来提交事务。
在两阶段提交中,事务管理器首先要求所有参与的资源管理器确保事务在提交后成功完成。这是投票阶段。如果所有资源管理器都同意,然后在提交阶段 将事务提交到所有资源管理器。哪怕一个资源管理器拒绝提交,所有资源管理器都会被回滚,以确保一致性。图 3 给出了一个两阶段提交事务。
图 3. 一个使用两阶段提交协议的分布式事务
此模型已成功用在基于主机的事务处理、Enterprise Java (JEE) 应用服务器、Microsoft Transaction Server 和其他系统中。(请参阅 “Enterprise JavaBeans (EJB) 与 Microsoft Transaction Server (MTS) 模型的详细比较”。)
请注意,分布式事务并不仅限于应用服务器,还可以将它用于 Web 服务。有一项针对 Web 服务的 WS-Transaction 标准,但我尚未看到它被实际使用。
现实生活中的分布式事务
事务的一个主要属性是原子性。在真实的分布式事务中,可能无法完全实现原子性,因此一致性可能也会遭到破坏。考虑一个消息队列与一个数据库之间的两阶段提交事务。将一个状态写入数据库中,并将一条相关消息发送到队列。考虑到原子性和一致性,我们可能期望一旦从队列收到消息,数据库状态将会与它保持一致。这是强一致性 模型,可在传统事务中得到保证。
但是,由于从事务管理器向两个资源管理器的提交是先后发送的,所以第一个资源中的更改可能先于第二个资源中的更改被看到。例如,一个作为第二个资源的数据库可能花更长时间来执行实际提交,而且提交到第一个资源管理器的消息可能已被收到。处理会失败,因为尚未看到数据库状态。
这种情况不会经常发生,但偶尔会发生,我也亲自体验过。(请参阅 “WebLogic 上的两阶段提交争用条件” 和 “消息/数据库争用条件”。)因此,甚至在传统分布式事务中,也有一个不一致性窗口,事务仅在最终 达到一致。参见图 4。
图 4. 甚至在采用两阶段提交时也使用了不一致性窗口
此外,两阶段提交协议要求执行完两个阶段,才会释放所有被阻塞的资源,比如数据库记录。为了确保事务的完整性 — 例如,如果事务管理器停止或重新启动 — 事务管理器自身会编写自己的事务日志,这些日志在它停止时也会保留下来。事务管理器启动时会读取此日志,并完成未完成的事务。因此,要支持事务管理器中的两阶段提交事务,需要一个持久性事务日志(通常被写入文件系统中)。
两阶段提交事务需要对提交操作执行多次远程调用。由于认识到 有分布式事务开销,通常会避免执行这些调用,甚至在传统 JEE 设置中也是如此。但是,在需要确保跨资源管理器的一致性时,可以使用它们。
事务异步(消息)协议
大力投资分布式事务的一种具体情况是,在将面向消息的中间件与一个数据库事务耦合时。确保仅在数据库事务成功时从队列中获取消息,这是一项非常好的功能,可减少应用程序中的错误处理工作。确保仅在数据库事务可成功提交时发送消息,这也有助于在数据库与消息队列之间实现一致性。
在这种设置中,分布式事务仅限于 “本地” 数据库和消息服务器,不包含其他资源管理器。消息系统然后在发送和接收应用服务器之间保证事务属性的一致性。此方法是最终一致的,因为接收资源仅在发送资源中的更改提交后才会更改。
图 5. 消息基础架构可提供事务最终一致性保证。
使用面向消息的中间件,可以创建一个能确保事务质量的事件驱动架构,而且可以将大量错误处理工作委托给事务管理器。
结束语
在第 1 部分中,我们探讨了事务的基础知识和分布式事务。由于事务的属性,它们可以简化错误处理,不仅包括单一资源中的错误,还包括资源之间的错误。我展示了两阶段提交分布式事务的工作原理,只有时间间隔非常小时,它们才具有 “最终一致性”。您还了解到,分布式事务要求事务管理器中存在持久性事务日志。
在许多云部署中(例如 CloudFoundry),没有持久性文件系统。因此,JEE 应用服务器运行时无法写入它自己的事务管理器事务日志,因为没有两阶段提交事务。另外,在一些新的运行时环境中,比如 JavaScript 服务器,运行时甚至完全没有事务管理器。(请参阅 “面向 Java 的微服务最佳实践”。)
第 2 部分将介绍分布式事务的替代方案,它们仍提供了一定的事务保证。使用这些替代方案,即使在云应用程序中也可以保证事务质量,确保网店中的订单得到正确处理。