不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

嘿哈布尔!

我们提醒您,遵循这本书 卡夫卡 我们出版了一本同样有趣的关于图书馆的著作 卡夫卡流 API.

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

目前,社区刚刚了解这个强大工具的局限性。 因此,最近发表了一篇文章,我们想向您介绍该文章的翻译。 作者从自己的经验讲述了如何将Kafka Streams变成分布式数据存储。 享受阅读!

阿帕奇库 卡夫卡流 在全球范围内的企业中使用 Apache Kafka 进行分布式流处理。 该框架未被充分重视的方面之一是它允许您存储基于线程处理生成的本地状态。

在本文中,我将告诉您我们公司在开发云应用程序安全产品时如何利用这个机会获利。 使用 Kafka Streams,我们创建了共享状态微服务,每个微服务都充当有关系统中对象状态的可靠信息的容错且高度可用的来源。 对于我们来说,这在可靠性和易于支持方面都向前迈出了一步。

如果您对允许您使用单个中央数据库来支持对象的正式状态的替代方法感兴趣,请阅读它,它会很有趣......

为什么我们认为是时候改变共享状态的工作方式了

我们需要根据代理报告维护各种对象的状态(例如:站点是否受到攻击)? 在迁移到 Kafka Streams 之前,我们经常依赖单个中央数据库(+服务 API)进行状态管理。 这种方法有其缺点: 日期密集的情况 保持一致性和同步成为一个真正的挑战。 数据库可能成为瓶颈或最终陷入困境 竞争条件 并遭受不可预测性的困扰。

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 1:过渡到之前看到的典型分裂状态场景
Kafka 和 Kafka Streams:代理通过 API 传达其视图,更新状态通过中央数据库计算

了解 Kafka Streams,轻松创建共享状态微服务

大约一年前,我们决定认真研究我们的共享状态场景以解决这些问题。 我们立即决定尝试 Kafka Streams - 我们知道它的可扩展性、高可用性和容错性,它具有丰富的流功能(转换,包括有状态的转换)。 这正是我们所需要的,更不用说 Kafka 中的消息系统已经变得多么成熟和可靠。

我们创建的每个有状态微服务都构建在具有相当简单拓扑的 Kafka Streams 实例之上。 它由 1) 源 2) 具有持久键值存储的处理器 3)​​ 接收器组成:

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 2:有状态微服务的流实例的默认拓扑。 请注意,这里还有一个包含规划元数据的存储库。

在这种新方法中,代理编写消息并馈入源主题,而消费者(例如邮件通知服务)通过接收器(输出主题)接收计算的共享状态。

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 3:共享微服务场景的新示例任务流程:1) 代理生成一条到达 Kafka 源主题的消息; 2) 具有共享状态的微服务(使用 Kafka Streams)对其进行处理并将计算出的状态写入最终的 Kafka 主题; 之后3)消费者接受新状态

嘿嘿,这个内置的键值存储其实非常有用!

如上所述,我们的共享状态拓扑包含一个键值存储。 我们找到了几种使用它的选项,下面描述了其中两个。

选项#1:使用键值存储进行计算

我们的第一个键值存储包含计算所需的辅助数据。 例如,在某些情况下,共享状态是由“多数票”原则确定的。 存储库可以保存有关某个对象状态的所有最新代理报告。 然后,当我们从一个代理或另一个代理收到一份新报告时,我们可以保存它,从存储中检索所有其他代理关于同一对象状态的报告,然后重复计算。
下面的图 4 显示了我们如何将键/值存储公开给处理器的处理方法,以便可以处理新消息。

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 4:我们为处理器的处理方法开放对键值存储的访问(此后,每个使用共享状态的脚本都必须实现该方法 doProcess)

选项#2:在 Kafka Streams 之上创建 CRUD API

建立了基本任务流程后,我们开始尝试为共享状态微服务编写 RESTful CRUD API。 我们希望能够检索部分或所有对象的状态,以及设置或删除对象的状态(对于后端支持有用)。

为了支持所有获取状态 API,每当我们在处理过程中需要重新计算状态时,我们都会将其长期存储在内置键值存储中。 在这种情况下,使用 Kafka Streams 的单个实例实现这样的 API 变得非常简单,如下清单所示:

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 5:使用内置键值存储获取对象的预计算状态

通过 API 更新对象的状态也很容易实现。 基本上,您需要做的就是创建一个 Kafka 生产者并使用它来创建包含新状态的记录。 这确保了通过 API 生成的所有消息都将以与从其他生产者(例如代理)接收的消息相同的方式进行处理。

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 6:您可以使用 Kafka 生产者设置对象的状态

小复杂:Kafka 有很多分区

接下来,我们希望通过为每个场景提供共享状态微服务集群来分配处理负载并提高可用性。 设置非常简单:一旦我们将所有实例配置为在相同的应用程序 ID(以及相同的引导服务器)下运行,几乎所有其他操作都会自动完成。 我们还指定每个源主题将由多个分区组成,以便可以为每个实例分配此类分区的子集。

我还将提到,制作状态存储的备份副本是常见的做法,以便在发生故障后恢复时将此副本传输到另一个实例。 对于 Kafka Streams 中的每个状态存储,都会使用更改日志(跟踪本地更新)创建一个复制主题。 因此,Kafka 不断备份状态存储。 因此,如果一个或另一个 Kafka Streams 实例发生故障,状态存储可以快速恢复到另一个实例上,相应的分区将存储在该实例上。 我们的测试表明,即使存储中有数百万条记录,这也只需几秒钟即可完成。

从具有共享状态的单个微服务迁移到微服务集群,实现 Get State API 变得不再那么简单。 在新情况下,每个微服务的状态存储仅包含整体情况的一部分(其键映射到特定分区的那些对象)。 我们必须确定哪个实例包含我们需要的对象的状态,并且我们根据线程元数据来完成此操作,如下所示:

不仅仅是处理:我们如何从 Kafka Streams 创建分布式数据库,以及它的结果

图 7:使用流元数据,我们确定从哪个实例查询所需对象的状态; GET ALL API 使用了类似的方法

主要结论

Kafka Streams 中的状态存储可以充当事实上的分布式数据库,

  • 在Kafka中不断复制
  • CRUD API 可以轻松地构建在这样的系统之上
  • 处理多个分区有点复杂
  • 还可以向流式拓扑添加一个或多个状态存储来存储辅助数据。 该选项可用于:
  • 流处理过程中计算所需数据的长期存储
  • 长期存储数据,下次配置流实例时可能有用
  • 多得多...

这些和其他优点使 Kafka Streams 非常适合在像我们这样的分布式系统中维护全局状态。 事实证明,Kafka Streams 在生产中非常可靠(自从部署它以来,我们几乎没有丢失任何消息),并且我们相信它的功能不会止步于此!

来源: habr.com

添加评论