1

当我们需要更改 2 个系统中的数据时,双重写入是一个问题:数据库(SQL 或 NoSQL)和 Apache Kafka(例如)。必须更新数据库并可靠/原子地发布消息。最终的一致性是可以接受的,但不一致是不能接受的。

没有 2 阶段提交 (2PC) 双重写入会导致不一致。

但在大多数情况下,2PC 不是一个选项。

Transactional Outbox是一种微服务架构模式,其中一个单独的消息中继进程将插入数据库的事件发布到消息代理。

交易发件箱

并行运行的多个消息中继进程会导致发布重复(2 个进程读取 OUTBOX 表中的相同记录)或无序(如果每个进程只读取 OUTBOX 表的一部分)。

单个消息中继进程也可能多次发布消息。消息中继可能会在处理 OUTBOX 记录之后但在记录它已经这样做的事实之前崩溃。当消息中继重新启动时,它将再次发布相同的消息。

如何在事务性发件箱模式中实现消息中继,以便将重复消息或无序的风险降至最低,并且该概念适用于所有 SQL 和 NoSQL 数据库?

4

1 回答 1

3

几乎无法实现 Exactly-once 交付保证,而不是 Transactional Outbox 模式的 at-least-once。

由消息中继发布的消息的消费者必须是幂等的并过滤重复和无序的消息。

消息必须包括

  • 实体的当前状态(而不是仅更改的字段,即更改事件,“delta”),
  • ID 标头或字段,
  • 版本标头或字段。

ID 标头/字段可用于检测重复项(确定消息已被处理)。

版本标头/字段可用于确定消息的更新版本已经被处理(如果消费者收到 msg_a: v1, v2, v4 那么它必须在 msg_a 到达时丢弃 v3 的 msg_a,因为更新的版本为 v4 msg_a 已被处理)。

当所有现有 Pod 在新 Pod 之前被杀死时,消息中继提取到单独的微服务中并在单个副本中运行(Kubernetes 中的 .spec.replicas=1)并使用重新创建部署策略(Kubernetes 中的 .spec.strategy.type=Recreate)进行更新创建(而不是 RollingUpdate 部署策略)无助于解决重复问题。消息中继可能会在处理 OUTBOX 记录之后但在记录它已经这样做的事实之前崩溃。当消息中继重新启动时,它将再次发布相同的消息。

拥有多个主动-主动消息中继实例可以实现更高的可用性,但会增加发布重复和无序的可能性。

对于消息中继的快速故障转移主备集群,可以基于

  • 使用边车 k8s.io/client-go/tools/leaderelection 的 Kubernetes 领导选举
  • Redis 分布式锁(Redlock)
  • SQL锁使用SELECT ... FOR UPDATE NOWAIT
  • 等等

正如Martin Klappmann 所解释的那样,没有围栏的分布式锁被破坏,并且只会最大限度地减少多个领导者(短时间内)在领导者选举中的机会。

分布式锁损坏

于 2021-05-04T09:35:28.247 回答