《Kafka权威指南》阅读笔记
序
- Kafka 是一个流平台:在这个平台上可以发布和订阅数据流,并把它们保存起来进行处理。
- Kafka经常会被拿来与现有的技术作比较:企业级消息系统、大数据系统(如Hadoop)和数据集成或ETL工具。这里的每一项比较都有一定的道理,但也有失偏颇。
- 作为一个现代的分布式系统,Kafka 以集群的方式运行,Kafka 集群并不是一组独立运行的broker,它可以自由伸缩,处理公司的所有应用程序的数据流。其次,Kafka 可以按照你的要求存储数据,保留多长时间完全可以由你来决定。作为数据连接层,Kafka 提供了数据传递保证——可复制、持久化。
- Kafka 可以看做是实时版的Hadoop。Hadoop可以存储和定期处理大量的数据文件,而 Kafka 可以存储和持续的处理大型的数据流。Hadoop 和大数据主要应用在数据分析上,而 Kafka 因其低延迟的特点更适合用在核心的业务应用上。
- Kafka 和 ETL 工具都擅长移动数据,不过 Kafka 并非只是把数据从一个系统拆解出来再塞进另一个系统,它还可以将现有的应用程序和数据系统连接起来,加强这些触发相同数据流的应用,这是以数据流为中心的架构。
- 除了3、4、5的不同点之外,对于那些习惯了开发请求与响应风格应用和关系型数据库的人来说,学会基于持续数据流构建应用程序也是一个巨大的思维转变。
前言
- Kafka 的一些典型应用场景:用于事件驱动的微服务系统的消息总线、流式应用和大规模数据管道。
第1章 初识 Kafka
- 花费越少的精力在数据移动上,就越能专注于核心业务。这就是为什么在一个以数据为驱动的企业里,数据管道会成为关键性组件。如何移动数据,几乎变得与数据本身一样重要。
- 发布与订阅消息系统:发布与订阅系统一般会有一个 broker,也就是发布消息的中心点。
- 业务中有很多的场景需要用到消息系统。此时,你真正需要的是一个单一的集中式系统,它可以用来发布通用类型的数据,其规模可以随着公司业务的增长而增长。
- Kafka 是一款基于发布与订阅的消息系统。它一般被称为 “分布式提交日志”或者“分布式流平台”。Kafka 的数据是按照一定顺序持久化保存的,可以按需读取。此外,Kafka 的数据分布在整个系统里,具备数据故障保护和性能伸缩能力。
消息和批次
- Kafka 的数据单元被称为消息。消息由字节数组组成,所以 对于 Kafka 来说,消息里的数据没有特别的格式或含义。消息可以有一个可选的元数据, 也就是键。键也是一个字节数组,与消息一样,对于 Kafka 来说也没有特殊的含义。当消息以一种可控的方式写入不同的分区时,会用到键。最简单的例子就是为键生成一个一致性散列值,然后使用散列值对主题分区数进行取模,为消息选取分区。这样可以保证具有相同键的消息总是被写到相同的分区上。
- 为了提高效率,消息被分批次写入 Kafka。批次就是一组消息,这些消息属于同一个主题和分区。把消息分成批次传输可以减少网络开销。不过,这要在时间延迟和吞吐量之间作出权衡。
模式
- 消息模式(schema)有许多可用的选项。像 JSON 和 XML 这些简单的系统,不仅易用,而且可读性好。不过,它们缺乏强类型处理能力,不同版本之间的兼容性也不是很好。Kafka 的许多开发者喜欢使用 Apache Avro,它最初是为 Hadoop 开发的一款序列化框架。Avro 提供了一种紧凑的序列化格式,模式和消息体是分开的,当模式发生变化时,不需要重新生成代码;它还支持强类型和模式进化,其版本既向前兼容,也向后兼容。
- 数据格式的一致性对于 Kafka 来说很重要,它消除了消息读写操作之间的耦合性。
主题和分区
- Kafka 的消息通过主题(Topic)进行分类。一个主题可以被分为若干个分区,消息以追加的方式写入分区,然后以先入先出的顺序读取。要注意,由于一个主题一般包含几个分区,因此无法在整个主题范围内保证消息的顺序,但可以保证消息在单个分区内的顺序。
- Kafka 通过分区来实现数据冗余和伸缩性。分区可以分布在不同的服务器上,也就是说,一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能。
- 生产者和消费者:Kafka 的客户端被分为两种基本类型:生产者和消费者。除此之外,还有其它高级客户端 API——用于数据集成的 Kafka Connect API 和用于流式处理 的 Kafka Streams。这些高级客户端 API 使用生产者和消费者作为内部组件,提供了高级的功能。
- 生产者创建消息。一般情况下,一个消息会被发布到一个特定的主题上。生产者在默认情况下把消息均衡地分布到主题的所有分区上,而并不关心特定消息会被写到哪个分区。
- 不过,在某些情况下,生产者会把消息直接写到指定的分区。这通常是通过消息键和分区器来实现的,分区器为键生成一个散列值,并将其映射到指定的分区上。这样可以保证包含同一个键的消息会被写到同一个分区上。生产者也可以使用自定义的分区器,根据不同的业务规则将消息映射到分区。
- 消费者读取消息。消费者订阅一个或多个主题,并按照消息生成的顺序读取它们。消费者通过检查消息的偏移量来区分已经读取过的消息。偏移量是另一种元数据,它是一个不断递增的整数值,在创建消息时,Kafka 会把它添加到消息里。在给定的分区里,每个消息的偏移量都是唯一的。消费者把每个分区最后读取的消息偏移量保存在 Zookeeper 或 Kafka 上,如果消费者关闭或重启,它的读取状态不会丢失。
- 消费者是消费者群组(Group)的一部分,也就是说,会有一个或多个消费者共同读取一个主题。群组保证每个分区只能被一个消费者使用。如果一个消费者失效,群组里的其他消费者可以接管失效消费者的工作。
broker和集群
- 一个独立的 Kafka 服务器被称为 broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。
- 根据特定的硬件及其性能特征,单个 broker 可以轻松处理数千个分区以及每秒百万级的消息量。
- broker 是集群的组成部分。每个集群都有一个 broker 同时充当了集群控制器的角色。
- 一个分区可以分配给多个 broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余。
保留消息
- 保留消息(在一定期限内)是 Kafka 的一个重要特性。Kafka broker 默认的消息保留策略是这样的:要么保留一段时间(比如 7 天),要么保留到消息达到一定大小的字节数(比 如 1GB)。当消息数量达到这些上限时,旧消息就会过期并被删除。所以在任何时刻,可用消息的总量都不会超过配置参数所指定的大小。
- Topic可以配置自己的保留策略,可以将消息保留到不再使用它们为止。
- 可以通过配置把Topic当作紧凑型日志,只有最后一个带有特定键的消息会被保留下来。这种情况对于变更日志类型的数据来说比较适用,因为人们只关心最后时刻发生的那个变更。
多集群
- Kafka 的消息复制机制只能在单个集群里进行,不能在多个集群之间进行。
- Kafka 提供了一个叫作 MirrorMaker 的工具,可以用它来实现集群间的消息复制。 MirrorMaker 的核心组件包含了一个生产者和一个消费者,两者之间通过一个队列相连。消费者从一个集群读取消息,生产者把消息发送到另一个集群上。
为什么选择Kafka
- 多个生产者
- 多个消费者
- 基于磁盘的数据存储
- 伸缩性
- 高性能
数据生态系统
使用场景
- 活动跟踪
- 传递消息
- 度量指标和日志记录
- 提交日志
- 流处理
起源故事
- Kafka 使用 Avro 作为消息序列化框架,每天高效地处理数十亿级别的度量指标和用户活动跟踪信息。
第2章 安装Kafka
安装 Kafka 所需环境
- 建议在Linux平台上先后安装Java8、Zookeeper、Kafka。
- Kafka 使用 Zookeeper 保存集群的元数据信息和消费者信息。
- Kafka 发行版也自带了Zookeeper,可以直接从脚本启动。
- 可以连到 Zookeeper 端口上,通过发送命令 srvr 来验证 Zookeeper 是否安装正确:
# telnet localhost 2181
srvr
- Zookeeper 使用的是一致性协议,所以建议每个群组里应该包含奇数个节点(比如 3 个、5 个等),因为只有当群组里的大多数节点(也就是法定人数))处于可用状态,Zookeeper 才能处理外部的请求。即如果你有一个包含 3 个节点 的群组,那么它允许1个节点失效。如果群组包含 5 个节点,那么它允许 2 个节点失效。
- 不过,也不建议一个群组包含超过 7 个节点,因为 Zookeeper 使用了一致性协议,节点过多会降低整个群组的性能。
验证安装结果
- 一旦 Kafka 创建完毕,就可以对这个集群做一些简单的操作来验证它是否安装正确,比如创建一个测试主题,发布一些消息,然后读取它们。
- 创建并验证主题:
# /usr/local/kafka/bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
Created topic "test".
# /usr/local/kafka/bin/kafka-topics.sh --zookeeper localhost:2181 --describe --topic test
Topic:test PartitionCount:1 ReplicationFactor:1 Configs:
Topic: test Partition: 0 Leader: 0 Replicas: 0 Isr: 0
#
- 往测试主题上发布消息:
# /usr/local/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
Test Message 1
Test Message 2
^D #
- 从测试主题上读取消息:
# /usr/local/kafka/bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning
Test Message 1
Test Message 2
^C
Consumed 2 messages
#
配置
- Zookeeper监听 2181 端口。
- Kafka监听 9092 端口。
- 单个broker对分区个数是有限制的,因为分区越多,占用的内存就越多,完成Leader选举需要的时间也越长。
- 估算出Topic的吞吐量和消费者吞吐量,可以用主题吞吐量除以消费者吞吐量算出分区的个数。如果不知道这些信息,那么根据经验,把分区的大小限制在 25GB 以内可以得到比较理想的效果。
- Kafka 通常根据时间来决定数据可以被保留多久。另一种方式是通过保留的消息字节数来判断消息是否过期。如果同时指定了这两个条件,那只要任意一个条件得到满足,消息就会被删除。
- 一个生产者可以写往一个主题的多个分区,一个消费者可以消费一个主题的多个分区。
- 建议使用最新版本的 Kafka,让消费者把偏移量提交到 Kafka 服务器上,消除对 Zookeeper 的依赖。
- 虽然多个 Kafka 集群可以共享一个 Zookeeper 群组,但如果有可能的话,不建议把 Zookeeper 共享给其他应用程序。Kafka 对 Zookeeper 的延迟和超时比较敏感,与 Zookeeper 群组之间的一个通信异常就可能导致 Kafka 服务器出现无法预测的行为。
第3章 Kafka生产者——向Kafka写入数据
客户端
- 除了内置的客户端外,Kafka 还提供了二进制连接协议,也就是说,我们直接向 Kafka 网络端口发送适当的字节序列,就可以实现从 Kafka 读取消息或 往 Kafka 写入消息。
生产者概览
发送消息的3种方式
- 发送并忘记(fire-and-forget)
- 同步发送
- 异步发送
KafkaProducer一般会发生两类错误
- 可重试错误
这类错误可以通过重发消息来解决,比如对于连接错误,可以通过再次建立连接来解决。 - 无法通过重试解决
比如“消息太大”异常。
异步发送消息
- 在大多数时候,我们并不需要等待响应——尽管 Kafka 会把目标主题、分区信息和消息的偏移量发送回来,但对于发送端的应用程序来说不是必需的。
- 但是,在遇到消息发送失败时,我们需要抛出异常、记录错误日志,或者把消息写入 “错误消息”文件以便日后分析。
- 为了在异步发送消息的同时能够对异常情况进行处理,生产者提供了回调支持。
序列化器
- 如果同一个公司的不同团队都需要往 Kafka 写入某一类数据,那么他们就需要使用相同的序列化器,如果序列化器发生改动,他们几乎要在同一时间修改代码。
- 我们不建议使用自定义序列化器,而是使用已有的序列化器和反序列化器,比如 Json、Avro、Thrift 或 Protobuf。
分区
- ProducerRecord 对象包含了目标主题、键和值。Kafka 的消息是一个个的键值对,ProducerRecord 对象可以只包含目标主题和值,键可以设置为默认的 null,不过大多数应用程序会用到键。
- 键有两个用途:可以作为消息的附加信息,也可以用来决定消息该被写到主题的哪个分区。拥有相同键的消息将被写到同一个分区。
- 如果键值为 null,并且使用了默认的分区器,那么记录将被随机地发送到主题内各个可用的分区上。
- 分区器使用轮询(Round Robin)算法将消息均衡地分布到各个分区上。
- 如果键不为空,并且使用了默认的分区器,那么 Kafka 会对键进行散列(使用 Kafka 自己的散列算法,即使升级 Java 版本,散列值也不会发生变化),然后根据散列值把消息映射到特定的分区上。这里的关键之处在于,同一个键总是被映射到同一个分区上,所以在进行映射时,我们会使用主题所有的分区,而不仅仅是可用的分区。这也意味着,如果写入数据的分区是不可用的,那么就会发生错误。但这种情况很少发生。
- 只有在不改变主题分区数量的情况下,键与分区之间的映射才能保持不变。
- 如果要使用键来映射分区,那么最好在创建主题的时候就把分区规划好,而且永远不要增加新分区。
第4章:Kafka消费者——从Kafka读取数据
KafkaConsumer概念
- 应用程序使用 KafkaConsumer 向 Kafka 订阅主题,并从订阅的主题上接收消息。
消费者和消费者群组
- Kafka 消费者从属于消费者群组。一个群组里的消费者订阅的是同一个主题,每个消费者接收主题一部分分区的消息。
- 如果群组里的消费者数量,超过了主题的分区数量,那么有一部分消费者就会被闲置,不会接收到任何消息。
- 往群组里增加消费者是横向伸缩消费能力的主要方式。Kafka 消费者经常会做一些高延迟的操作,比如把数据写到数据库或 HDFS,或者使用数据进行比较耗时的计算。在这些情况下,单个消费者无法跟上数据生成的速度,所以可以增加更多的消费者,让它们分担负载,每个消费者只处理部分分区的消息,这就是横向伸缩的主要手段。
- 我们有必要为主题创建大量的分区,在负载增长时可以加入更多的消费者。不过要注意,不要让消费者的数量超过主题分区的数量,多余的消费者只会被闲置。
- 对于多个应用程序需要从同一个主题读取数据的情况。在这些场景里,每个应用程序可以获取到所有的消息, 而不只是其中的一部分。只要保证每个应用程序有自己的消费者群组,就可以让它们获取到主题所有的消息。
- 不同于传统的消息系统,横向伸缩 Kafka 消费者和消费者群组并不会对性能造成负面影响。
消费者群组和分区再均衡
- 分区的所有权从一个消费者转移到另一个消费者,这样的行为被称为再均衡。
- 消费者通过向被指派为群组协调器的 broker(不同的群组可以有不同的协调器)发送心跳来维持它们和群组的从属关系以及它们对分区的所有权关系。只要消费者以正常的时间间隔发送心跳,就被认为是活跃的,说明它还在读取分区里的消息。消费者会在轮询消息(为了获取消息)或提交偏移量时发送心跳。如果消费者停止发送心跳的时间足够长,会话就会过期,群组协调器认为它已经死亡,就会触发一次再均衡。
提交和偏移量
- 消费者可以使用 Kafka 来追踪消息在分区里的位置(偏移量)。
- 我们把更新分区当前位置的操作叫作提交。
- 那么消费者是如何提交偏移量的呢?消费者往一个叫作 _consumer_offset 的特殊主题发送消息,消息里包含每个分区的偏移量。
- 如果消费者一直处于运行状态,那么偏移量就没有什么用处。不过,如果消费者发生崩溃或者有新的消费者加入群组,就会触发再均衡,完成再均衡之后,每个消费者可能分配到新的分区,而不是之前处理的那个。为了能够继续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。
- 如果提交的偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理。
- 如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。
KafkaConsumer API 提供了很多种方式来提交偏移量
- 自动提交
- 提交当前偏移量
- 异步提交
- 提交特定的偏移量
再均衡监听器
- 在提交偏移量一节中提到过,消费者在退出和进行分区再均衡之前,会做一些清理工作。
从特定偏移量处开始处理记录
如何退出
- 在之前讨论轮询时就说过,不需要担心消费者会在一个无限循环里轮询消息,我们会告诉
消费者如何优雅地退出循环。
反序列化器
- 在之前的章节里提到过,生产者需要用序列化器把对象转换成字节数组再发送给 Kafka。 类似地,消费者需要用反序列化器把从 Kafka 接收到的字节数组转换成 Java 对象。
独立消费者——为什么以及怎样使用没有群组的消费者
- 你可能只需要一个消费者从一个主题的所有分区或者某个特定的分区读取数据。这个时候就不需要消费者群组和再均衡了,只需要把主题或者分区分配给消费者,然后开始读取消息并提交偏移量。
- 如果是这样的话,就不需要订阅主题,取而代之的是为自己分配分区。一个消费者可以订 阅主题(并加入消费者群组),或者为自己分配分区,但不能同时做这两件事情。
第5章 深入 Kafka
第6章 可靠的数据传递
第7章 构建数据管道
第8章 跨集群数据镜像
第9章 管理Kafka
主题操作
- 使用 kafka-topics.sh 工具可以执行主题的大部分操作,可以用它创建、修改、删除和查看集群里的主题。
- 要使用 kafka-topics.sh 工具的全部功能,需要通过 --zookeeper 参数提供 Zookeeper 的连接字符串。
创建主题
第10章 监控Kafka
第11章 流式处理
封底
- 应用程序会产生各种数据:日志消息、度量指标、用户活动记录、响应消息等,这些数据都可以通过Kafka进行移动。
- Kafka是在流式平台上处理实时数据的利器。