【问题标题】:Outbox pattern - Message Relay without duplicates and unordering for any SQL and NoSQL DB发件箱模式 - 任何 SQL 和 NoSQL DB 的无重复和无序消息中继
【发布时间】:2021-04-08 18:22:21
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • Kafka 的排序保证比 Confluent 声称的要弱得多(和/或在实践中不太适用)。
  • 对于您的特定场景,在我看来,使用数据库进行分布式锁的最佳方式,因为您已经依赖它。 Postgres 有咨询锁的概念。想象一下,您有 n 个 ServiceA 的副本,在每个副本中,您都有一个后台作业,它试图在无限循环中获取锁,如果锁被占用,这个副本将成为主副本并开始处理事务中的消息,如果它被提交或回滚或服务崩溃,锁被释放,另一个副本可以快速成为主副本。

标签: duplicates microservices distributed-transactions 2phase-commit outbox-pattern


【解决方案1】:

很难实现 Exactly-once 交付保证,而不是事务发件箱模式的至少一次交付。

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

消息必须包含

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

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

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

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

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

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

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

由于explained by Martin Klappmann 没有隔离的分布式锁被破坏,并且只能最大限度地减少多个领导者(短期)在领导者选举中的机会。

【讨论】:

    猜你喜欢
    • 2019-10-25
    • 1970-01-01
    • 2012-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多