Kafka入门简介
一、Kafka 简介
Kafka 创建背景
Kafka 是一个消息系统,原本开发自 LinkedIn,用作 LinkedIn 的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。现在它已被多家不同类型的公司作为多种类型的数据管道和消息系统使用。
活动流数据是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分。活动数据包括页面访问量(Page View)、被查看内容方面的信息以及搜索情况等内容。运营数据指的是服务器的性能数据(CPU、IO 使用率、请求时间、服务日志等等数据)。
Kafka 简介
Kafka 是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下:
- 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间复杂度的访问性能
- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输
- 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消息顺序传输
- 同时支持离线数据处理和实时数据处理
- Scale out:支持在线水平扩展
Kafka 基础概念
概念一:生产者与消费者
对于 Kafka 来说客户端有两种基本类型:生产者(Producer)和消费者(Consumer)。除此之外,还有用来做数据集成的 Kafka Connect API 和流式处理的 Kafka Streams 等高阶客户端,但这些高阶客户端底层仍然是生产者和消费者 API。
- 生产者(发布者)创建消息
- 消费者(订阅者)负责消费/读取消息
概念二:主题(Topic)与分区(Partition)
在 Kafka 中,消息以主题(Topic)来分类,每一个主题都对应一个"消息队列"。
如果把所有同类的消息都塞入到一个"中心"队列中,势必缺少可伸缩性。我们引入**分区(Partition)**的概念,类似"多修几条道"的方式对主题完成水平扩展。
概念三:Broker 和集群(Cluster)
一个 Kafka 服务器也称为 Broker,它接受生产者发送的消息并存入磁盘;同时服务消费者拉取分区消息的请求,返回目前已经提交的消息。
若干个 Broker 组成一个集群(Cluster),其中集群内某个 Broker 会成为集群控制器(Cluster Controller),它负责管理集群,包括分配分区到 Broker、监控 Broker 故障等。
Kafka 的一个关键性质是日志保留(retention),可以配置主题的消息保留策略,譬如只保留一段时间的日志或者只保留特定大小的日志。
概念四:多集群
通常处于以下原因需要多集群:
- 基于数据的隔离
- 基于安全的隔离
- 多数据中心(容灾)
对于多个 Kafka 集群消息同步可以使用 Kafka 提供的 MirrorMaker 工具。本质上,MirrorMaker 只是一个 Kafka 消费者和生产者,并使用一个队列连接起来而已。
二、Kafka 的设计与实现
讨论一:Kafka 存储在文件系统上
Kafka 的消息是存在于文件系统之上的。Kafka 高度依赖文件系统来存储和缓存消息。
实际上,磁盘比人们预想的快很多。现代操作系统针对磁盘的读写已经做了一些优化:预读(提前将较大的磁盘块读入内存)、后写(将很多小的逻辑写操作合并成大的物理写操作)、主内存剩余空间用作磁盘缓存。
顺序写磁盘操作让 Kafka 的效率非常高(顺序写磁盘效率比随机写内存还要高,这是 Kafka 高吞吐率的一个重要保证)。
Topic 其实只是逻辑上的概念,物理上存储的其实是 Partition,每一个 Partition 最终对应一个目录,里面存储所有的消息和索引文件。默认情况下,每一个 Topic 在创建时如果不指定 Partition 数量时只会创建 1 个 Partition。
讨论二:Kafka 中的底层存储设计
在 Kafka 的文件存储中,同一个 Topic 下有多个不同的 Partition,每个 Partition 都为一个目录,而每一个目录又被平均分配成多个大小相等的 Segment File,每个 Segment File 由 .index(索引文件)和 .log(数据文件)组成,他们总是成对出现。
Segment 文件命名规则:Partition 全局的第一个 Segment 从 0 开始,后续每个 Segment 文件名为上一个 Segment 文件最后一条消息的 offset 值。
稀疏索引: Kafka 采取稀疏索引存储的方式,每隔一定字节的数据建立一条索引,减少了索引文件大小,使得能够把 index 映射到内存,降低了查询时的磁盘 IO 开销。
当需要查找一个指定 offset 的 message 时,通过在所有 segment 的文件名中进行二分查找就能找到它归属的 segment,再在其 index 文件中找到其对应到文件上的物理位置。
每一条 message 都包含以下三个属性:
- offset:表示 message 在当前 Partition 中的偏移量
- MessageSize:表示 message 内容 data 的大小
- data:message 的具体内容
讨论三:生产者设计概要
生产者写消息的基本流程:
- 创建一个 ProducerRecord 对象,包含消息的主题(topic)和值(value),可以选择性指定键值(key)或分区(partition)
- 生产者对键值和值序列化成字节数组,然后发送到分配器(partitioner)
- 如果指定了分区,分配器返回该分区;否则基于键值来选择一个分区
- 选择完分区后,将记录添加到相同主题和分区的批量消息中,另一个线程负责发送这些批量消息到对应的 Kafka broker
- 当 broker 接收到消息并成功写入则返回一个 RecordMetadata 对象,否则返回异常
- 生产者接收到结果后,对于异常可能会进行重试
讨论四:消费者设计概要
消费者与消费组
Kafka 消费者是消费组的一部分,当多个消费者形成一个消费组来消费主题时,每个消费者会收到不同分区的消息。
消费者数量不应该比分区数多,因为多出来的消费者是空闲的。
关键特性: 只需写入一次消息,可以支持任意多的应用读取这个消息。每个应用都需要有不同的消费组(Consumer Group)。
消费组与分区重平衡
当新的消费者加入消费组,它会消费一个或多个分区;另外,当消费者离开消费组时,它所消费的分区会分配给其他分区。这种现象称为重平衡(rebalance)。
消费者通过定期发送心跳(heartbeat)到一个作为**组协调者(group coordinator)**的 broker 来保持在消费组内存活。
在 0.10.1 版本,Kafka 对心跳机制进行了修改,将发送心跳与拉取消息进行分离。更高版本的 Kafka 支持配置一个消费者多长时间不拉取消息但仍然保持存活,可以避免活锁(livelock)。
Partition 与消费模型
- Kafka 只会保证在 Partition 内消息是有序的,不保证全局顺序
- 无论消息是否被消费,除非消息到期 Partition 从不删除消息
- Partition 会为每个 Consumer Group 保存一个偏移量,记录 Group 消费到的位置
为什么 Kafka 是 pull 模型?
push 模式很难适应消费速率不同的消费者,而 pull 模式则可以根据 Consumer 的消费能力以适当的速率消费消息。pull 模式可简化 broker 的设计,Consumer 可自主控制消费消息的速率,同时可以选择批量消费或逐条消费。
讨论五:Kafka 如何保证可靠性
Kafka 中的可靠性保证有如下四点:
- 对于一个分区来说,它的消息是有序的
- 当消息写入所有 in-sync 状态的副本后,消息才会认为已提交(committed)
- 一旦消息已提交,那么只要有一个副本存活,数据不会丢失
- 消费者只能读取到已提交的消息