服务交易
服务有时需要参与交易。这样做是为了确保正确执行业务逻辑,或者其中一项服务失败的情况下完全取消业务逻辑。让我们看一个经典的例子:
银行应用程序需要将钱从一个帐户转移到另一个帐户。当前,银行系统具有两项服务:
- 存款服务
- 提款服务
为了将钱从一个帐户转移到另一个帐户,银行应用程序需要调用提款服务和存款服务。例如,首先从帐户A提取$ 100,然后将$ 100存入帐户B。
如果由于某些原因两个服务调用之一失败,则银行系统将最终处于不一致状态。如果银行系统首先呼叫提款服务,而存款服务呼叫失败,则将提取$ 100,而不会将它们存入任何地方。网络空间损失了100美元。另一方面,如果首先调用了存款服务,然后提款服务失败,则将存入$ 100,而不会从任何帐户中提款。 $ 100是凭空创造的。
解决此问题的方法是将提款和存款服务调用作为一个单独的原子操作执行。这就是交易的目的。
交易作为原子行为
事务将一个或者多个动作组合在一起,并确保它们像只是一个动作一样被执行。如果其中一项操作失败,则所有操作都会失败。只有所有动作都成功,动作结果才会"提交"(永久存储)到系统。
两阶段提交事务
参与事务的服务需要进行协调,以确保所有或者不被调用的服务在该事务中"提交"它们的动作。协调此类分布式事务的一种流行方法是"两阶段提交"协议。
两阶段提交协议包括三个步骤:
- 开始交易
- 准备阶段
- 提交阶段
首先,告诉所有参与者参加交易。从这一点开始,每个参与者引用此交易执行的所有操作都必须作为一个单独的操作(全部或者全部)执行。这些动作尚不能提交到主系统,但必须保留在参与者(服务)内部,直到指示参与者执行动作为止。
其次,一旦所有参与者成功执行了所有动作,就命令所有参与者(例如服务)进入"准备阶段"。一旦参与者成功地处于准备阶段,参与者必须保证在事务内执行的所有操作都准备就绪。如果参与者成功进入准备阶段后失败,则一旦参与者再次正常运行,它必须能够执行操作。换句话说,即使参与者崩溃/重启,事务内部执行的动作也必须能够幸免。这通常是通过将事务日志写入持久介质(例如硬盘)来完成的。
如果其中一位参与者无法进入准备阶段,则将命令所有参与者回滚(中止)交易。
如果所有参与者都成功进入准备阶段,那么现在将命令所有参与者执行其操作。提交后,这些动作将在系统中可见并执行。
两阶段提交弱点
两阶段提交协议不是100%安全的。想象一下,如果服务在进入准备阶段后崩溃了。现在,所有服务都需要提交。想象一下,失败的服务是链中要提交的最后一项服务。但是提交顺序失败,因为该服务已崩溃。此事务中的所有其他服务都已经提交了它们的操作,但最后一个未提交。
通常,一旦服务再次运行,该服务就必须提交它作为事务的一部分。但是,如果该服务永不恢复,那又如何呢?它的动作尚未提交,那么系统的状态是什么?
交易协调
当多个服务要参与交易时,某些实体必须协调交易。换句话说,必须说出事务何时开始以及何时进入准备和提交阶段。该实体(事务协调器)可以是:
- 应用程序
- 企业服务总线
- 单独的交易管理器/服务
在本文前面的示例中,应用程序协调了事务。
每笔交易一项服务
在面向服务的体系结构中简化事务管理的一种方法是设计服务,以使每个事务都包含在单个服务中。例如,在本文开头的示例中,汇款将被实现为单独的服务,而不是涉及两个服务的交易。