NEventStore 3.0 - 吞吐量/性能

10

我一直在尝试将JOliver的事件存储(Event Store)3.0作为项目中的潜在组件,同时一直在试图测量事件在Event Store中的吞吐量。

我开始使用了一个简单的工具,基本上是通过for循环迭代创建新的流,并向MSSQL2K8 R2数据库提交非常简单的事件,包括GUID ID和一个字符串属性。调度程序本质上是一个无操作的过程。

这种方法在HP G6 DL380 8核处理器的机器上以及DB位于独立的32核G7 DL580机器上,成功达到了大约每秒3K次操作(operations/s)。测试机器没有资源限制,阻塞似乎是我的情况的限制因素。

有没有人有测量Event Store吞吐量的经验,并取得了什么样的数字?我希望至少获得1个数量级更高的吞吐量,以使其成为可行的选项。

3个回答

7
我认为阻塞IO将是最大的瓶颈。我在基准测试中看到的一个问题是,您正在对单个流进行操作。在每秒3K+事件的域中,您有多少个聚合根?EventStore的主要设计是针对多线程操作多个聚合根,这可以减少现实应用程序中的争用和锁定。
另外,您使用哪种序列化机制?JSON.NET?我没有Protocol Buffers实现(但),但每个基准测试都显示PB在性能方面显着更快。运行分析器以查看应用程序中最大的瓶颈将是有趣的。
我注意到的另一件事是,您引入了网络跳跃,这会增加任何单个流的延迟(和阻塞时间)。如果您写入使用固态驱动器的本地SQL实例,则可以看到与运行磁盘驱动器并且数据和日志文件位于同一盘片上的远程SQL实例相比数字更高的数字。
最后,您的基准测试应用程序是否使用System.Transactions或默认为无事务?(EventStore在不使用System.Transactions或任何类型的SQL事务的情况下是安全的。)
现在,尽管如此,我毫不怀疑EventStore中存在可以通过一点关注极大优化的领域。实际上,我正在考虑一些向后兼容的模式修订,以减少单个提交操作期间在SQL Server(和RDBMS引擎总体上)中执行的写入数量,这是3.1版本的基础。
当我开始进行2.x重写时,我面临的最大设计问题之一是异步,非阻塞IO的想法。我们都知道,node.js和其他非阻塞Web服务器比线程Web服务器快一个数量级。但是,引入到调用方的复杂性的潜力增加了,并且必须认真考虑,因为它是大多数程序和库操作方式的根本性转变。如果我们确实移动到事件驱动的非阻塞模型,则可能会在4.x时间框架内更多地实现。
底线:发布您的基准测试,以便我们可以看到瓶颈在哪里。

1
感谢您的回复,乔纳森。为了澄清一下,每次提交都是一个新的EventSource,因此我每秒提交3K个不同的EventSources。省略网络跳跃并没有改善情况,但这是一个有效的观点。就事务而言,我没有显式地注册事务,但这可能与不使用事务不同。我正在使用JSON进行序列化,尽管我们不受CPU限制,但我认为这并不会限制我们。我已经将测试工具发布到GitHub上(https://github.com/MattCollinge/EventStore-Performance-Tests.git)。 - MattC

6

马特提出了一个很好的问题,我看到奥利弗先生亲自回答了这个问题!

我想提供一种略微不同的方法,这是我自己在尝试帮助解决你所见到的每秒3,000次提交瓶颈的方法。

CQRS模式,大多数使用JOliver的EventStore的人都似乎在尝试遵循这种模式,它允许一些“扩展”子模式。人们通常排队处理事件提交本身,这是你正在遇到瓶颈的地方。 “排队处理”意味着从实际提交中卸载并将它们插入某些写优化、非阻塞I/O进程或“队列”中。

我的松散解释是:

命令广播 -> 命令处理程序 -> 事件广播 -> 事件处理程序 -> 事件存储

这些模式中实际上有两个可扩展点:命令处理程序和事件处理程序。如上所述,大多数人从事件处理程序部分开始扩展,或者在你的情况下是Commits到EventStore库,因为这通常是由于需要将其持久化到某个地方(例如Microsoft SQL Server数据库)而产生的最大瓶颈。

我自己正在使用一些不同的提供程序来测试最佳性能,以“排队”这些提交。CouchDB和.NET的AppFabric Cache(它具有出色的GetAndLock()功能)。 [OT]我真的很喜欢AppFabric的持久性缓存功能,它允许您创建冗余缓存服务器,跨多台机器备份您的区域 - 因此,只要至少有1个服务器处于运行状态,您的缓存就会保持活动状态。[/OT]

因此,想象一下你的事件处理程序不直接将提交写入EventStore。相反,你有一个处理程序将它们插入到“队列”系统中,例如Windows Azure Queue、CouchDB、Memcache、AppFabric Cache等。关键是选择一个几乎没有阻塞的系统来排队事件,但具有内置冗余(对于冗余选项,我最不喜欢Memcache)。在服务器掉落的情况下,您必须拥有那种冗余,以便仍然可以排队事件。

最后提交这个“Queued Event”的方式有几种选择。我喜欢Windows Azure的队列模式,因为你可以有很多“工作者”不断地寻找队列中的工作。但它不一定非得是Windows Azure - 我已经在本地代码中使用“队列”和“工作者角色”在后台线程中运行来模拟Azure的队列模式。它的扩展性非常好。

假设您有10个工人不断查看这个“队列”中的任何用户更新事件(我通常为每种事件类型编写一个单独的工作角色,这样在扩展时更容易监视每种类型的统计数据)。两个事件被插入到队列中,前两个工人立即各自接收到一条消息,并将它们(提交它们)直接同时插入到您的EventStore中 - 多线程,正如Jonathan在他的回答中提到的那样。您使用该模式的瓶颈将是您选择的任何数据库/事件存储后端。假设您的EventStore正在使用MSSQL,瓶颈仍然是3,000 RPS。这很好,因为系统建立在“最终一致性”的基础上,当RPS降至50 RPS时,例如在20,000次突发之后,系统会“赶上”。
我说过CQRS模式本身还有其他可扩展模式。另一个模式,正如我上面提到的,是命令处理程序(或命令事件)。这也是我所做的,特别是如果您拥有非常丰富的域域,就像我的客户一样(每个命令都有数十个处理器密集型验证检查)。在这种情况下,我实际上会将命令本身排队,由某些工作角色在后台处理。这也为您提供了一个良好的扩展模式,因为现在您的整个后端,包括事件的EventStore提交,都可以进行线程处理。
显然,这样做的缺点是您会失去一些实时验证检查。我通过通常将验证分成两类来解决这个问题,当构建我的域时。其中之一是Ajax或实时“轻量级”域验证(有点像预命令检查)。另外一个是硬失败验证检查,在域中完成但不可用于实时检查。然后,您需要在领域模型中编写代码以应对失败。这意味着,如果发生故障,通常以向用户发送通知电子邮件的形式编写出路。因为用户不再被此排队的命令阻塞,所以如果命令失败,他们需要得到通知。
您需要进行的验证检查需要前往您的查询或“只读”数据库,对吗?不要进入EventStore来检查唯一的电子邮件地址等内容。您应该针对前端查询的高度可用性只读数据存储进行验证。嘿,让单个CouchDB文档专门用于系统中所有电子邮件地址的列表作为CQRS查询部分。
CQRS只是建议……如果您确实需要实时检查繁重的验证方法,那么可以在此基础上构建一个查询(只读)存储库,并在被插入队列之前在PreCommand阶段加快验证速度。这样具有很大的灵活性。我甚至认为像空用户名和空电子邮件这样的验证不是领域关注点,而是UI责任(将实时验证的需求卸载到领域之外)。我曾经设计过一些项目,在我的MVC/MVVM ViewModels中具有非常丰富的UI验证。当然,我的Domain进行了非常严格的验证,以确保在处理之前是有效的。但是将中等输入验证检查或我称之为“轻量级”验证提升到ViewModel层可以给最终用户提供近乎即时的反馈,而无需进入我的领域。(还有一些技巧可以保持与领域的同步)。因此,总之,可能要考虑在提交事件之前排队。这与Jonathan在他的答案中提到的EventStore的多线程特性非常吻合。

1
有趣的回答。感谢您写下来! - Daniel Lidström

0

我们使用Erlang/Elixir构建了一个小型的并发模板,https://github.com/work-capital/elixir-cqrs-eventsourcing 使用Eventstore。我们仍然需要优化数据库连接、池等...但是每个聚合物有一个进程和多个数据库连接的想法与您的需求相符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接