4

任何人都知道提供对一致性保证的精细控制的消息总线实现?Full ACID 太慢,没有 ACID 太错误。

我们目前正在使用 Rhino ESB 包装 MSMQ 进行消息传递。当对分布式事务使用持久的事务性消息传递时,MSMQ 可以在等待 I/O 完成时阻塞提交相当长的时间。

我们的消息分为两大类:业务逻辑和非规范化。后者占消息总线流量的很大比例。

业务逻辑消息需要完整的 ACID 保证,MSMQ 已证明足以满足此要求。

非规范化消息:

  1. 必须耐用。
  2. 在原始事务完成之前不得处理。
  3. 可以多次处理。
  4. 即使原始事务回滚,也可以处理,只要遵守 2)。

(在某些特定情况下,可能会放宽对持久性的要求,但将这些情况作为规则的例外来识别和处理会增加复杂性。)

所有非规范化消息都在进程中处理,因此不需要 IPC。

如果重新启动该过程,则可以假定所有事务都已完成(已提交或回滚),并且必须恢复所有尚未处理的非规范化消息。重播已经处理的非规范化消息是可以接受的。

据我所知,处理事务的消息传递系统倾向于在完整的 ACID 或没有之间提供选择,而 ACID 会带来性能损失。我们看到在某些情况下调用 TransactionScope#Commit() 需要几百毫秒,具体取决于发送的消息数量。

使用非事务性消息队列会导致消息在其原始事务完成之前被处理,从而导致一致性问题。

我们系统的另一部分具有相似的一致性要求但复杂性较低,它已经在使用类似于事务日志的自定义实现,并且对于这个用例进行概括肯定是一种选择,但我宁愿不实现低延迟,并发,持久,事务性消息传递系统,如果我不需要的话:P

如果有人想知道,需要非规范化消息的持久性的原因是检测不同步和修复不同步可能分别非常困难和极其昂贵。人们确实会注意到什么时候出现了轻微的错误并且页面刷新并不能解决它,所以忽略不同步不是一种选择。

4

3 回答 3

1

这并不完全是您正在寻找的答案,但 Jonathan Oliver 已经撰写了大量关于如何避免在消息传递中使用分布式事务并保持事务完整性的文章:

http://blog.jonathanoliver.com/2011/04/how-i-avoid-two-phase-commit/ http://blog.jonathanoliver.com/2011/03/removing-2pc-two-phase-commit/ http://blog.jonathanoliver.com/2010/04/idempotency-patterns/

不确定这是否对您有帮助,但是,嘿。

于 2013-08-30T13:50:50.253 回答
0

事实证明,MSMQ+SQL+DTC 甚至不能提供我们需要的一致性保证。我们之前遇到过一个问题,即在将消息排队的分布式事务提交到数据库之前处理消息,导致读取过期。这是使用 ReadCommitted 隔离来消耗队列的副作用,因为:

  1. 开始事务 A。
  2. 更新 A 中的数据库表。
  3. 在 A 中排队消息。
  4. 请求提交 A。
  5. 消息队列提交 A
  6. 开始事务 B。
  7. 阅读 B 中的消息。
  8. 读取 B 中的数据库表,使用 ReadCommitted <- 获取 pre-A 数据。
  9. 数据库提交 A.

我们的要求是 B 在 A 提交时读取表块,这需要可序列化事务,这会带来性能损失。

看起来正常的做法确实是实施必要的约束并保证自己,即使这听起来像是重新发明轮子。

有人对此有任何意见吗?

于 2013-09-03T09:25:12.560 回答
0

如果您想手动执行此操作,这是一种可靠的方法。它满足 (1) 和 (2),甚至不需要您在 (3) 和 (4) 中允许的自由。

  1. 生产者(业务逻辑)启动事务 A。
  2. 将任何内容插入/更新到一个或多个表中。
  3. 将相应的消息插入 PrivateMessageTable(域的一部分,如果您愿意,可以不共享)。这就是将要分发的内容。
  4. 提交事务 A. 生产者现在已经简单而可靠地执行其写入,包括插入消息,或回滚所有内容。
  5. 专用分发器作业从 PrivateMessageTable 查询一批未处理的消息。
  6. 分发器启动事务 B。
  7. 将未处理的消息标记为已处理,如果修改的行数与预期不同(两个实例同时运行?),则回滚。
  8. 将消息的公共表示插入 PublicMessageTable(以任何方式公开的表)。将新的、严格顺序的 Id 分配给公共表示。因为只有一个进程在执行这些插入,所以可以保证这一点。注意表必须在同一台主机上以避免2PC。
  9. 提交事务 B。Distributor 现在已将每条消息准确地分发到公共表一次,并具有严格连续的 Id。
  10. 一个消费者(可能有多个)从 PublicMessageTable 查询下一批 Id 大于其自己的 LastSeenId 的消息。
  11. 消费者开始事务 C。
  12. 消费者将自己的消息表示插入到自己的表 ConsumerMessageTable 中(从而推进 LastSeenId)。插入忽略可以帮助防止多个实例运行。请注意,此表可以位于完全不同的服务器中。
  13. 提交事务 C。消费者现在只消费了每条消息一次,按照消息公开可用的相同顺序,没有跳过任何消息。
  14. 我们可以根据消费的消息做任何我们想做的事情。

当然,这需要非常仔细的实施。

它甚至适用于数据库集群,只要只有一个写入节点,并且读取和写入都执行因果关系检查。很可能拥有其中一个就足够了,但我必须更仔细地考虑其含义才能提出这一主张。

于 2018-09-19T10:07:09.687 回答