使用线性事件存储实现CQRS中的一致性

在最近的一个涉及基于事件的CQRS系统的项目中,我们决定做一些与大多数谈论的解决方案相比似乎有些不同的事情。 但是,它们使我们获得了一些不错的属性,否则这些属性将很难(如果可能的话)。

事件存储为常规表

我们决定将事件存储作为RDBMS中的常规表来实现。 我们使用了PostgreSQL,但是这里几乎没有特定于PostgreSQL的内容。 我们知道该数据库非常可靠,功能强大且非常成熟。 最重要的是,单节点ACID事务提供了一些非常好的好处。

该表以以下字段结尾:

  • event_id (int)–来自全局序列的主键
  • stream_id (UUID)–事件流的ID,通常是DDD聚合
  • seq_no (int)–特定流历史记录中的序列号
  • transaction_time (时间戳)–事务开始时间,对于一次事务中提交的所有事件都相同
  • correlation_id (UUID)
  • payload (JSON)

并非所有这些对于事件存储都是必需的,但是有一个重要event_id常见的区别: event_id –全局,顺序增加。 稍后我们将解决这个问题。

你可以做到吗?

如果您在常规数据库表中进行事件存储,则获得这样的全局事件ID极其便宜。 数据库确实非常有效地生成,存储,索引等列。 唯一的实际问题是,您首先是否可以负担得起使用数据库表。

我们一直在构建的系统并不面向广泛的网络。 它旨在供拥有数百或数千用户的公司内部使用。 这是一个相对较低的规模,而Postgres不会有任何问题。

总而言之,如果您要构建下一个亚马逊,那不是我不推荐的东西。 但是您并非偶然,因此您可以负担得起使用简单技术的奢侈。

全局顺序事件ID的好处

现在我们有了这个特殊的事件ID,我们该怎么办?

让我们看一下事件存储的读取接口:

public interface EventStoreReader {
    List<Event> getEventsForStream(UUID streamId, long afterSequence, int limit);
    List<Event> getEventsForAllStreams(long afterEventId, int limit);
    Optional<Long> getLastEventId();
}

第一种方法很明显,随处可见。 我们仅使用它从事件存储中还原单个流(聚合)以处理新命令。

其他两个使用事件ID,在特定事件之后返回一批事件,以及最后一个事件的ID。 它们是我们的读取模型(投影)的基础。

linear_read_models-e1441992727210

读取模型是通过轮询(带有提示)事件存储来实现的。 他们记住最后处理的事件的ID 。 每隔一段时间(或当事件存储中的通知唤醒时),他们会从存储中读取下一批事件,并在单个线程中按顺序处理它们。

这种线性单线程处理可能会尽可能简单,但显然可伸缩性有限。 如果每分钟收到600个事件,则意味着无论如何,平均每个事件您的速度都不会慢于100 ms。 实际上,您还需要考虑开销并留出一些空间,因此它需要比这更快。

可以通过在读取模型中分片或并行化写入来解决该问题,但目前我们还没有发现这一点。 并行运行多个独立的专业模型无疑可以帮助实现这一目标。

将投影的最后处理的事件ID与当前的全局最大值进行比较,您可以立即知道该投影背后有多少 。 这在逻辑上等同于队列大小。

全局序列也可以用来减轻最终一致性 (或陈旧性) 的不利影响

执行命令可能会返回上一个写入事件的ID。 然后查询可以使用该ID,并要求:“我可以等待5秒钟,但是如果您的数据早于该ID,请不要给我结果”。 在大多数情况下,这仅是几毫秒。 对于该价格,当用户进行更改时,她会立即看到结果。 这是来自服务器的实际数据,而不是通过在用户界面中复制域逻辑来实现的模拟!

在域方面也很有用。 我们有一些应用程序和域服务,它们查询某些特定于域的预测(例如,进行唯一检查)。 如果您知道事件存储中的最后一个事件是X,则可以等到投影赶上该点,然后再对该命令进行进一步处理。 这就是解决通常用传奇解决的许多问题所需要的全部。

最后但并非最不重要的一点是,由于所有事件都是有序的, 因此投影始终是一致的 。 它可能会落后几秒钟或几天,但绝不会前后矛盾。 根本不可能遇到这样的问题,例如一个流要处理到星期一,而另一个流要处理到星期四。 如果在发生特定事件之前发生了某些事情,则视图模型中始终保持相同的顺序。

它使代码和系统状态更易于编写,维护和推理。

再谈可扩展性和复杂性

无论实际的客户需求和实际规模如何,都有使用复杂,可扩展性高的技术的趋势。 这样的工具虽然占有一席之地,但并不是显而易见的赢家,没有解决所有问题的金锤子。 此外,如果考虑到开发和运营的复杂性及其局限性,那么它们确实非常昂贵。

有时,使用更简单的工具可以很好地解决问题。 您不仅可以节省开发和运营成本,还可以使用一些真正强大的工具,而这在大规模生产中是不可能的。 包括全局计数器,线性化和ACID事务。

我们的示例显示了一个系统,该系统足够复杂,可以使用CQRS保证事件来源,但是规模足够小,即使在线性Postgres数据库中,也可以使用线性事件存储(甚至具有线性投影)。

选择无聊技术有很多充分的原因。 如果要进行创新(应该这样做),请谨慎选择为什么要这样做,并且不要同时在所有领域进行创新。

翻译自: https://www.javacodegeeks.com/2015/09/achieving-consistency-in-cqrs-with-linear-event-store.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值