为什么两阶段提交不适合微服务架构?
Why is 2-phase commit not suitable for a microservices architecture?
我读过 post 说:
We can not implement traditional transaction system like 2 phase commit in micro-services
in a distributed environment.
我完全同意这一点。
不过如果有人能解释一下具体原因就好了。
如果我使用微服务实施两阶段提交,我将面临哪些问题?
提前致谢
微服务的整体思想是松散耦合和独立的服务。因为 2pc 意味着我们有 2 个阶段来提交事务。控制节点将驱动事务,所有其他节点首先响应它们准备就绪,在第二阶段它们都根据第一阶段提交或回滚。
如果控制节点关闭会怎样?当任何其他节点关闭时会发生什么?由于此限制,您的整个交易无法通过。在分布式事务中,您的节点可以位于不同的数据中心或区域。响应最慢的节点将使其他节点保持等待状态,同时它们可以继续前进。所以原子性阻碍了性能。
您无法扩展系统,整个点服务应该是独立的,可扩展性丢失了。
2pc 不是错误的答案,但在大多数情况下我们考虑的是最终一致性。如果您的系统需要很强的一致性,那么 2pc 可能是一个选择。
避免两阶段提交的主要原因是,事务协调器是一种独裁者,因为它告诉所有其他节点该做什么。通常事务协调器嵌入在应用服务器中。当在第一阶段或准备阶段之后事务协调器或应用程序服务器出现故障时,就会出现问题。现在,参与节点不知道该怎么办。他们无法提交,因为他们不知道其他人是否已经用 "no" 回复了协调员,并且他们无法回滚,因为其他人可能已经对协调员说了 "yes"。因此,直到协调员在 15 分钟(比方说)后回来并完成第二阶段,参与的数据存储将保持锁定状态。这 抑制了可扩展性和性能 。当协调器的事务日志在第一阶段后损坏时,会发生更糟糕的事情。在这种情况下,数据存储将永远处于锁定状态。即使重新启动进程也无济于事。唯一的解决办法是手动检查数据以确保一致性,然后解除锁定。这些事情通常发生在高压情况下,因此这绝对是一个巨大的 运营开销 。因此,传统的两阶段提交不是一个好的解决方案。
不过,这里需要注意的是,像Kafka这样的一些现代系统也实现了两阶段提交。但这与传统的解决方案不同,这里每个broker都可以是一个协调者,因此Kafka的leader选举算法和复制模型缓解了传统模型中提到的问题。
一些注意事项并提供一些背景信息:
- 在大多数情况下,微服务通过 HTTP(一种无状态协议)进行交互,因此全局/XA 事务不适用/不可能。
- Exactly once 语义是不可能的,你应该选择“至少一次”。这意味着所有服务都应该是幂等的。
- 为什么在这样的设置中不可能实现“恰好一次”语义的一个很好的例子是 http 连接在返回客户端的途中经常丢失。这意味着通过 POST 服务器的状态已更改,而客户端收到超时错误。
- 在微服务的边界内,您可以很好地使用它们。正如您提到的 Kafka,您可以很容易地消费(从 1 个主题)和产生(到 1 个或多个主题)单个原子/全有或全无操作(恰好一次语义)。
- 但是,如果您希望通过 http 交互的微服务之间的全局和长 运行 事务是唯一实用的选择(如果您 google,您可能会看到通过 http 进行全局事务,但对于生产系统忽略它们),就是为了最终的一致性而设计。简而言之,这意味着永远重试可恢复的错误(这本身就是一整章)并公开补偿端点或产生最终将修正不可恢复错误的补偿事件。查看 sagas pattern. Narayana Transaction Manager 具有良好的 Sagas 支持和良好的产品比较。
- 查看提供 XA 事务替代方案的相关微服务模式(您可能将其视为全局事务或 2 阶段提交/2PC),例如 Transactional Outbox or Event Sourcing 提供良好的“至少一次语义”。
- 分布式系统非常复杂,您应该有理由采用这样的解决方案。如果你去分布式,你的单体可以安全地委托给你的事务管理器的操作,将必须由开发人员/架构师处理:-)。
- 此外,大多数非 SQL 数据库/系统根本不支持 XA 事务(即全局事务),因为它们会显着降低处理速度。
这里的"We can not"其实就是"It's a bad idea, and I don't want to, and if I admit the possibility then I might not be able to convince you not to insist".
当然你可以跨微服务实现两阶段提交,但是:
- 两阶段提交需要在可以参与事务的每个服务中进行大量开发工作,
- 随着服务器之间的通信延迟,它会导致客户端之间的大量争用;和
- 所有涉及的服务都必须就许多协议、配置、部署和其他细节达成一致,这些细节决定了两阶段提交的实际工作方式。
这些问题很难在具有专用网络的位于同一位置的服务器上的一些紧密耦合的服务中进行管理。在更加异构的环境中,有更多的服务器和更高的延迟,微服务部署的特点是,它变得非常非常困难。
我认为并不是我们不能为微服务实现 XA 或 2PC,而是基于 HTTP 的 API 世界在政治上还没有被接受。在较旧的应用程序中,组件可能代表一组更大的复杂业务逻辑步骤,但也代表跨度、硬件、地理、组织和技术。即我的业务组件可能分布在多个公司中,每个公司都有不同的用户界面。集成所有这些支持的分布式事务 (2PC) 以及传播用户身份等的网络协议
这有官僚作风和遗留问题。多个可互操作平台支持的标准化分布式事务可以追溯到 80 年代。 IT 在很大程度上受时尚驱动,如果您过分提倡此类功能,您的工作场所会发生什么。
注意:关于如果控制节点宕机会发生什么的问题。在传统应用程序中,如果提交应用程序的组件在提交过程中死亡,事务最终会超时并在每个组件上回滚。在某些情况下,可以使用可恢复的事务功能。如果提交者在超时之前恢复,它将恢复事务并继续提交。我们在企业应用程序中经常看到这种情况,如果应用程序服务器反弹,它会恢复进程中的工作。
虽然我心情不好 :) - 一些权威人士声称 XA 必须在单一平台上实现,例如 JTA。我从来没有发现这是真的,XA 一直为我无缝地跨数据库、应用程序服务器和大型机工作)
我将详细阐述 Vassilis 的第一点“在大多数情况下,微服务通过 HTTP(一种无状态协议)进行交互,因此全局/XA 事务不适用/不可能。”作为在微服务领域不使用 2-PC 的最正当理由。
微服务架构带来了 application/code 级别分离,每个微服务都在其单独的进程和自己的数据库中运行(每个服务模式都有一个数据库)。每个微服务负责自己的本地事务。仅当所有参与服务都在单个服务器进程内 运行 但连接到不同(分片)数据库或消息传递中间件等事务性资源时,才有可能使用 2-PC。
我读过 post 说:
We can not implement traditional transaction system like 2 phase commit in micro-services in a distributed environment.
我完全同意这一点。
不过如果有人能解释一下具体原因就好了。 如果我使用微服务实施两阶段提交,我将面临哪些问题?
提前致谢
微服务的整体思想是松散耦合和独立的服务。因为 2pc 意味着我们有 2 个阶段来提交事务。控制节点将驱动事务,所有其他节点首先响应它们准备就绪,在第二阶段它们都根据第一阶段提交或回滚。
如果控制节点关闭会怎样?当任何其他节点关闭时会发生什么?由于此限制,您的整个交易无法通过。在分布式事务中,您的节点可以位于不同的数据中心或区域。响应最慢的节点将使其他节点保持等待状态,同时它们可以继续前进。所以原子性阻碍了性能。
您无法扩展系统,整个点服务应该是独立的,可扩展性丢失了。
2pc 不是错误的答案,但在大多数情况下我们考虑的是最终一致性。如果您的系统需要很强的一致性,那么 2pc 可能是一个选择。
避免两阶段提交的主要原因是,事务协调器是一种独裁者,因为它告诉所有其他节点该做什么。通常事务协调器嵌入在应用服务器中。当在第一阶段或准备阶段之后事务协调器或应用程序服务器出现故障时,就会出现问题。现在,参与节点不知道该怎么办。他们无法提交,因为他们不知道其他人是否已经用 "no" 回复了协调员,并且他们无法回滚,因为其他人可能已经对协调员说了 "yes"。因此,直到协调员在 15 分钟(比方说)后回来并完成第二阶段,参与的数据存储将保持锁定状态。这 抑制了可扩展性和性能 。当协调器的事务日志在第一阶段后损坏时,会发生更糟糕的事情。在这种情况下,数据存储将永远处于锁定状态。即使重新启动进程也无济于事。唯一的解决办法是手动检查数据以确保一致性,然后解除锁定。这些事情通常发生在高压情况下,因此这绝对是一个巨大的 运营开销 。因此,传统的两阶段提交不是一个好的解决方案。
不过,这里需要注意的是,像Kafka这样的一些现代系统也实现了两阶段提交。但这与传统的解决方案不同,这里每个broker都可以是一个协调者,因此Kafka的leader选举算法和复制模型缓解了传统模型中提到的问题。
一些注意事项并提供一些背景信息:
- 在大多数情况下,微服务通过 HTTP(一种无状态协议)进行交互,因此全局/XA 事务不适用/不可能。
- Exactly once 语义是不可能的,你应该选择“至少一次”。这意味着所有服务都应该是幂等的。
- 为什么在这样的设置中不可能实现“恰好一次”语义的一个很好的例子是 http 连接在返回客户端的途中经常丢失。这意味着通过 POST 服务器的状态已更改,而客户端收到超时错误。
- 在微服务的边界内,您可以很好地使用它们。正如您提到的 Kafka,您可以很容易地消费(从 1 个主题)和产生(到 1 个或多个主题)单个原子/全有或全无操作(恰好一次语义)。
- 但是,如果您希望通过 http 交互的微服务之间的全局和长 运行 事务是唯一实用的选择(如果您 google,您可能会看到通过 http 进行全局事务,但对于生产系统忽略它们),就是为了最终的一致性而设计。简而言之,这意味着永远重试可恢复的错误(这本身就是一整章)并公开补偿端点或产生最终将修正不可恢复错误的补偿事件。查看 sagas pattern. Narayana Transaction Manager 具有良好的 Sagas 支持和良好的产品比较。
- 查看提供 XA 事务替代方案的相关微服务模式(您可能将其视为全局事务或 2 阶段提交/2PC),例如 Transactional Outbox or Event Sourcing 提供良好的“至少一次语义”。
- 分布式系统非常复杂,您应该有理由采用这样的解决方案。如果你去分布式,你的单体可以安全地委托给你的事务管理器的操作,将必须由开发人员/架构师处理:-)。
- 此外,大多数非 SQL 数据库/系统根本不支持 XA 事务(即全局事务),因为它们会显着降低处理速度。
这里的"We can not"其实就是"It's a bad idea, and I don't want to, and if I admit the possibility then I might not be able to convince you not to insist".
当然你可以跨微服务实现两阶段提交,但是:
- 两阶段提交需要在可以参与事务的每个服务中进行大量开发工作,
- 随着服务器之间的通信延迟,它会导致客户端之间的大量争用;和
- 所有涉及的服务都必须就许多协议、配置、部署和其他细节达成一致,这些细节决定了两阶段提交的实际工作方式。
这些问题很难在具有专用网络的位于同一位置的服务器上的一些紧密耦合的服务中进行管理。在更加异构的环境中,有更多的服务器和更高的延迟,微服务部署的特点是,它变得非常非常困难。
我认为并不是我们不能为微服务实现 XA 或 2PC,而是基于 HTTP 的 API 世界在政治上还没有被接受。在较旧的应用程序中,组件可能代表一组更大的复杂业务逻辑步骤,但也代表跨度、硬件、地理、组织和技术。即我的业务组件可能分布在多个公司中,每个公司都有不同的用户界面。集成所有这些支持的分布式事务 (2PC) 以及传播用户身份等的网络协议
这有官僚作风和遗留问题。多个可互操作平台支持的标准化分布式事务可以追溯到 80 年代。 IT 在很大程度上受时尚驱动,如果您过分提倡此类功能,您的工作场所会发生什么。
注意:关于如果控制节点宕机会发生什么的问题。在传统应用程序中,如果提交应用程序的组件在提交过程中死亡,事务最终会超时并在每个组件上回滚。在某些情况下,可以使用可恢复的事务功能。如果提交者在超时之前恢复,它将恢复事务并继续提交。我们在企业应用程序中经常看到这种情况,如果应用程序服务器反弹,它会恢复进程中的工作。
虽然我心情不好 :) - 一些权威人士声称 XA 必须在单一平台上实现,例如 JTA。我从来没有发现这是真的,XA 一直为我无缝地跨数据库、应用程序服务器和大型机工作)
我将详细阐述 Vassilis 的第一点“在大多数情况下,微服务通过 HTTP(一种无状态协议)进行交互,因此全局/XA 事务不适用/不可能。”作为在微服务领域不使用 2-PC 的最正当理由。
微服务架构带来了 application/code 级别分离,每个微服务都在其单独的进程和自己的数据库中运行(每个服务模式都有一个数据库)。每个微服务负责自己的本地事务。仅当所有参与服务都在单个服务器进程内 运行 但连接到不同(分片)数据库或消息传递中间件等事务性资源时,才有可能使用 2-PC。