我正在尝试评估EventStore作为服务器软件内部的可靠排队机制。
MSMQ作为替代方案失败,因为它不支持部分排序,即消息的"对话"中的有序消息。而且由于它的4MB消息大小限制(可以通过部分排序来克服)。SQL Service Broker确实支持部分排序,但是以编程方式进行设置和管理是一个麻烦。
关于EventStore的文档确实很少,有EventStore经验的人可以帮助解决以下问题吗?
- EventStore是否支持事件的事务性处理?如果处理失败,是否可以回滚退出队列?
- 在不同的线程、进程或机器中有多个读取器,EventStore是否强制每个事件只分配给一个事件读取器(一次,可能在事务期间)
- 假设以上是可能的,不同"对话"上的事件是否可以以任意顺序同时读取,而同一对话中的消息则可以同时读取按顺序单独阅读? 我读到EventStore基本上是"至少一次"的交付。它是可能的,使用某些存储提供程序,以确保"只执行一次"交货吗?
- 如何处理"中毒"事件?处理过程中出错的事件。也许错误本质上是暂时的,可以重试。也许它本质上是永恒的需要行政干预。 如果有必要,EventStore存储可以手工操作吗?它能在其他读者继续阅读的时候完成?
(我读到存储引擎上的事务不是必需的,但我仍然使用事务语言来表示在EventStore级别取代事务的任何东西。如果在从事务转换到其他功能的过程中有重要的功能后果,请对此发表评论。我不需要马上理解每一个方面,只需要希望有更多的时间来试验)
虽然EventStore可能被用来构建一个完整的队列,但它的设计从来没有考虑到这一点。这意味着在构建库的过程中会有很多固执己见的决定,这些决定与你的问题所提出的要求背道而驰。
例如,消息传递系统并不真正支持精确一次传递的概念。上面提到的其他事情,比如有毒消息,并不是一个真正的问题,因为EventStore并没有以这种方式连接到消息管道中。
你试图解决的问题似乎不是EventStore可以帮助你的。因此,我建议评估一个成熟的消息队列,比如RabbitMQ。
还有,是什么让你的消息大于4MB?如果您正在推送文件或大型二进制流,为什么不将它们推送到某种高可用性的"全局"存储(如Amazon S3),然后在消息上有一个指向它们的指针呢?
一些想法,尽管你似乎对第一个答案很满意:
-
偏序是在跟踪消息的因果关系时发生的情况。有很多方法可以做到这一点。naïve的方法是简单地保留一个给定消息所见过的分布式网络中所有节点的列表,并在发送消息时将其添加到该列表中。我说的是消息,但我真正指的是那次对话中的消息。
现在,这可能在简单的系统中工作得很好,但是当您开始得到更复杂的系统时,您可能需要一个Saga来跟踪消息的确切含义。
尽管如此,您可能需要在单个接收方节点上进行部分排序——然后,在消息流方面处于中心辐射状模式对您没有帮助。然后,也许您实际上需要在正在使用的传输中添加一些逻辑。
一种算法称为向量时钟,另一种类似的算法称为版本向量。下面是Go中矢量时钟的一个示例实现——如果你想花几个小时结对,我正在为f#开发一个小小的矢量时钟库——因为算法实际上非常简单。如果你真的想读一些有意义的东西,我推荐这本书——分布式系统的元素。第2-3、5章很好。
然后,您在分布式系统中获得对话的部分排序。使用队列无法实现这一点的原因是,在队列和节点之间存在网络鸿沟,如果与队列相同节点上的节点或进程在传输消息时出现故障,则该消息将被重新排队和排序。如果你想要获取信息也是一样。您可以通过在队列和消费客户端上使用2PC来解决这个重新排序问题,或者您可以根据它们的矢量时钟对消息进行排序,或者您可以根据发布/发送应用程序中给出的序列id对它们进行排序,或者您可以根据某些数据对它们进行排序,因为从消费者的角度来看,它具有语义意义。
-
至于其他需求,如有毒消息,您应该查看服务总线提供了什么。我个人使用MassTransit,它能很好地处理有毒信息。以下是使用消息时的一些故障模式:
- 序列化错误-你犯了一个编程错误。修正你的开发过程,因为这些不应该发生。如果它们仍然发生,只需将它们移到有毒队列-这些消息可能已损坏。从你的代码未处理异常-你犯了一个编程错误。同样,您的开发过程需要进行检查。除非你抛出它,让你的服务总线运行时将消息移动到有毒队列。
- 您可能会遇到写入本地消费者数据库的问题——这是一个操作问题,不应该在代码中发生。终止进程,因为您现在实际上无法从代码中执行任何操作。Naigos或其他进程监视器应该告诉运维人员,某些东西出了问题,需要紧急修复——因为如果你不能向读模型写入数据,那么读模型可能无法为它提供服务。Puppet或其他进程监视器可能会重启你的进程一段时间后,然后你可以通过相同的步骤,假设一切正常,但这一次,不要开始消耗你的队列,直到你有一个连接到数据库(这是NHibernate在它启动时做的静态初始化,例如)-并实现一个重试策略,如断路器在重试逻辑之上。
-
大型事件-确保您的队列API块太长字节数组。ZeroMQ有多部分消息。AMQP/RabbitMQ没有,所以你必须自己分组,当然,迫使你再次重新排序。或者你可以像我们其他人一样,在某个地方给一个二进制位块一个句柄,你可以从中读取它。