马特提出了一个很好的问题,我看到奥利弗先生亲自回答了这个问题!
我想提供一种略微不同的方法,这是我自己在尝试帮助解决你所见到的每秒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的多线程特性非常吻合。