内容


漫游 Web 服务原子事务操作

传统事务、数据恢复及映射到 Web 服务原子事务的初学者指南

Comments

然而,传统的事务处理频繁地使用非通用的方法来进行,并且使用特定方法进行互操作,如果新的服务原子事务从根本上是基于广泛公认的互操作标准(比如 XML 和 WSDL),那么使用 Web 服务机制会带来更好的灵活性和互操作性。

引言

大多数人知道事务对于任何交易来讲都是至关重要的。但很少有人知道事务处理在幕后是怎样工作的。在本文中,我们使用一个简单的实例来说明事务是怎样工作的。该实例阐述了一个常见的事务问题:遗失顾客的钱,并且为该问题提供了一个解决方案。首先使用事务处理的传统方式阐明问题,然后把旧的方式映射到新的基于 Web 服务的机制,该机制在灵活性和互操作性上具有新的优势。

不遗失钱是相当重要的。刚刚询问过 Waldo。Waldo 的情形代表了对事务的需求。Waldo 使用 ATM(或浏览器)从一个帐户将一些钱移动到另一个帐户。这些帐户可能位于同一金融机构的不同部门,或者可能位于不同机构中。

对于 Waldo 来讲,他的钱消失是绝对不可接受的。如果 Waldo 曾经怀疑过他的钱的安全性,那么他将可能更换金融机构。

Waldo 的钱是通过两个数据库中的数据来表示的,这两个数据库通过互相协作来保证它们包含的数据总是在一个已知的并且一致的状态。更确切的说,这两个数据库允许它们之间活动或任务在一个共同的活动或工作范围之内(参见 图 1)。或者说,单个事务可以操作两个数据库中的数据并且 一些机制将保证两个可能结果中仅有一个发生: 所有的更改都成功地完成或者完全 没有更改发生。

图 1. 共同的活动包含多种可恢复的动作
共同的活动包含多个具有 all-or-none 结果的动作

保证所有的动作具有常见结果的 机制是两个数据库和一些支持中间件所支持的协议。数据库使用的保持数据(如 Waldo 的余额)相等的协议被称为 两阶段提交,或简称为 2PC。本文实例使用了 2PC 的一个常见的变体 presumed abort,在该变体中,当成功的结果不在时默认的行为是回滚或撤销活动中的所有动作。

从编程的观点来看,有不同的方法可以用来指定多个动作应该在单个事务的范围内。一个独特清晰的用来指定事务行为的方法如 清单 1所示。该代码是逻辑运行在 Waldo 正使用的 ATM 之后某个地方的一小块--可能在某个相关金融结构的数据中心里。

很明显大部分清单 1 都没有包括。我们打算仅仅显示足够本文实例说明使两个活动(从一个帐户取出钱并且存到另一个帐户)一致的 2PC 协议的部分。

清单 1. Waldo 事务的伪代码
TransferCash(fromAcct, toAcct, amount)
    BeginTransaction
    fromAcct -= amount
    toAcct += amount
    CommitTransaction
Return

虽然另一个指定所需事务的方法是使用 J2EE 的容器管理事务(Container Managed Transactions,CMT),但是现在我们使用了清单 1 中的代码,因为它清楚且容易匹配 Waldo 的简单事务。使用 J2EE CMT 时,会自动包含 BeginTransaction 和 CommitTransaction 而不需要使用代码。

那么传统事务是什么?

理论上,传统事务仅仅是一组可恢复的动作,这些动作所保证的结果是要么所有的动作都发生,要么动作没有一个发生(见 图 1)。简单地,可恢复的动作是修改被保护数据的任何事情。例如,从 Waldo 某个帐户中取款(fromAcct -= amount)是可恢复的动作,该动作可以被一直倒退到事务的末端。

在 Waldo 案例中,他的事务由两个动作组成:从一个帐户取款和向另一个帐户存款。这两个动作都发生是允许的,如果这两个动作都不发生,那么这也是允许的。决不允许一个动作发生而另一个不发生,一个动作发生而另一个动作不发生会导致坏数据和 Waldo 的净值或者银行的资产消失或从无到有。因此,两个动作需要在具有单个结果的单个事务内:两个动作都发生(提交结果),或两个动作都不发生(回滚结果)。

假设没有错误发生, 清单 1中的代码显示的是想得到提交结果。代码可以简单地指定回滚而不是提交(当 Waldo 击中 ATM 上的 <Cancel>键时),这意味着撤销事务工作作用范围内(开始和结束之间)的所有的动作。事务监控器( transaction monitor)是一个帮助 清单 1 中的代码支持事务处理的底层中间件,如果程序遭受未经处理过的异常,事务监控器就会自动指定为回滚。作为事务监控器一部分的该自动回滚是一个确保数据不会被破坏的保护机制--例如,即使 ATM 应用程序出乎意料地失败,中间件会“彻底清理”并且保证结果。

对于该介绍性的论文,我们适当地忽略了由于灾难性的事件而阻碍事务的最后结果处理。真正严重的事件比规则的结果处理常见性少好几个数量级,这些严重的事件属于叫做直观推断(heuristics)的课题。直观推断超出了我们的范围,且可能在某个银行中包含人为的干涉。

典型事务的常规处理

现在,让我们看怎样使用 2PC 常用的变体(presumed abort)来影响 Waldo 事务并且使用可恢复的方法把钱从一个帐户移到另一个帐户。该举例说明的一个要点是无论何种错误发生,都能保持数据的完整性且 Waldo 继续是忠诚的顾客。

图 2在时间线上显示了 Waldo 的事务,包含了执行 清单 1 中所示逻辑所需的全部互相作用的组件。ATM 应用程序本身是上面的直线。接下来的两条线代表应用程序操作的帐户数据库。数据库是事务的参与者。紧接着的直线是事务协调器(coordinator),或配合 2PC 协议的中间件。

如果故障发生时的,事务的状态指示恢复处理。在最底部的有色直线及时显示不同点的 Waldo 事务的状态。

Database-1、Database-2、和 Coordinator 的直线同时表示时间(从左到右流逝)和一些已记录到恢复日志的重要记录。在恢复处理时,使用恢复日志保证数据的完整性,这一点稍后说明。

有关本例子的一些说明

恢复日志的安全是至关重要的。如果数据非常重要(Waldo 的存款非常重要),那么使用硬件特别地保护日志。恢复日志不丢失是关键,因此使用与数据的价值相当的冗余性和安全性(如安全访问、RAID 存储、物理上独立的冗余存储等等)来保护恢复日志。

大多数的最优化技术,检查点处理(聚集中间结果以便不需要读整个日志就可以恢复动作)和 “边缘案例” 超出了我们的范围。例如,我们很少关心打算把什么写到日志里。一些事情必须在处理继续之前写到日志里,而许多事情可以稍后写到日志里。在这两种重要的情况中(下面的步骤 12 和 13),我们指定强迫日志,这意味着在每向下执行一步之前,写日志发生。

在我们的实例中仅仅使用有关 2PC 的许多变体中的一个。数据库在数据(Undo 和 Do 记录)和状态信息映象之前和之后使用日志。我们已经进一步简化并跳过了 Do 记录,但是大型和高性能的数据库可能会用到它们,为此我们做了很多工作(在下面的步骤中用法会变得清晰)。

时间线上的 Waldo 事务

现在,让我们从头到尾走一下 Waldo 的事务。下面,当我们谈论 ATM 应用程序时,把它当作应用程序本身或一些支持应用程序的中间件。例如,当说到应用程序开始了一个事务工作范围时,可能是中间件代表应用程序开始了事务工作范围。

图 2:幕后的 Waldo ATM 事务
 Waldo 的事务按部就班的示意图
Waldo 的事务按部就班的示意图

这里的叙述帮助解释图 2 所示的已编号的步骤(应用程序伪代码在 清单 1中给出):

  1. ATM 应用程序显示事务工作范围的开始。协调器记住该事务时可能在日志中写东西者也可能不在日志中写东西--如稍后的恢复规则所示,在使用 presumed abort(本实例使用的)过程中,此时不需要在日志上强调任何东西。协调器为该事务创建一个上下文,或唯一标识符及与之相关的一些其它信息。更重要地,该事务上下文流回到应用程序。上下文和其它交互作用在应用程序和资源管理器之间流动,是上下文把整个集合的动作粘合在一起形成一个事务活动。
  2. 应用程序从 Database-1 取出钱。把上下文(来自步骤 1)插入到这个流中。
  3. Database-1 不但查看动作的请求,而且查看事务上下文。Database-1 使用该上下文联系事务协调器及在该事务或活动中登记有关信息(以便协调器稍后通过 2PC 处理以帮助 Database-1 保证提交或回滚所有动作的结果)。协调器要记住 Database-1 是事务的参与者。
  4. Database-1 根据请求修改可恢复的数据。它把记录写入恢复日志,增加事务状态信息。一个记录描述稍后决定是提交时数据库所做的更改(Do Record),另一个记录描述决定是回滚时数据库所做的更改(Undo Record)。

    • 在该情况下,Undo 记录表达 make Waldo's balance = X ,Do 记录表达 make Waldo's balance = X-$ 。X 是该事务开始前的余额的数量,$ 是转移的数量。注意我们仅仅着眼于恢复日志而不是数据库文件。
    • 当应用程序请求数据库而不是推迟时,即使 DBMS 更新了数据库文件,Do 记录也不是严格需要的。但是,推迟写数据在性能和并发上具有优势。另外,Do records 可以被用于审计或其它高级的动机。因为它们这么有用,所以我们的数据库使用了它们。
    • 稍后就会清楚,实际上数据库也不需要马上把 Do、Undo、和状态记录写入它的日志,尽管可以这么做。它可能一点也不写任何东西!因为我们在协议中没有数据库保证能提交或回滚(参阅步骤 12)这一点,所以数据库可以不执行任何写人操作。就例子来讲,我们假设按照时间线写入数据(参见 图 2)并且在某处(如隔离--其它程序都不能修改同一数据)数据库对其数据保持锁定。
  5. 回到应用程序。
  6. 与步骤 2 类似,应用程序请求操作另一个数据库 Database-2。应用程序想要在帐户上增加从 Database-1 中取出来的钱的数量.
  7. 与该事务有关的 Database-2 的记录,协调器按照 Database-1 的方法处理。协调器要记住 Database-2 是事务的参与者。
  8. Database-2 把 Undo 和 Do 记录及状态写入它的恢复日志,再次像 Database-1 做的一样。
  9. 返回应用程序。
  10. 应用程序选择提交事务。现在协调器接管。当接受到 Commit,2PC 的阶段 1 开始。
  11. 在阶段 1,协调器沿着所有与该事务有关的参与者(在本实例中是 Database-1 和 Database-2)的列表向下,请求每个参与者准备。准备意味着准备好接受提交或回滚的命令。
  12. Database-1 和 Database-2 两个都作出 Prepared 响应,这意味着两个数据库都已准备好接收最后结果(提交或回滚所有已做的更改),并且支持最后结果。
    • 此时两个数据库必须已经提交一些内容(至少在它们的日志里),因为作出 Prepared响应意味着数据库保证能提交或回滚。此时动作仅仅是 试探性的。
    • 如果任一数据库有一些已准备的故障类型(不同的场景),那么数据库响应 Rollback而不是 Prepared,并且协调器把 Rollback广播给所有的参与者。
  13. 协调器强制一条日志记录显示 Transition to Phase 2(T02)。这有时被称作为 atomic instant
    • 一旦该记录在我们知道的日志中被强调并且已经记录:
      • 所有的参与者已准备好执行任一路径(提交或回滚)。
      • 事务的最后结果是已知的(在本文实例中是提交),并且,
      • 结果受恢复处理保证(稍后解释)。
    • 如果由于某种原因该记录不能把这些写入日志,最后结果会是 Rollback(在本文实例中使用了 presumed abort)。恢复处理会强制执行该结果。
  14. 协调器通知每个参与者决定是提交更改。然后参与者做它们需要做的任何事情,如把结果写为真正的数据库数据或进行一些其它优化。
  15. 参与者携带 Committed返回协调器。一旦协调器确信所有的参与者携带 Committed承认提交命令,协调器会忘记该任务:所有的参与者承认事务后就结束了。
  16. 返回到应用程序。
  17. 在某处,因为协调器通过确认共同的结果得到在 2PC 流中所有的参与者都已成功,所以它在日志中写入一个 end指示器,但是这仅仅是一种优化。(因为检查点超出了我们的范围)。

图 2说明了幕后运行的许多事情。接下来我们就会解释在本文实例中可恢复的资源管理器(参与者)如何从故障中恢复和保证数据完整性。

跨越故障的数据恢复

恢复处理其实不是两阶段提交的一部分。不过更确切地说,2PC 启用恢复。也就是说,因为所有的资源管理器使用 2PC,它们可以执行这里所描述的动作并且保证数据的完整性。通过确认所有的可恢复的动作或者向前或者后退来在数据库之间维持数据的完整性。

图 3是图 2 的一个副本,并在图 2 的基础上插入了 3 条垂直的红色直线。这些红色垂直的直线代表故障。假设可能出现的最坏的故障不是持久的:所有失败的事情都是由于严重的、大范围的功能性故障造成的。小的故障大部分仅仅是大故障的子集(例如,应用程序失败、数据库失败和协调器失败)。故障发生后,如 图 3所示,数据库和协调器最后可能会重新启动并使用恢复日志和恢复规则来确保数据完整性。

我们实际上不需要过多关心 ATM 应用程序本身。如果应用程序单独地失败了,那么它下面的中间件会驱动回滚(假设应用程序在发出 Commit 之前终止)。如果应用程序能继续向下运行并且恢复到以前,就可以幸运地忽略故障发生前正在做的事情,因为中间件和数据库保证了数据的完整性。这使得应用程序本身更容易写入和维护。

数据恢复使用基于事务状态的规则,其中事务状态是由协调器和参与者记录的。这里给出们的恢复规则(在本文实例中,使用带有 presumed abort 和我们特有的优化的 2PC)。故障发生后:

  • 如果协调器不了解事务(换句话说,是在协调器日志上有记录之前),那么恢复规则是撤销(Undo)所有的动作(我们的数据库使用它们的 Undo 记录)。
  • 如果协调器了解事务,但是事务状态是 ActiveIn-Doubt,那么规则是撤销所有的动作(我们的数据库再次使用它们的 Undo 记录)。
  • 如果协调器了解事务且事务状态是 In Commit,那么规则是加固所有的动作(我们的数据库使用它们的 Do 记录--其它 DBMS 可能已经把数据直接写入它们的数据库媒体且不需要 Do 日志)。同样,如果状态是 In Rollback,那么规则是撤销所有的动作(我们的数据库再次使用它们的 Undo 记录)。
图 3:故障发生后维护数据完整性
交互流和故障点
交互流和故障点

故障 1

  • Database-1 查看它的恢复日志,发现有不完全的事务。Database-1 联系协调器(关于如何进行联系,这在用于该事务的恢复日志的状态信息中编码表示——或者是已知的)。但是,协调器没有把记录写入它的日志并且告诉 Database-1 它没有发现该事务。因此使用恢复 Rule A 和应用它的 Undo 记录,来从 Database-1 观点重新保持数据的一致性。Database-1 现在可以忘记该事务。
  • Database-2 在它的日志中没有发现任何关于该事务的信息,所以它不需要做什么。
  • 协调器在它自己的日志中也没有发现内容,所以也不做什么。

当 Waldo 返回到 ATM 时,他所有的帐户都处于开始事务之前的同一状态。当一个严重的功能性故障在 ATM 和他转移钱的两个银行发生时,他认为事务在处理。当他发现自己的余额一点都没变时,才会放心。

故障 2

  • Database-1 查看它的恢复日志,发现有不完全的事务。Database-1 联系协调器。协调器告诉 Database-1 它没有发现该事务。Database-1 使用恢复 Rule A 并且应用它的 Undo 记录。一旦完成,Database-1 可以忘记该事务。
  • Database-2 与 Database-1 做同样的操作,应用它的日志文件,忘记该事务。
  • 协调器没有登录任何有关该事务的日志,所以不需要做什么。

如故障 1,当返回到 ATM 时,他的帐户处于他开始事务(严重的故障破坏了该事务)之前的同一状态。

故障 3

  • 假设故障 3 发生时(如在 图 3 中所示),已向 Database-1 和 Database-2 发送了 Commit,但是它们可能还没有登录与 Commit 命令相关的日志,所以当开始时它们不知道是否完成了事务。
    • 如果由于数据库发现影响日志文件的信息,它知道自己已提交了事务,那么它可以忘记该事务。
    • 如果数据库只能推出它做好了准备,那么它需要尝试重新连接协调器,重新得到结果(事务可能是 In-CommitIn-Rollback,但是数据库不确定)。一旦数据库从协调器那里得到结果(在日志中被固定为 T02),数据库将会应用 Rule C 并且使用它的 Do 或 Undo 记录。
  • 因为在故障前 End记录没有加入到协调器的日志中,所以协调器知道它还没有告诉 Database-1 和 Database-2 结果。所以协调器可能尝试连接两个数据库参与者,两个数据库会响应它们已经完成或需要结果(换一种方法,协调器可以什么都不做,等待被请求)。一旦协调器知道已经给所有的参与着发送了结果并且它们完成了,协调器会忘记事务。

当 Waldo 在故障发生后再次返回 ATM,他的钱又在一个已知状态,但是这次事务被成功地完成了(钱从一个帐户取出并且放入到了另一个帐户)。Waldo 对他的金融机构提供的服务印象深刻,该金融机构甚至在严重的故障中成功地完成了他的事务。

记住该实例仅仅使用了一种带有一些特有的优化程序的 2PC--实际上遗漏了一些事情(例如,其中的一个数据库何时可以忘记该事务?)。在任何真实系统中,运行了更多的优化程序和许多启发式处理(heuristic processing)。

有用的事务协议需要以某种方式隐藏故障的所有恢复案例,或者在协议中或者在参与者中,并且协议可以启用。该实例仅仅阐明了故障的少数几种案例。

从传统事务到 Web 服务原子事务的映射

在图 23中,我们没有提到 Database-1 联系协调器的方法,也没有指定应用程序访问数据库的方法。实际上,我们不指定一种事务与其它事务联系的机制。在过去,大部分是不通用的机制,这些机制有时只能在实体(应用程序、资源管理器和协调器或事务监控器)的特定联合之间工作。

Web 服务、Web 服务调整(WS-C)和 Web 服务原子事务(WS-AT)的结合映射了 图 2所示的所有流并且指定了接受相同结果的精确的通信机制。然而作为仅仅能在特定联合之间工作的替代,基于流的 Web 服务几乎可以对任何东西起作用。

图 4:修正的 Waldo 事务
新近的事务流图
新近的事务流图

图 4中,按照如下步骤把传统的流(图 23)转化成 Web 服务。重要更改的步骤已被标号并且在下面描述。像以前一样,当我们说到 应用程序时,把它理解为应用程序或一些助手中间件。同样地,当提到 数据库时,它可能是实际的数据库或一些助手中间件。

  1. 应用程序使用在 Web 服务协调中定义的活动服务(Activation Service)获取事务上下文。
  2. 应用程序激活受 Database-1 影响的 Web 服务(或受与 Database-1 交互的应用程序影响的)从 Waldo 的余额中减去钱。不被应用程序所知的是,上下文随着 Web 服务调用流动。
  3. 数据库使用上下文中的信息调用定义在 Web 服务协调中的注册服务(Registration Service)来注册该事务的信息。
  4. 应用程序调用受 Database-2 影响的 Web 服务把钱增加到 Waldo 的余额中。如步骤 2,上下文随着 Web 服务调用流动。
  5. 正如步骤 3,Database-2 使用上下文中的信息调用注册服务并且登记与该事务相关的信息。
  6. 应用程序使用定义在 Web 服务原子事务的完成协议(Completion Protocol)来指出它希望提交事务。例如,当 CMT 的应用程序成功地完成时,使用容器管理事务的 J2EE 会使用完成协议。
  7. 数据库和协调器参与在 Web 服务原子事务 2PC 协议(WS-AtomicTransaction 2PC Protocol)中定义的 2PC 流程(2PC 流)。

图 4中可以清楚地得到使用 Web 服务的原子事务(WS-C 和 WS-AT)和不使用 Web 服务的原子事务(例如图 23)大体上是相同的。主要的差别是外表的装饰并且包括实体相互之间的通信方法,而不是它们通信内容的本质。尽管如此,实体通信方法的差异对灵活性和互操作性有很大的影响。

使用 Web 服务可以达到普遍的互操作性,因为作为更改资源管理器 X 来与事务监控器 Y 交互的替代,可以使用 Web 服务同时更改 X 和 Y,并同时与其它的资源管理器和事务监控器交互。因此取代一次两个(two-at-a-time)的互操作性,或仅仅在特定领域内的互操作性,多种方式(n-way)的互操作性成为可能。

在有关各方之间,使用 Web 服务的恢复处理和在 Web 服务之前的也是一样的。只有资源管理器知道它们的资源及提交或回滚它们的方法。

例如,假设故障 1( 图 3)发生,而且在转换到 Web 服务之后。Database-1 恢复到 Web 服务之前,Database-1 读取它的日志,发现需要联系协调器。有关联系协调器方法的信息保存在恢复日志中;使用 Web 服务,会有一个结束点参考(EPR--在 WS-Addressing 中定义)。Database-1 按 EPR 联系协调器,该 EPR 带有定义在 Web 服务原子事务中的称作 Replay的消息。Replay 引起协调器把最后的协议消息重发给 Database-1,这使得 Database-1 推断事务状态--然后应用合适的恢复规则。

隔离和其它事务选项

数据完整性取决于一组作为原子的从一个定义明确的状态变化到另一个定义明确的状态的动作。我们的例子显示了 Waldo 事务,该事务具有两个属于普通的 all-or-nothing 活动的动作:从一个帐户中取款和把这笔款存放到另一个帐户。如果此时 Waldo 通过使用镇上的 ATM 进入该场景并尝试从某个相同的帐户中取款,怎么办?

如果 Waldo 夫人想从由 Database-1 控制的帐户中取款( 图 2中步骤 4 和 16 之间),她尝试的事务可能失败或可能必须等待 Waldo 的事务完成。原因是 Waldo 正在操作 Database-1 中相同的帐户数据. 该帐户的数据库记录是锁定的(取决于数据库管理系统,可能锁定多个余额)。

在这种情况下,锁定数据库记录有优点也有缺点。优点是因为必须保护数据的完整性,所以在允许其它事务操作相同数据之前,代表 Waldo 事务的动作需要完成(换句话讲,被隔离)。另一方面,保证数据完整性的恢复步骤可能会更复杂。尽管如此,锁定数据库也可能是不好的事情,因为,打个比方,Waldo 夫人不能存取款。

支持长期的锁定可以减少数据库支持的并发数量。虽然缺乏并发使 Waldo 夫人不方便,但是在其它使用例子中,影响会更大。

在两人共有的银行帐户上,封锁 Waldo 夫人多长时间?假设在 图 3的步骤 4 和 16 之间发生故障。在那个范围内的某处, Database-1 记录可能被锁定不确定的时间。在本例中,可以观察到对 Database-1 记录的访问取决于 Database-2 的可用性(因为在 Database-2 被锁期间,步骤 4 和 16 之间有许多 Database-2 处理)。

Waldo 夫人正意外地展示 2PC 的缺点:需要数据库锁来保证隔离,但这样可能对数据访问(和并发)有负面影响。这说明一个原因,2PC 被通常认为比较适合大多数受控环境,在受控环境中希望参与者(如 Database-1 和 Database-2)的行为依照相关严格的策略。

虽然在保持事务语义(和最终的数据完整性)时放松隔离会具有更好的灵活性,但是 2PC 不适合这样做。在其它事物之间,不严格隔离属于不同的 Web 服务事务规范,这种事务规范针对大多数宽松的受控环境:Web 服务业务活动(WS-BA)。

WS-BA 超出了本文的范围,但是可能将来某篇文章的主题。

复杂性

大多数人可能不会直接使用定义在 Web 服务协调和 Web 服务原子事务中的各种功能。相比之下,中间件组成了该处理事务(和更简单的)。尽管如此,知道幕后怎样工作是有好处的,因为这会对更有效地使用设备有帮助。并且可能因为你不知道某天需要编写一个资源管理器。

结束语

我们着眼于传统的事务处理和通过在动作集合上强制实行 all-or-none 语义来在许多动作间保持数据完整性的方法。使用 Web 服务的事务处理从逻辑上与传统的事务处理相似。从传统的到 Web 服务事务有相对直接的映射,我们映射 Web 服务原子事务的 Durable 2PC 变体作为例子。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=23143
ArticleTitle=漫游 Web 服务原子事务操作
publish-date=09012004