这篇文章是由Fivetran首席执行官George Fraser和Materialize首席执行官Arjun Narayan共同撰写的。此博客文章交叉发布在Fivetran博客上。
Apache Kafka是一个消息代理,在过去几年中迅速普及。消息经纪人已经存在了很长时间。它们是一种数据存储,专门用于在生产者和使用者系统之间“缓冲”消息。 Kafka之所以流行,是因为它是开源的,并且能够扩展到大量邮件。
消息代理通常用于使数据的生产者和消费者分离。例如,在Fivetran中,我们使用类似于Kafka的消息代理来缓冲客户生成的Webhook,然后将它们批量加载到数据仓库中:
在这种情况下,消息代理将提供持久的事件存储,从客户发送事件到Fivetran将事件加载到数据仓库之间。
但是,卡夫卡偶尔被描述为不仅仅是一个更好的消息代理。支持该观点的人将Kafka定位为一种全新的数据管理方式,其中Kafka取代了关系数据库作为已发生事件的确定记录。无需读写传统数据库,而是将事件追加到Kafka,然后从代表当前状态的下游视图中读取。这种体系结构已被描述为“将数据库内翻”。
原则上,可以以同时支持读取和写入的方式来实现此体系结构。但是,在此过程中,您最终将面临数据库管理系统数十年来所面临的每一个难题。您将或多或少必须在应用程序代码中编写完整的DBMS。而且您可能做得不好,因为数据库需要花费数年的时间才能正确完成。您将不得不处理草率读取,幻像读取,写入歪斜以及仓促实现的数据库的所有其他症状。
使用Kafka作为您的主要数据存储的根本问题是它没有提供隔离。隔离意味着,在全球范围内,所有事务(读和写)都遵循某个一致的历史记录。 Jepsen提供了隔离级别的指南(使用隔离级别意味着系统将永远不会遇到某些异常情况)。
让我们考虑一个简单的示例,说明隔离为何如此重要:假设我们正在运营一个在线商店。用户结帐时,我们要确保他们所有的物品实际上都在库存中。做到这一点的方法是
假设我们正在使用Kafka来管理此过程。我们的架构可能看起来像这样:
Web服务器从Kafka下游的视图读取库存级别,但是它只能在checkouts主题的上游提交事务。问题是并发控制之一:如果有两个用户竞相购买最后一个项目,那么只有一个必须成功。我们需要阅读清单视图并在单个时间点确认结帐。但是,在这种体系结构中无法做到这一点。
我们现在遇到的问题称为写偏斜。在处理结帐事件时,我们从库存视图中读取的数据可能已过期。如果两个用户尝试几乎同时购买同一商品,那么他们都会成功,而我们将没有足够的存货供他们使用。
此类事件源架构会遇到许多此类隔离异常,这些异常会不断以众所周知的“时间旅行”行为吸引用户。更糟糕的是,研究表明,允许异常的体系结构会制造出彻底的安全漏洞,从而使黑客能够窃取数据,正如本研究论文的这篇出色博客文章所述。
如果将Kafka用作传统数据库的补充,则可以避免这些问题:
OLTP数据库执行消息代理不太适合提供的一项关键任务:事件的准入控制。 OLTP数据库不会使用消息代理作为“触发并忘记”事件的容器,而是将事件模式强制为“意图模式”,而可以拒绝发生冲突的事件,从而确保仅发出一致的事件流。 OLTP数据库确实擅长于此核心并发控制任务-每秒扩展到数百万个事务。
使用数据库作为写入的入口点,从数据库提取事件的最佳方法是通过流式传输更改数据捕获。有许多出色的开放式CDC框架,例如Debezium和Maxwell,以及现代SQL数据库中的本机CDC。变更数据捕获还建立了一个优雅的操作案例。在恢复方案中,所有内容都可以从下游清除,并从(非常耐用的)OLTP数据库重建。
数十年来,数据库社区已经学习(并重新学习了)一些重要的经验教训。这些课程中的每一项都是以数据损坏,数据丢失和大量面向用户的异常情况的高昂代价获得的。您要做的最后一件事是发现自己在重新学习这些课程,因为您不小心构建了一个数据库。
实时流消息代理是管理高速数据的绝佳工具。但是您仍然需要传统的DBMS来隔离事务。最好的参考架构“将您的数据库全部翻过来”是使用OLTP数据库进行准入控制,使用CDC进行事件生成,并将数据的下游副本建模为实例化视图。
如果您想实时获取有关Kafka数据的完全一致的视图,请尝试实现,以查看它是否是适合您的解决方案!