在上一篇文章中,我们研究了RabbitMQ中使用的模式和拓扑。 在这一部分中,我们将转向Kafka并将其与RabbitMQ进行比较,以获得有关它们之间差异的一些想法。 应该记住,将比较面向事件的应用程序体系结构,而不是数据处理管道,尽管在这种情况下,这两个概念之间的界线将相当模糊。 总的来说,这是一个频谱,而不是一个清晰的分离。 我们的比较将仅关注与事件驱动的应用程序相关的部分。

首先想到的区别是RabbitMQ用于在Kafka中处理死信消息的消息重试和贪睡机制是没有意义的。 在RabbitMQ中,消息是临时的,它们被传输并消失。 因此,重新添加它们是绝对真实的用例。 在卡夫卡,该杂志成为焦点。 通过将消息重新发送到队列来解决传递问题没有任何意义,只会损害日志。 优点之一是可以确保在整个日记的分区中清楚地分配消息,重复的消息会混淆组织良好的方案。 在RabbitMQ中,您已经可以将消息发送到与一个收件人一起工作的队列,并且在Kafka平台上,所有收件人都只有一本日记。 传递的延迟和消息传递的问题不会对日记的操作造成很大的损害,但是Kafka不包含内置的延迟机制。
如何在Kafka平台上重新传递消息将在关于消息传递方案的部分进行讨论。
影响可能的消息传递方案的第二个大差异是RabbitMQ存储的消息远少于Kafka。 当消息已经在RabbitMQ中传递给接收者时,将被删除而不会留下其存在的痕迹。 在Kafka中,每条消息都会保留在日志中,直到被清除为止。 清理的频率取决于可用数据量,计划为其分配的磁盘空间量以及要确保的消息传递方案。您可以使用给定时间段内我们存储消息的时间窗口:最近几天/几周/几个月。
这样,Kafka允许收件人重新查看或重新捕获以前的邮件。 它看起来像一种发送消息的技术,尽管它的功能与RabbitMQ不同。
如果RabbitMQ移动消息并提供强大的元素来创建复杂的路由方案,则Kafka将保存系统的当前状态和先前状态。 该平台可以用作可靠历史数据的来源,因为RabbitMQ不能。
Kafka平台上的示例消息传递方案
同时使用RabbitMQ和Kafka的最简单示例是根据“发布者-订阅者”方案传播信息。 一个或多个发布者将消息添加到分区日志中,并且这些消息被一个或多个订户组的订户接收。

图1.几个发布者将消息发送到分区日志,几组接收者接收到它们。
如果您不了解发布者如何将邮件发送到日记的必要部分以及收件人组之间如何进行协调的详细信息,则此方案与RabbitMQ中使用的扇出拓扑(分支交换)没有什么不同。
在上一篇文章中,讨论了所有RabbitMQ消息传递方案和拓扑。 也许在某个时候您认为“我不需要所有这些困难,我只想在队列中发送和接收消息”,而您可以将杂志倒带到以前的位置这一事实说明了卡夫卡的明显优势。
对于习惯了排队系统传统功能的人们来说,将时钟往回移动并将事件日志倒回过去的可能性实在令人惊讶。 此属性(可通过使用日志而不是队列来使用)对于从故障中恢复非常有用。 我(英语文章的作者)四年前开始为我当前的客户工作,担任服务器系统支持小组的技术经理。 我们有50多个应用程序通过MSMQ接收有关业务事件的实时信息,通常的情况是,当应用程序中发生错误时,系统仅在第二天才检测到该错误。 不幸的是,结果通常是消息消失了,但是通常我们能够从第三方系统获取初始数据,然后仅将消息转发给出现问题的“订户”。 这要求我们为收件人创建消息传递基础结构。 而且,如果我们拥有Kafka平台,那么要做这样的工作就不会比将链接更改为发生错误的应用程序最后收到消息的位置的链接要困难得多。
面向事件的应用程序和系统中的数据集成
尽管与单个应用程序无关,但该方案在许多方面都是生成事件的一种方式。 事件生成分为两个级别:软件和系统。 本方案与后者相关。
程序级事件生成
应用程序通过存储在事件存储中的不变的更改事件序列来管理自己的状态。 为了获得应用程序的当前状态,您应该按正确的顺序播放或组合其事件。 通常在这种模型中, CQRS Kafka 模型可以用作此系统。
系统级应用程序之间的交互。
应用程序或服务可以以其开发人员想要管理的任何方式来管理其状态,例如,在常规关系数据库中。
但是应用程序通常需要彼此有关的数据,这会导致体系结构欠佳,例如,通用数据库,实体边界模糊或不方便的REST API。
我(英语文章的作者)收听了“ 软件工程日报 ”播客,该播客描述了社交网络上服务配置文件的面向事件的场景。 系统中有许多相关服务,例如搜索,社交图谱系统,推荐引擎等,所有这些服务都需要了解用户个人资料状态的变化。 当我(英语文章的作者)担任与航空运输相关的体系结构的架构师时,我们拥有两个大型软件系统以及大量相关的小型服务。 支持服务需要的订单和航班数据。 每次创建或更改订单时,如果航班延误或取消,则必须激活这些服务。
它需要一种用于生成事件的技术。 但是首先,让我们看一下大型软件系统中出现的一些常见问题,并查看事件的生成如何解决它们。
大型的集成公司系统通常会有机地发展; 进行了向新技术和新体系结构的迁移,这可能不会影响系统的100%。 数据被分发到机构的不同部分,应用程序公开了供公众使用的数据库,以便尽快进行集成,而且没有人可以确定地预测系统的所有元素将如何交互。
随机数据分配
数据分布在不同的地方,并在不同的地方进行管理,因此很难理解:
- 数据如何在业务流程中移动;
- 系统某一部分的变化如何影响其他部分;
- 由于存在许多数据传播缓慢的事实而导致的数据冲突该怎么办。
如果域实体没有明确的界限,则更改将是昂贵且冒险的,因为它们会立即影响许多系统。
集中式分布式数据库
公开打开的数据库可能会导致几个问题:
- 它不能针对每个应用程序分别进行优化,最有可能的是,该数据库包含一个过于完整的应用程序数据集,而且,对数据库进行规范化的方式是,应用程序必须运行非常复杂的查询才能接收它们。
- 使用公共数据库,应用程序可以影响彼此的工作。
- 数据库逻辑结构的更改需要大规模的协调和数据迁移方面的工作,并且在整个过程中将停止单个服务的开发。
- 没有人想改变存储结构。 每个人都在等待的变化太痛苦了。
使用不方便的REST API
一方面,通过REST API从其他系统获取数据增加了便利性和隔离性,但可能并不总是成功。 每个此类接口都可以具有自己的特殊样式和约定。 获取必要的数据可能需要大量HTTP请求,并且非常复杂。
我们越来越朝着以API为中心的方向发展,这种架构提供了许多优势,尤其是当服务本身不受我们的控制时。 目前,有许多方便的方法来创建API,因此我们不必编写以前需要的代码。 但是,这不是唯一可用的工具,并且系统的内部体系结构还有其他选择。
Kafka作为事件存储库
我们举一个例子。 有一个系统可以管理关系数据库中的预订。 该系统使用数据库提供的所有原子性,一致性,隔离性和持久性保证,以便有效地管理其特征,每个人都很高兴。 通常,没有传统上构建的整体来将责任划分为团队和请求,事件的生成,微服务。 但是有很多与预订相关的支持服务(可能是微服务):推送通知,电子邮件分发,反欺诈系统,会员计划,计费,取消系统等。 这个清单不胜枚举。 所有这些服务都需要预订详细信息,并且有很多方法可以获取它们。 这些服务本身会产生可能对其他应用程序有用的数据。

图2.各种类型的数据集成。
基于Kafka的替代架构。 每次进行新的保留或更改先前的保留时,系统都会将有关此保留当前状态的完整数据发送到Kafka。 通过合并日记帐,您可以缩短消息,以便仅保留有关最新预订状态的信息。 在这种情况下,日志的大小将受到控制。

图3.基于Kafka的数据集成作为事件生成的基础
对于所有需要这样做的应用程序,此信息都是真理的来源,也是唯一的数据来源。 突然之间,我们正从依赖项和技术的集成网络过渡到与Kafka主题之间收发数据。
Kafka作为事件存储库:
- 如果磁盘空间没有问题,Kafka可以存储事件的整个历史记录,也就是说,可以部署新的应用程序并从日志中下载所有必要的信息。 可以通过编译日志来压缩完全反映对象特征的事件记录,这将使这种方法在许多情况下更加合理。
- 如果事件需要以正确的顺序播放怎么办? 只要正确分配了事件记录,就可以设置事件的回放顺序,并应用过滤器,转换工具等,以便数据回放始终在必要的信息上结束。 根据数据分发的可能性,可以确保以正确的顺序进行高度并行的处理。
- 可能需要更改数据模型。 创建新的过滤器/转换功能时,可能有必要回放过去一周中所有事件或事件的记录。
不仅可以从组织的应用程序发送有关其特征的所有更改(或这些更改的结果)的消息,还可以从与系统集成的第三方服务向Kafka发送消息。 这通过以下方式发生:
- 定期导出,传输,导入从第三方服务接收的数据,并将其下载到Kafka。
- 从Kafka中的第三方服务下载数据。
- CSV数据和从第三方服务上载的其他格式的数据上载到Kafka。
让我们回到我们先前考虑的问题。 基于Kafka的体系结构简化了数据分发。 我们知道真理的来源,我们知道其数据的来源,并且所有目标应用程序都使用从该数据派生的副本。 数据从发送者到接收者。 源数据仅属于发送方,而其他数据则可以自由处理其投影。 他们可以过滤,转换,补充其他来源的数据,并将其保存在自己的数据库中。

图4.源和输出数据
每个需要预订和航班数据的应用程序都会自己接收它,因为它被“订阅”到Kafka包含这些数据的部分中。 对于此应用程序,他们可以使用SQL,Cypher,JSON或任何其他查询语言。 然后,应用程序可以根据需要将数据保存在其系统中。 可以更改数据分发方案,而不会影响其他应用程序的操作。
可能会出现问题:为什么不能使用RabbitMQ来完成所有这些工作? 答案是RabbitMQ可用于实时处理事件,但不能用作生成事件的基础。 RabbitMQ是仅用于响应当前发生的事件的完整解决方案。 当添加新的应用程序时,需要保留其自己的保留数据的一部分,该保留数据以针对该应用程序的任务进行了优化的格式呈现,RabbitMQ将无法提供帮助。 使用RabbitMQ,我们返回共享数据库或REST API。
其次,事件的处理顺序很重要。 如果使用RabbitMQ,则在将第二个收件人添加到队列时,将失去对订单的保证。 因此,仅对一个接收者观察到发送消息的正确顺序,但这当然是不够的。
相比之下,Kafka可以提供此应用程序创建其自己的数据副本并保持数据最新所需的所有数据,而Kafka遵循消息发送的顺序。
现在回到以API为中心的体系结构。 这些接口将永远是最佳选择吗? 当您要打开只读数据访问时,我希望使用事件发出架构。 这将防止级联故障,并缩短与其他服务的依赖性数量相关的寿命。 系统中将有更多机会进行创造性和高效的数据组织。 但是有时您需要同步更改系统和另一个系统中的数据,在这种情况下,以API为中心的系统将很有用。 许多人喜欢它们而不是其他异步方法。 我认为这是一个品味问题。
高流量和事件处理敏感应用程序。
不久前,RabbitMQ的接收方之一出现了问题,该接收方从第三方服务接收排队的文件。 文件的总大小很大,并且已将应用程序特别配置为接收这样的数据量。 问题是数据输入不一致,这带来了很多问题。
另外,有时存在一个问题,即有时两个文件旨在到达同一目的地,并且它们的到达时间相差几秒钟。 他们都经过处理,必须上载到一台服务器。 在服务器上记录了第二条消息之后,其后的第一条消息将覆盖第二条消息。 因此,一切都以保存无效数据结束。 RabbitMQ发挥了作用,并以正确的顺序发送了消息,但所有这些都以应用程序本身的错误顺序结束了。
通过从现有记录中读取时间戳以及如果消息较旧则缺少响应,可以解决此问题。 此外,在数据交换期间应用了一致的哈希,并且对队列进行了划分,就像在Kafka平台上进行相同的分区一样。
作为分区的一部分,Kafka按发送消息的顺序存储消息。 消息顺序仅存在于分区内。 在上面的示例中,使用Kafka,我们必须将哈希函数应用于目标的ID,才能选择所需的分区。 我们必须创建一组分区,其中应该有更多的分区,而不是客户端所需的分区。 由于每个分区仅面向一个收件人,因此应该已经完成了消息处理的顺序。 简单有效。
与RabbitMQ相比,Kafka具有与使用散列进行消息拆分相关的一些优势。 RabbitMQ平台上没有任何东西可以防止在使用一致哈希的数据交换过程中生成的同一队列中的收件人冲突。 RabbitMQ无法帮助协调收件人,因此整个队列中只有一个收件人使用该消息。 Kafka通过使用收件人组和协调节点来提供所有这些功能。 这使您可以确保该部分中只有一个收件人可以使用该消息,并且可以确保数据处理顺序。
数据局部性
Kafka使用哈希函数在分区之间分配数据,从而提供了数据局部性。 例如,来自ID为1001的用户的消息应始终发送给收件人3。由于用户1001的事件始终发送给收件人3,因此,如果需要定期访问外部数据库或其他系统来接收,则收件人3可以有效地执行某些操作,而这些操作将更加困难。数据。 我们可以读取数据,执行汇总等。 直接将信息存储在收件人的记忆中。 这是面向事件的应用程序和数据流开始结合的地方。
Kafka如何提供数据局部性? 首先,必须注意,Kafka不允许弹性增加和减少分区数。 首先,您根本无法减少分区数:如果有10个分区,则不能减少至9个。 但是,另一方面,这不是必需的。 每个收件人可以使用1个或几个分区,因此,几乎没有必要减少其数量。 在Kafka上创建其他分区会导致重新平衡时出现延迟,因此我们在考虑峰值负载的情况下尝试扩展分区的数量。
但是,如果我们仍然需要增加分区和接收者的数量以进行扩展,那么在需要重新平衡的情况下,我们仅需要一次性的间接费用。 应该注意的是,在缩放旧数据时,旧数据将保留在原来的分区中。 但是新的传入消息将已经以不同的方式路由,并且新的分区将开始接收新的消息。 现在可以将来自用户1001的消息发送给收件人4(因为有关用户1001的数据现在分为两部分)。
此外,我们将比较和比较两个系统中传递消息的传递语义。 关于平衡和分区的主题值得单独撰写,我们将在下一部分中进行讨论。