原文链接 What is Kafka, and How Does it Work? A Tutorial for Beginners
目录
Kafka and Events – Key/Value Pairs
Why Kafka? Benefits and Use Cases
基础概念(Kafka Architecture – Fundamental Concepts)
Kafka Components and Ecosystem
简介
Kafka是一个事件流平台(event streaming platform)。
用来大规模的 collect, process, store, and integrate data。
用例包括 分布式流(distributed streaming), (流处理)stream processing, (数据集成)data integration , and (发布/订阅)pub/sub messaging.
Event Streaming Platform 是什么?
Event是什么?
event可以是任何类型的动作(action),事件(incident)或者变动(change)。
例如可以是一笔交易、一次网站的点击、一次温度的读取、对所发生事情的描述等。
换句话说 event是通知和状态的结合,通知就是告诉你,有事情发生了,你准备听着。状态就是具体发生的内容。状态通常不到1MB或更小,格式可以是JSON , Apache Avro™ 或 Protocol Buffers的序列化对象。
Kafka and Events – Key/Value Pairs
Kafka基于分布式提交日志(distributed commit log)的抽象。通过将日志拆分为分区(partitions),Kafka能够横向(scale-out )扩展系统。
因此,Kafka将事件(events)建模为键/值对。在内部,键和值只是字节序列,但在外部,在所选的编程语言中,它们通常是语言类型系统中表示的结构化对象。
Kafka称语言类型和内部字节之间的转换为序列化和反序列化。序列化格式通常是JSON、JSON Schema、Avro或Protobuf。
值(Values)通常是应用程序域对象的序列化表示或某种形式的原始消息输入,如传感器的输出。
键(Keys)也可以是复杂的域对象(domain objects),但通常是字符串或整数等基本类型。Kafka事件的key不一定是事件的唯一标识符,就像关系数据库中一行的主键一样。它更有可能是系统中某个实体的标识符,比如用户、订单或特定连接设备。
现在这听起来可能没那么重要,但我们稍后会看到,keys对于Kafka如何处理并行化(parallelization)和数据局部性(data locality)等问题至关重要。
Why Kafka? Benefits and Use Cases
Kafka被全球100, 000多个组织使用,并得到了蓬勃发展的专业开发人员社区的支持,他们不断共同推进流处理的最新技术。由于Kafka的高吞吐量、容错性、弹性和可扩展性,几乎每个行业都有大量用例,从银行和欺诈检测到运输和物联网。我们通常看到Kafka用于以下目的。
Data Integration
Kafka可以连接到传统企业信息系统、现代数据库或 cloud 上的几乎任何其他数据源。它与内置数据连接器形成了一个高效的集成点,而不会在脆弱的(brittle)集中式基础设施中隐藏逻辑(hiding logic)或路由(routing)。
Metrics and Monitoring
Kafka通常用于监控操作数据。这涉及到聚合来自分布式应用程序的统计信息,以生成具有实时指标的集中提要。
Log Aggregation
现代系统通常是分布式系统,日志数据必须从系统的各个组件集中到一个地方。Kafka经常通过集中所有来源的数据(无论形式或数量如何)来充当单一的真相来源。
Stream Processing
对事件流执行实时计算是Kafka的核心能力(core competency)。从实时数据处理到数据流编程,Kafka在数据流生成时以任何规模摄取(ingests)、存储和处理数据流。
Publish-Subscribe Messaging
作为一个分布式发布/订阅消息系统,Kafka作为传统消息代理的现代化版本运行良好。每当生成事件的进程必须与进程或接收事件的进程解耦时,Kafka都是一种可扩展且灵活的完成工作的方法。
基础概念(Kafka Architecture – Fundamental Concepts)
Kafka Topics
A topic is a log of events。
首先,新写的message,只能追加在最后。
其次,只能通过在日志中查找任意偏移量,然后扫描连续的日志条目来读取它们。
然后,日志中的事件是不可变的——一旦发生了什么,就很难让它不发生。
Logs也是持久性的,传统企业消息系统包括 topics 和 queues,用来临时存储message,作为source和destination的缓冲。
由于topics 是日志(logs),因此其中的数据本身没有任何临时性。每个topic都可以配置为在数据达到一定年龄(或topic 整体达到一定大小)后过期,从几秒钟到几年不等,甚至可以无限期保留消息。Kafka topics 背后的logs 是存储在磁盘上的文件。当你将一个event写入一个topic时,它就像你将它写入你曾经信任的任何数据库一样持久。
log 的简单性和其中内容的不变性是Kafka作为现代数据基础设施中关键组件成功的关键,但这仅仅是开始。
Kafka Partitioning
如果一个topic被限制在一台机器上,那将对Kafka的扩展能力造成相当大的限制。它可以在许多机器上管理许多topic——毕竟Kafka是一个分布式系统——但没有一个topic会变得太大,或者渴望容纳太多的读写。幸运的是,Kafka并没有让我们没有选择:它让我们能够对主题进行partition。
Partitioning将单个topic log分解为多个logs,每个log都可以位于Kafka集群中的一个单独节点上。这样,存储消息、编写新消息和处理现有消息的工作可以在集群中的许多节点之间分配。
How Kafka Partitioning Works
将topic分解为partitions后,我们需要一种方法来决定将哪些messages写入哪些partitions。通常,如果一条message没有key,后续message将在分解的所有partitions循环分发。在这种情况下,所有partitions都会获得均匀的数据份额,但我们不会保留输入消息的任何排序。如果消息确实有key,则将根据key的哈希计算 destination partition。这使得Kafka能够保证具有相同key的messages总是落在相同的partition,因此总是有序的。
例如,如果您生成的events都与同一客户相关联,则使用客户ID作为key可以保证来自给定客户的所有events始终按顺序到达。这就产生了一种可能性,即一个非常活跃的key将创建一个更大、更活跃的分区,但这种风险在实践中很小,当它出现时是可以管理的。为了保持keys的顺序,这通常是值得的。
Kafka Brokers
到目前为止,我们已经讨论了events, topics, and partitions,但到目前为止我们还没有太明确地说明图片中的实际计算机。从物理基础设施的角度来看,Kafka由一个称为代理(brokers)的机器网络组成。在现代(contemporary)部署中,这些可能不是单独的物理服务器,而是运行在Pod上的容器,Pod运行在虚拟化服务器上,虚拟化服务器运行在某个物理数据中心的实际处理器上。无论部署如何,它们都是独立的机器,每个机器都运行Kafka代理进程。每个代理都托管一组分区(partitions),并处理向这些分区写入新事件或从中读取事件的传入请求。代理 (brokers)还处理彼此之间的分区复制(replication of partitions)。
Replication
如果我们只将每个分区(partition)存储在一个代理上(broker),那就不行了。无论代理(brokers)是裸机(bare metal)服务器还是托管容器(managed containers),它们及其底层存储都容易发生故障(susceptible),因此我们需要将分区数据复制到其他几个代理以确保其安全。这些副本称为追随者副本(follower replica),而主分区称为领导者副本(leader replica)。当你向领导者(leader)提供数据时——一般来说,读和写作都是对领导者(leader)完成的——领导者(leader)和追随者(followers)会共同努力,将这些新写的记录复制给追随者(followers)。
这是自动发生的,虽然您可以调整生产者(producer)中的一些设置以产生不同级别的持久性保证,但作为在Kafka上构建系统的开发人员,这通常不是一个必须考虑的过程。作为一名开发人员,你真正需要知道的是你的数据是安全的,如果集群中的一个节点死亡,另一个节点将接管它的角色。
Client Applications
现在,让我们走出Kafka集群本身,看看使用Kafka的应用程序:生产者和消费者。这些是包含您的代码、将消息放入topics和从topics读取消息的客户端应用程序。Kafka平台中每个不是Kafka代理的组件,本质上要么是生产者,要么是消费者,或者两者兼而有之。生产和消费是您与集群交互的方式。
Kafka Producers
生产者库(producer library)的API表面相当轻量级:在Java中,有一个名为KafkaProducer的类用于连接到集群。您可以为此类提供配置参数的映射,包括集群中某些代理的地址、任何适当的安全配置以及确定生产者网络行为的其他设置。还有一个名为ProducerRecord的类,用于保存要发送到集群的键值对。
对于一阶近似,这是生成消息所需的所有API表面积。在幕后,该库正在管理连接池、网络缓冲、等待代理确认消息、在必要时重新传输消息,以及应用程序程序员不需要关心的许多其他细节。
Kafka Consumers
使用消费者API与使用生产者API在原则上是相似的。您使用一个名为KafkaConsumer的类连接到集群(传递一个配置映射来指定集群的地址、安全性和其他参数)。然后使用该连接订阅一个或多个主题。当这些主题上的消息可用时,它们会以一个名为ConsumerRecords的集合返回,该集合包含以ConsumerRecord对象形式出现的消息的各个实例。一个ConsumerRecord对象代表单个Kafka消息的键/值对。
KafkaConsumer像KafkaProducer一样管理连接池和网络协议,但是在读取端有一个比网络管道更大的故事。首先,Kafka与传统消息队列的不同之处在于,读取消息不会破坏它;它仍然在那里,供任何其他可能对它感兴趣的消费者阅读。事实上,在Kafka中,许多消费者从一个主题中读取是非常正常的。这个小事实对围绕Kafka出现的各种软件架构产生了积极的不成比例的影响,这是一个在其他地方讨论得很好的主题。
此外,消费者还需要能够处理这样的场景:来自主题的消息消耗率与处理单个消息的计算成本一起过高,以致于应用程序的单个实例无法跟上。也就是说,消费者需要扩大规模。在Kafka中,扩展消费者组或多或少是自动的。
Kafka Components and Ecosystem
如果您拥有的只是代理(brokers),它们管理分区的、复制的主题,以及不断增长的生产者和消费者写入和读取事件的集合,那么您实际上拥有了一个非常有用的系统。然而,Kafka社区的经验是,某些模式将会出现,这些模式将鼓励你和你的同事围绕核心Kafka一遍又一遍地构建相同的功能。
您最终将构建通用的应用程序功能层,以重复某些无差别的任务。这些代码可以完成重要的工作,但与您实际从事的业务没有任何关系。它不会直接为你的客户带来价值。它是基础设施,应该由社区或基础设施供应商提供。
您可能很想自己编写这段代码,但您不应该这样做。Kafka Connect、Confluent Schema Registry、Kafka Streams和ksqlDB都是这类基础架构代码的例子。我们将依次看一下它们。
Kafka Connect
在信息存储和检索(retrieval)的世界里,有些系统不是Kafka。有时你希望其他系统中的数据进入Kafka主题,有时你希望Kafka主题中的数据进入这些系统。作为Apache Kafka的集成API,这正是Kafka Connect所做的。
What Does Kafka Connect Do?
一方面,Kafka Connect是一个可插拔(pluggable)连接器生态系统,另一方面,它是一个客户端应用程序。
作为一个客户端应用程序,Connect是一个服务器进程,运行在独立于Kafka代理本身的硬件上。它是可扩展的和容错的,这意味着你可以运行的不是一个单一的连接工作者,而是一个连接工作者集群,共享数据从外部系统进出Kafka的负载。Kafka Connect也将代码的业务从用户中抽象出来,只需要JSON配置就可以运行。例如,以下是如何将数据从Kafka流到Elasticsearch:
{
"connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
"topics" : "my_topic",
"connection.url" : "http://elasticsearch:9200",
"type.name" : "_doc",
"key.ignore" : "true",
"schema.ignore" : "true"
}
Benefits of Kafka Connect
Kafka Connect的主要优势之一是其庞大的连接器生态系统。编写代码将数据移动到云blob存储,或写入到Elasticsearch,或将记录插入到关系数据库中,这些代码不太可能因业务而异。同样,从关系数据库、Salesforce或遗留的HDFS文件系统读取数据,无论何种应用程序执行,都是相同的操作。你当然可以编写这样的代码,但花时间这样做不会给你的客户增加任何独特的价值,也不会让你的业务更具独特的竞争力。
所有这些都是Confluent Hub中提供的Kafka连接器的示例,Confluent Hub收集了各种类型的连接器,最重要的是,所有许可和支持级别。有些是商业许可,有些可以免费使用。Connect Hub允许您搜索各种类型的源连接器和汇聚连接器,并清楚地显示每个连接器的许可证。当然,连接器不必来自Hub,可以在GitHub或市场上的其他地方找到。如果您仍然找不到满足您需要的连接器,那么您可以使用一个相当简单的API编写自己的连接器。
现在,自己构建这种功能似乎很简单:如果外部源系统很容易读取,那么从中读取并生成目标主题就足够容易了。如果外部接收系统很容易写入,那么从主题中消费并写入该系统也会很容易。但是出现了许多复杂性,包括如何处理故障转移、水平扩展、管理入站或出站数据上的常见(commonplace)转换操作、分发公共连接器代码、通过标准接口配置和操作这些操作等等。
Connect表面上看起来很简单,但实际上它本身就是一个复杂的分布式系统和插件生态系统。如果插件生态系统碰巧没有你需要的东西,开源的Connect框架可以让你很容易地构建自己的连接器,并继承Connect提供的所有可伸缩性和容错属性。
Schema Registry
一旦应用程序忙于向Kafka生成消息并从Kafka消费消息,就会发生两件事。首先,现有topics的新消费者将会出现。这些是全新的应用程序——可能是由编写消息原始生产者的团队编写的,也可能是由另一个团队编写的——需要理解主题中消息的格式。其次,这些消息的格式将随着业务的发展而发展。订单对象获得一个新的状态字段,用户名从全名拆分为姓和名,等等。我们的领域对象的模式是一个不断移动的目标,我们必须有一种方法来就任何给定主题中的消息模式达成一致。
Confluent Schema Registry 的存在就是为了解决这个问题。
What Is Schema Registry?
Schema Registry是一个独立的服务器进程,运行在Kafka代理之外的机器上。它的工作是维护一个数据库,其中包含所有已写入它所负责的集群中的主题的模式。这个“数据库”被持久化在Kafka的内部主题中,并缓存在Schema Registry中以实现低延迟访问。Schema Registry可以在冗余的、高可用性的配置中运行,因此如果一个实例失败,它仍然保持正常。
Schema Registry也是一个API,它允许生产者和消费者预测他们将要产生或消费的消息是否与以前的版本兼容。当生产者被配置为使用模式注册中心时,它调用模式注册中心REST端点上的API,并显示新消息的模式。如果它与生成的最后一条消息相同,则生成可能成功。如果它与最后一条消息不同,但与为主题定义的兼容性规则匹配,则生成仍然可能成功。但是,如果它在某种程度上违反了兼容性规则,则产品将以应用程序代码可以检测到的方式失败。
同样,在消费者端,如果消费者从消费者代码期望的版本中读取具有不兼容模式的消息,schema Registry将告诉它不要使用该消息。Schema Registry并没有完全自动化模式演化的问题——这在任何系统中都是一个挑战,无论使用什么工具——但是它通过尽可能地防止运行时故障的发生,使一个困难的问题变得容易得多。
看看我们到目前为止所涵盖的内容,我们已经有了一个用于持久存储事件的系统、读写这些事件的能力、一个数据集成框架,甚至还有一个用于管理不断发展的模式的工具。剩下的是流处理的纯计算方面。
Kafka Streams
在不断增长的基于kafka的应用程序中,消费者的复杂性往往会增加。一开始可能只是简单的无状态转换(例如,屏蔽个人识别信息或更改消息格式以符合内部模式需求),但很快就演变成复杂的聚合、丰富(enrichment)等。如果您回忆一下我们上面看到的消费者代码,就会发现该API中对这些操作的支持并不多:您将不得不构建大量框架代码来处理时间窗口、延迟到达的消息、查找表、按键聚合等等。一旦你明白了这一点,回想一下,像聚合和富集这样的操作通常是有状态的。
该“状态”将是程序堆中的内存,这意味着它是容错责任。如果您的流处理应用程序宕机,它的状态也会随之而去,除非您设计了一种方案来在某处持久化该状态。这类东西的编写和大规模调试非常(fiendishly)复杂,而且对改善用户的生活没有任何直接作用。这就是为什么Apache Kafka提供了一个流处理API。这就是为什么我们有Kafka Streams。
What Is Kafka Streams?
Kafka Streams是一个Java API,它可以让你轻松访问流处理的所有计算原语(computational primitives of stream processing):过滤、分组、聚合、连接等等,让你不必在消费者API之上编写框架代码来完成所有这些事情。它还支持流处理计算可能产生的大量状态。如果按照具有许多惟一值的字段对高吞吐量主题中的事件进行分组,然后每小时对该组计算一次上滚,则可能需要使用大量内存。
实际上,对于大容量主题和复杂的流处理拓扑,不难想象您需要像普通消费者组那样部署共享流处理工作负载的机器集群。Streams API通过处理所有分布式状态问题为你解决了这两个问题:它将状态持久化到本地磁盘和Kafka集群的内部主题,当向集群中添加或删除流处理节点时,它会自动在流处理集群中的节点之间重新分配状态。
在典型的微服务中,流处理是应用程序在其他功能之外所做的一件事。例如,发货通知服务可以将发货事件与包含客户记录的产品信息变更日志中的事件结合起来,生成发货通知对象,其他服务可以将其转换为电子邮件和文本消息。但是,在呈现显示给定货物状态的视图时,该装运通知服务也可能有义务公开一个REST API,以便由移动应用程序或web前端进行同步键查找。
该服务对事件做出反应——在本例中,将三个流连接在一起,并可能对连接的结果执行其他窗口计算——但它也针对其REST端点服务HTTP请求,可能使用Spring Framework或Micronaut或其他常用的Java API。因为Kafka Streams是一个Java库,而不是一组专门做流处理的基础设施组件,所以使用其他框架来完成其他端(如REST端点)和复杂的、可扩展的、容错的流处理是微不足道的。