13

在我们的组织中,我们有一个 SQL Server 2005 数据库和相当数量的数据库客户端:网站(php、zope、asp.net)、富客户端(legacy fox pro)。现在我们需要将核心数据库中的某些事件传递给其他系统(MongoDb、LDAP 等)。消息传递范式似乎非常有能力解决这类问题。所以我们决定使用 RabbitMQ 代理作为中间件。

最初从数据库消费事件的问题似乎只有两种可能的解决方案:

  1. 轮询数据库以获取传出消息并将它们传递给消息代理。
  2. 在某些表上使用触发器将消息传递到同一台机器上的代理。

由于涉及定期执行 sql 时出现的延迟问题,我不喜欢第一个想法。

但是基于事件的触发方法有一个问题,目前我似乎无法解决。考虑这种情况:

  1. 将一行插入到表中。
  2. 触发器触发并发送消息(使用 C# 编写的 CLR 存储过程)

除非写数据的事务被回滚,否则一切正常。在这种情况下,数据将是一致的,但消息已经发送并且无法回滚,因为触发器在写入数据库日志时触发,而不是在事务提交时触发(这是 RDBMS 的正确行为) .

我现在意识到我对触发器的要求太多了,它们不适合处理数据以外的任务。

所以我的问题是:

  1. 有没有人设法使用触发器提取数据事件?
  2. 您还能建议哪些其他使用数据事件的方法?
  3. 查询通知(建立在 Service Broker 之上)是否适合我的情况?

提前致谢!

4

2 回答 2

12

以免首先从等式中剔除明显的不合适之处:查询通知不适用于此技术,因为它旨在解决相对稳定数据的缓存失效问题。使用 QN,您将只知道该表已更改,但您无法知道发生了什么更改。

感谢您弄清楚为什么触发器调用 SQLCRL 不起作用:回滚时一致性被破坏。

那么有什么作用呢?考虑一下:BizTalk Server。换句话说,围绕这个问题空间建立了一个完整的业务,并且解决方案远非微不足道(否则没有人会购买这样的产品)。

尽管遵循一些原则,你可以走得很远:

  • 解耦。基于事件的触发器可以,但不要从触发器发送消息。除了回滚的一致性问题之外,您还存在每个 DML 操作现在等待外部 API 调用(RabbitMQ 发送)的延迟问题和外部 API 调用失败的可用性问题(如果 RabbitMQ 不可用,则您的数据库不可用)。解决方案是让触发器使用普通表作为队列,触发器将在本地数据库队列中加入一条消息(即插入到该表中),并且外部进程将通过使消息出队来服务该队列(即从表)并将它们转发到 RabbitMQ。这将事务与 RabbitMQ 操作解耦(外部进程能够看到仅当原始 xact 提交时才发送消息),但代价是一些明显增加的延迟(涉及额外的跃点,本地表充当队列)。
  • 幂等性。由于 RabbitMQ 无法使用数据库注册分布式事务,因此您无法保证 DB 操作(从本地表中出列充当队列)和 RabbitMQ 操作(发送)的原子性。当另一个失败时,任何一个都可以成功,并且如果没有明确的分布式事务注册支持,根本无法绕过它。这意味着应用程序每隔一段时间发送一次重复的消息(通常是当事情由于某种原因已经变坏时)。快速提醒一下:加入显式“确认”消息并发送序列号的行为是一场失败的战斗,因为您很快就会发现您正在重新发明 TCP 在消息传递之上,这条道路铺满了主体。
  • 宽容。出于与上述项目相同的原因,有时您认为发送的消息永远不会发送。同样,这造成的损害完全是业务特定的。问题不在于如何防止这种情况(几乎不可能......),而是如何检测这种情况以及如何处理。恐怕没有灵丹妙药。

您在传递 Service Broker 时确实提到了(支持查询通知的事实是它最不感兴趣的方面......)。作为内置于 SQL Server 中的消息传递平台,它提供 Exactly Once In Order 交付保证并且完全交易,它将解决上述所有痛点(您可以SEND从触发器中不受惩罚,您可以使用Activation为了解决延迟问题,您将永远不会看到重复或丢失的消息,有明确的错误语义)和我之前没有提到的其他一些痛点(备份/恢复的一致性,因为数据和消息是相同的存储单元 - 数据库、HA/DR 故障转移的一致性,因为 SSB 支持数据库镜像和集群等)。缺点是 SSB 只能与另一个 SSB 服务通信,换句话说,它只能用于在两个(或多个)SQL Server 实例之间交换消息。任何其他用途都需要双方使用 SQL Server 来交换消息。但是,如果您的端点都是 SQL Server,那么请考虑使用 Service Broker 进行一些大规模部署。请注意,像 php 或 asp.net 这样的端点可以被认为是 SQL Server 端点,它们只是 DB API 之上的编程层,例如,不同的端点需要将消息从手持设备(电话)直接发送到数据库(而且 99% 的时间都过去了)通过 Web 服务,这意味着它们最终可以访问 SQL Server)。另一个考虑因素是 SSB 面向吞吐量和可靠交付,而不是低延迟。例如,绝对不是用于在 HTTP Web 请求中取回响应的技术。是一种用于提交处理由 Web 请求触发的内容的技术。

于 2012-10-26T13:13:59.407 回答
2

Remus 的回答列出了一些用于生成和处理事件的合理原则。您可以从触发器启动事件推送以实现低延迟。

您可以通过触发器实现一切所需。我们仍将其解耦为两个组件:生成事件的触发器和读取事件的本地读取器。

第一个组件是触发器。

  • 制作一个 CLR 触发器,准备在事务提交时需要执行的操作。
  • 创建一个System.Transactions.IEnlistmentNotification始终同意准备好的,并且其void Commit(System.Transactions.Enlistment)方法执行准备好的操作。
  • 在触发器中,调用System.Transactions.Transaction.Current.EnlistVolatile(enlistmentNotification, System.Transactions.EnlistmentOptions.None)

您将希望您的操作简短而甜蜜,例如将数据附加到内存中的无锁队列或更新内存中的某些其他状态。不要尝试与其他机器或进程通信。不要写入磁盘(如果您想写入磁盘,只需制作一个插入到队列表中的普通触发器)。您需要小心确保您的程序集只加载一次,以便任何共享的静态状态都是唯一的;如果您的静态处于未被其他程序集引用的顶级程序集中,这是最容易做到的,因此没有其他程序集会尝试加载它。

您还需要

  • 以这样的方式初始化您的状态,即使系统重新启动而没有发送所有先前排队的消息,它也是正确的(因为内存中的短队列不会持久)。这意味着您可能正在重新发送消息,因此它们需要是幂等的。或者
  • 依靠另一个组件的容忍度来接收丢失的消息

第二个组件读取由触发器更新的状态。制作一个单独的 CLR 组件,该组件从您的队列或状态中读取数据,并执行您需要完成的任何操作(例如将幂等消息发送到消息传递系统,记录它已发送,等等)。如果这个组件可能失败(提示:它可以),您将需要某种形式的容差,它可能属于另一个系统。当新状态可用时,您可以通过将触发信号作为第二个组件来实现低延迟。

一种架构可能性是让触发器在提交时将事件放入内存中,以便另一个低延迟组件接收并让第二个组件发送幂等消息的低延迟、低可靠性副本。您可以将其与更可靠或更持久的消息传递系统(例如 SSB)配对,该系统将可靠且持久地发送相同的幂等消息,但延迟更大。

于 2014-03-05T01:44:46.717 回答