Kafka学习(一):文档的读书笔记

125 阅读12分钟

概述

学习Kafka,首先还是要从整体入手。看Kafka集群抽象出了哪些模块,各个模块间是如何交互的,以及整体的流程。然后再去深挖各个点的内核,对系统的影响。

kafka集群部署.jpg

设计

kafka1x.apachecn.org/documentati…

高吞吐量

不要害怕磁盘

Kafka 对消息的存储和缓存严重依赖于文件系统。普遍印象是“磁盘速度慢”,而设计合理的磁盘结构通常可以和网络一样快。

线性的读取和写入是磁盘使用模式中最有规律的,与随机读写性能相差几千倍,并且由操作系统进行了大量的优化。

pagecache

现代操作系统提供了 read-ahead 和 write-behind 技术,read-ahead 是以大的 data block 为单位预先读取数据,而 write-behind 是将多个小型的逻辑写合并成一次大型的物理磁盘写入。

Pagecache 是操作系统内核中的一个机制,用于缓存文件系统的数据。

当应用程序读取文件时,操作系统会将数据加载到内存中的 pagecache 中。如果后续有其他读取请求,操作系统会优先从 pagecache 中读取数据,而不是直接从磁盘读取,这样可以显著提高读取速度和系统性能。

Pagecache 还可以用于写操作。当应用程序写入数据时,数据首先被写入 pagecache,然后在适当的时候(例如,当 pagecache 达到一定大小或系统空闲时)被刷新到磁盘。可以减少磁盘 I/O 操作,提高写入性能。

因为一旦消除了磁盘访问模式不佳的情况,系统性能低下的主要原因就剩下了两个:大量的小型 I/O 操作,以及过多的字节拷贝。

消息块

Kafka的协议是建立在一个 “消息块” 的抽象基础上,合理将消息分组。 这使得网络请求将多个消息打包成一组,而不是每次发送一条消息,从而使整组消息分担网络中往返的开销。Consumer 每次获取多个大型有序的消息块,并由服务端 依次将消息块一次加载到它的日志中。

第二个低效率的操作,字节拷贝,在消息量少时,这不是什么问题。但是在高负载的情况下,影响就不容忽视。为了避免这种情况,Kafka使用 producer ,broker 和 consumer 都共享的标准化的二进制消息格式,这样数据块不用修改就能在他们之间传递。

sendfile

mp.weixin.qq.com/s/t3ZO_cnAa…

pagecache 和 sendfile 的组合使用意味着,在一个kafka集群中,大多数 consumer 消费时,您将看不到磁盘上的读取活动,因为数据将完全由缓存提供。

压缩

在成本压力下,数据传输的瓶颈不是 CPU ,也不是磁盘,而是网络带宽。

Kafka 以高效的批处理格式支持一批消息可以压缩在一起发送到服务器。这批消息将以压缩格式写入,并且在日志中保持压缩,只会在 consumer 消费时解压缩。Kafka 支持 GZIP,Snappy 和 LZ4 压缩协议。

生产者

负载均衡(分布式相关)

生产者直接发送数据到主分区的服务器上,不需要经过任何中间路由。

为了让生产者实现这个功能,所有的 kafka 服务器节点都能响应这样的元数据请求: 哪些服务器是活着的?主题的哪些分区是主分区?分配在哪个服务器上?

客户端控制消息发送数据到哪个分区,这个可以实现随机的负载均衡方式,或者使用一些特定语义的分区函数。 Kafka有提供特定分区的接口让用于根据指定的键值进行hash分区

异步发送(高吞吐相关)

批处理是提升性能的一个主要驱动,为了允许批量处理,kafka 生产者会尝试在内存中汇总数据,并用一次请求批次提交信息。

批处理,不仅仅可以配置指定的消息数量,也可以指定等待特定的延迟时间(如64k 或10ms),这允许汇总更多的数据后再发送,在服务器端也会减少更多的IO操作。 该缓冲是可配置的,并给出了一个机制,通过权衡少量额外的延迟时间获取更好的吞吐量。

Consumer

Pull Based

producer 把数据 push 到 broker,然后 consumer 从 broker 中 pull 数据。

pull-based 系统有一个很好的特性, 那就是当 consumer 速率落后于 producer 时,可以在适当的时间赶上来。还可以通过使用某种 backoff 协议来减少这种现象:即 consumer 可以通过 backoff 表示它已经不堪重负了。

另一个 pull-based 系统的优点在于:它可以大批量生产要发送给 consumer 的数据。consumer 总是将所有可用的(或者达到配置的最大长度)消息 pull 到 log 当前位置的后面,从而使得数据能够得到最佳的处理而不会引入不必要的延迟。

OFFSET !!

consumer 的每个请求都在 log 中指定了对应的 offset,并接收从该位置开始的一大块数据。因此,consumer 对于该位置的控制就显得极为重要,并且可以在需要的时候通过回退到该位置再次消费对应的数据。

消息丢失问题

让 broker 和 consumer 就被消费的数据保持一致性也不是一个小问题。如果 broker 在每条消息被发送到网络的时候,立即将其标记为 consumed,那么一旦 consumer 无法处理该消息(可能由 consumer 崩溃或者请求超时或者其他原因导致),该消息就会丢失。

为了解决消息丢失的问题,许多消息系统增加了确认机制:即当消息被发送出去的时候,消息仅被标记为sent 而不是 consumed;然后 broker 会等待一个来自 consumer 的特定确认,再将消息标记为consumed。

但这种方式又会产生新的问题。

重复消费问题

首先,如果 consumer 处理了消息但在发送确认之前出错了,那么该消息就会被消费两次。

第二个是关于性能的,现在 broker 必须为每条消息保存多个状态(首先对其加锁,确保该消息只被发送一次,然后将其永久的标记为 consumed,以便将其移除)。

还有更棘手的问题要处理,比如如何处理已经发送但一直得不到确认的消息。

offset

Kafka 使用完全不同的方式解决消息丢失问题。

Kafka的 topic 被分割成了一组完全有序的 partition,其中每一个 partition 在任意给定的时间内只能被每个订阅了这个 topic 的 consumer 组中的一个 consumer 消费。

这意味着 partition 中 每一个 consumer 的位置仅仅是一个数字,即下一条要消费的消息的offset。这使得被消费的消息的状态信息相当少,每个 partition 只需要一个数字。这个状态信息还可以作为周期性的 checkpoint。这以非常低的代价实现了和消息确认机制等同的效果。

这种方式还有一个附加的好处。consumer 可以回退到之前的 offset 来再次消费之前的数据,这个操作违反了队列的基本原则,但事实证明对大多数 consumer 来说这是一个必不可少的特性。

at least once

Kafka 默认保证 at-least-once 的消息交付:

At most once——消息可能会丢失但绝不重传。

At least once——消息可以重传但绝不丢失。

Exactly once——这正是人们想要的, 每一条消息只被传递一次.

Replication

partition

Kafka 允许 topic 的 partition 拥有若干副本,你可以在server端配置partition 的副本数量。

这是概述图中比较容易看到的。Topic B是2个分区,也可以是Topic A那样3个分区。Topic A和Topic B都是两副本,也可以是三副本。

创建副本的单位是 topic 的 partition ,正常情况下, 每个分区都有一个 leader 和零或多个 followers 。 总的副本数是包含 leader 的总和。

所有的读写操作都由 leader 处理,一般 partition 的数量都比 broker 的数量多的多,各分区的 leader 均 匀的分布在brokers 中。所有的 followers 节点都同步 leader 节点的日志,日志中的消息和偏移量都和 leader 中的一致。

alive

当集群中的节点出现故障时,能自动进行故障转移,保证数据的可用性。与大多数分布式系统一样,自动处理故障需要精确定义节点 “alive” 的概念:

1、节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接。

2、如果节点是个 follower ,它必须能及时的同步 leader 的写操作,并且延时不能太久。

当然还有 leader 的选举机制raft一致性协议,这部分工作没有涉猎,学习估计很难深入。

partition下的生产和消费

可以更精确地定义, 只有当消息被所有的副本节点加入到日志中时, 才算是提交, 只有提交的消息才会被 consumer 消费, 这样就不用担心一旦 leader 挂掉了消息会丢失。

在所有时间里,Kafka 保证只要有至少一个同步中的节点存活,提交的消息就不会丢失。

投票机制

假设有2f + 1个副本,如果在 leader 宣布消息提交之前必须有f+1个副本收到 该消息,并且如果从这至少f+1个副本之中,有着最完整的日志记录的 follower 里来选择一个新的 leader,那么在故障次数少于f的情况下,选举出的 leader 保证具有所有提交的消息。

但要冗余单点故障需要三份数据,并且要冗余两个故障需要五份的数据。每写5次,对磁盘空间需求是5倍, 吞吐量下降到 1/5,这对于处理海量数据问题是不切实际的。

这可能是为什么 quorum 算法更常用于共享集群配置(如 ZooKeeper ), 而不适用于原始数据存储的原因,例如 HDFS 中 namenode 的高可用是建立在 基于投票的元数据 ,这种代价高昂的存储方式不适用数据本身。

ISR

Kafka 不是用大多数投票选择 leader 。Kafka 动态维护了一个同步状态的备份的集合 (a set of in-sync replicas), 简称 ISR ,在这个集合中的节点都是和 leader 保持高度一致的,只有这个集合的成员才 有资格被选举为 leader,一条消息必须被这个集合 所有 节点读取并追加到日志中了,这条消息才能视为提交。

另一个重要的设计区别是,Kafka 不要求崩溃的节点恢复所有的数据,协议能确保备份节点重新加入ISR 之前,即使它挂时没有新的数据, 它也必须完整再一次同步数据。

当所有的 ISR 副本都挂掉时,会选择一个可能不同步的备份作为 leader ,可以配置属性 unclean.leader.election.enable 禁用此策略

向 Kafka 写数据时,producers 设置 ack 是否提交完成。 0:不等待broker返回确认消息,1: leader保存成功返回或,-1: all,所有备份都保存成功返回

Broker

日志压缩

上面的压缩小节侧重点不同,上面是发送数据的压缩,是为了节省数据传输的成本。而这一节,是日志在Broker中的压缩不过期保存。

日志压缩可确保 Kafka 始终至少为单个 topic partition 的数据日志中的每个 message key 保留最新的已知值。 这样的设计解决了应用程序崩溃、系统故障后恢复或者应用在运行维护过程中重启后重新加载缓存的场景。

日志压缩机制是更细粒度的、每个记录都保留的机制,而不是基于时间的粗粒度。 这个理念是选择性的删除那些有更新的变更的记录的日志。 这样最终日志至少包含每个key的记录的最后一个状态。 image.png

日志压缩还有一些细节,比如如何配置Log Cleaner(负责日志删除)等。

资源配置

Kafka broker 可以对客户端做两种类型资源的配额限制,同一个group的client 共享配额。

1、定义字节率的阈值来限定网络带宽的配额。

2、request 请求率的配额,网络和 I/O线程 cpu利用率的百分比。

因为producers 和 consumers 可能会生产或者消费大量的数据或者产生大量的请求,导致对 broker 资源的垄断,引起网络的饱和,对其他clients和brokers本身造成DOS攻击。

在大型多租户集群中,因为一小部分表现不佳的客户端降低了良好的用户体验,这种情况下非常需要资源的配额保护。

实现

消息

消息通常按照批量的方式写入。record batch 是批量消息的技术术语,它包含一条或多条 records。

baseOffset: int64
batchLength: int32
partitionLeaderEpoch: int32
magic: int8 (current magic value is 2)
crc: int32
attributes: int16
    bit 0~2:
        0: no compression
        1: gzip
        2: snappy
        3: lz4
    bit 3: timestampType
    bit 4: isTransactional (0 means not transactional)
    bit 5: isControlBatch (0 means not a control batch)
    bit 6~15: unused
lastOffsetDelta: int32
firstTimestamp: int64
maxTimestamp: int64
producerId: int64
producerEpoch: int16
baseSequence: int32
records: [Record]

日志

消息是Producer的写入格式,而日志是在Broker中的存储格式。 image.png

总结

有几个核心的问题点:

1、如何提升文件系统使用的性能。

顺序读写、批量读写、pagecache & sendfile、消息压缩等。

2、如何保证消息被正确消费。

pull-based(push-and-pull)模型、offset机制、at-least-once、日志持久化与压缩等。

3、如何自动进行故障转移。

Broker集群的分布式与zk、topic的partition分区、partition的副本、ISR等。