kafka支持两种启动方式
Kafka with KRaft
Kafka with ZooKeeper(4.0后将会被废弃)
目前只有Java客户端是由官方维护的,其他客户端由社区贡献维护。
属性 | 描述 |
---|---|
advertised.listeners | Broker 向外部暴露的网络地址 |
auto.leader.rebalance.enable | 否启用自动的分区 Leader 重新平衡 |
delete.topic.enable | 控制 Kafka 是否允许删除主题 |
log.retention.ms | Kafka 日志保留的时间 |
message.max.bytes | Kafka Broker 能够接收的单条消息的最大字节数 |
log.cleanup.policy | 默认delete,可选范围[compact, delete],compact 压缩,delete删除 |
max.request.size | 限制了生产者发送的单个请求的最大字节数,可以是单个或多个消息 |
buffer.memory | 生产者(Producer)缓冲区的大小 |
batch.size | 用于设置生产者发送消息时,每个批次(Batch)可占用的内存大小(字节) |
linger.ms | 表示消息在生产者缓冲区中的最长滞留时间 |
acks | 0,1,all |
max.in.flight.requests.per.connection | 单个网络连接上,生产者可以同时发送的最大未确认请求数量,控制请求并发量 |
fetch.min.bytes | 消费者期望从Kafka服务器获取的最小数据量(以字节为单位) |
auto.offset.reset | earliest:消费者将从最早的记录开始读取,latest:消费者将从最新的记录开始读取,none:没有可用的偏移量时,消费者将抛出异常。 |
enable.auto.commit | 控制消费者是否自动提交偏移量 |
max.poll.records | 最大拉取数据量 |
磁盘顺序读写:在某些情况下,顺序磁盘访问可能比随机内存访问更快!
充分利用操作系统的pagecache机制:kafka是运行在JVM上的,而Java内存使用情况存在如下问题:
因此kafka提供了一种简单的设计:我们不是尽可能多地在内存中保存数据,然后在空间不足时将其全部刷新到文件系统中,而是将其反转。所有数据都会立即写入文件系统上的持久日志中,而不必刷新到磁盘。实际上,这仅意味着它被传输到内核的页面缓存中(pagecache)
批量处理(异步发送):批处理可以带来更大的网络数据包、更大的连续磁盘操作、连续的内存块等等,所有这些都允许 Kafka 将突发的随机消息写入流转换为流向消费者的线性写入
零拷贝(sendfile):kafka维护了通用的消息格式,使用sendfile系统调用完成从页面缓存(pagecache)直接发送到NIC缓冲区,避免了多次的复制和系统调用
1、传统的数据复制流程
2、sendfile
3、sendfile+DMA scatter/gather
这种 pagecache 和 sendfile 的组合意味着在 Kafka 集群中,当消费者大部分都忙于处理数据时,您将看不到磁盘上的任何读取活动,因为它们将完全从缓存中提供数据。
端到端批量压缩: kafka支持批处理数据进行压缩,并将保持压缩状态并保存在日志中,并且还将以压缩形式传输给消费者。消费者将解压缩收到的任何压缩数据。Kafka 支持 GZIP、Snappy、LZ4 和 ZStandard 压缩协议
分区:分区是Kafka存储消息的基本单位,也是消息并行处理的基础
轮询策略(Round-robin):默认情况下,如果没有指定分区,Kafka 生产者会使用轮询策略,将消息依次发送到所有可用的分区。
键控分区策略(Key-based Partitioning):如果消息带有一个键(key),Kafka 生产者会根据这个键的哈希值将消息映射到一个固定的分区。相同键的消息总是会被发送到同一个分区,保证了消息的顺序性。
自定义分区器(Custom Partitioners):用户可以实现自定义的分区器逻辑,根据业务需求对消息进行分区,以实现更细粒度的负载均衡控制。
分区再均衡(Rebalancing):当消费者组中的成员数量发生变化(如增加或减少消费者实例)时,Kafka 会触发分区再均衡操作。再均衡过程中,Kafka 会重新分配分区,以确保每个消费者实例获得相对均衡的分区。
再均衡协调器(Rebalance Coordinator):在 Kafka 中,每个消费者组都有一个协调器节点,负责管理组成员列表和分区分配。协调器通过触发再均衡来调整负载均衡状态。
静态成员(Static Membership):Kafka 2.3 及以上版本引入了静态成员概念,允许消费者在重启或故障恢复后继续使用先前分配的分区,而不需要立即触发再均衡。这可以减少不必要的分区移动,提高系统的稳定性。
通过生产者重试,retries参数,消息确认 acks实现
kafka主要通过事务+幂等实现Exactly once语义
实现精确一次语义的完整过程
生产者初始化时,Kafka 分配一个唯一的 Producer ID(PID)。
在事务内,生产者将消息发送到多个分区,并在每个分区中使用幂等性机制(通过序列号)确保单个分区内的消息不会重复。
当所有消息发送完毕后,生产者提交事务,Kafka 会确保所有分区的数据要么全部可见(事务提交),要么全部不可见(事务中止)。
消费者配置为 isolation.level=read_committed,确保只消费已提交事务的数据。
这避免了消费者读到未提交或回滚的消息,从而实现精确一次的消费。
Kafka 中的每个分区都有一个leader和零个或多个followers。包括leader在内的副本总数构成复制因子。
ISR:kafka维护了一个同步副本集"in sync" replicas(ISR)。在ISR列表的brokers必须满足两个条件
1)保持活跃状态(控制器通过心跳检测),对于kraft集群通过broker.session.timeout.ms控制心跳超时时间,对于zookeeper集群通过zookeeper.session.timeout.ms控制;2)数据不能落后leader太多(通过replica.lag.time.max.ms控制,无法在该配置设置的最大时间内赶上领导者日志末尾的副本将从 ISR 中移除)。 如果不满足这两个条件之一则剔除ISR集合。
kafka leader选举优先会优先选择ISR列表中的副本,通过配置属性unclean.leader.election.enable允许从非ISR列表中选举leader,但这可能会破坏数据的一致性
默认情况下,当 acks=all 时,只要所有当前同步的副本都收到消息,就会立即进行确认。例如,如果某个主题仅配置了两个副本,并且其中一个发生故障(即,仅剩下一个同步副本),则指定 acks=all 的写入将会成功
另外,kafka支持指定最小的ISR大小,仅当 ISR 大小高于某个最小值时,分区才会接受写入,以防止丢失仅写入单个副本(随后不可用)的消息。此设置仅在生产者使用 acks=all 并保证至少这么多同步副本将确认该消息时才生效。 此设置提供了可用性与持久性的平衡,ISR SIZE设置越大一致性越好,但是可用性越低
日志压缩可确保 Kafka 始终在单个主题分区的数据日志中保留每个消息键的至少最后一个已知值。此保留策略可以根据每个主题进行设置,因此单个集群可以有一些按大小或时间强制保留的主题,还有一些按压缩强制保留的主题。
日志压缩由日志清理器处理(log cleaner),它是一组后台线程,用于重新复制日志段文件,删除日志头部出现的键记录。
Kafka 集群能够对请求实施配额,以控制客户端使用的代理资源。Kafka 代理可以对共享配额的每组客户端实施两种类型的客户端配额:
网络带宽配额定义字节速率阈值(自 0.9 起)
请求率配额将 CPU 利用率阈值定义为网络和 I/O 线程的百分比(自 0.11 起)
为什么需要配额?
生产者和消费者可能会以极高的速率生产/消费大量数据或生成请求,从而垄断代理资源,导致网络饱和,并且通常会对其他客户端和代理本身造成 DOS 攻击。配额可以防止这些问题,在大型多租户集群中尤为重要,因为一小部分行为不当的客户端可能会降低行为良好的客户端的用户体验。事实上,当将 Kafka 作为服务运行时,这甚至可以根据商定的合同强制执行 API 限制。
Kafka控制器(Controller)是Kafka集群中的一个核心组件,它扮演着监控和管理Kafka集群的关键角色。控制器指责:
所有的 Kafka Broker 都具备成为控制器的能力,但在任意时间内,整个集群只能有一个控制器。第一个成功在 Zookeeper 中创建 /controller 节点的 Broker 将成为控制器
协调器(Coordinator)是管理消费者组状态的关键组件。每个消费者组在 Kafka 中都有一个协调器,负责处理与消费者组相关的各种任务,如分区分配、消费者心跳监控、提交偏移量等。
协调器作用:
Kafka 的 Rebalance(重新平衡)机制是 Kafka 保证高可用性、负载均衡和数据一致性的关键部分。Kafka 重新平衡是 Kafka 在消费者之间重新分配分区的过程,以确保每个消费者处理的分区数量大致相同
日志文件的格式是一系列“日志条目”;每个日志条目都是一个 4 字节整数N,用于存储消息长度,后面跟着N 个消息字节。每条消息都由一个 64 位整数偏移量唯一标识,该偏移量给出了该消息在该分区上发送到该主题的所有消息流中的起始字节位置。
每个日志文件都以其包含的第一条消息的偏移量命名。因此,创建的第一个文件将是 0000000000000000000000.log
默认情况下,Kafka的日志文件大小是固定的,通常为1GB(可以通过log.segment.bytes参数进行配置)。当日志文件写满后,Kafka会创建一个新的日志文件继续写入。新文件的名称将基于该文件中第一条消息的偏移量。
除了日志文件外,Kafka还会为每个日志文件创建一个索引文件(通常以.index为后缀)。索引文件以偏移量为索引来记录对应的日志文件中的消息偏移量,这有助于快速定位到消息的物理位置。
offset 是 Kafka 消息的逻辑序号,用于消费者识别和定位消息;而 position 是消息在物理存储中的具体字节偏移量。Kafka 通过索引文件将 offset 映射到 position,从而实现快速的消息检索。
分区目录内容:
|── test-topic-0
├── 00000000000000000000.index
├── 00000000000000000000.log
├── 00000000000000000000.timeindex
├── 00000000000000001007.index
├── 00000000000000001007.log
├── 00000000000000001007.snapshot
├── 00000000000000001007.timeindex
├── leader-epoch-checkpoint
查看.index内容,执行:
bin/kafka-run-class.sh kafka.tools.DumpLogSegments --deep-iteration --print-data-log --files /tmp/kafka-logs/quickstart-events-0/00000000000000000000.index 可以看到如下index内容
offset: 0 position: 0
查看.log文件,执行:bin/kafka-run-class.sh kafka.tools.DumpLogSegments --deep-iteration --print-data-log --files /tmp/kafka-logs/quickstart-events-0/00000000000000000000.log
Log starting offset: 0
baseOffset: 0 lastOffset: 0 count: 1 baseSequence: 0 lastSequence: 0 producerId: 0 producerEpoch: 0 partitionLeaderEpoch: 0 isTransactional: false isControl: false deleteHorizonMs: OptionalLong.empty position: 0 CreateTime: 1724817016421 size: 90 magic: 2 compresscodec: none crc: 2848027936 isvalid: true
| offset: 0 CreateTime: 1724817016421 keySize: -1 valueSize: 22 sequence: 0 headerKeys: [] payload: This is my first event
baseOffset: 1 lastOffset: 1 count: 1 baseSequence: 1 lastSequence: 1 producerId: 0 producerEpoch: 0 partitionLeaderEpoch: 0 isTransactional: false isControl: false deleteHorizonMs: OptionalLong.empty position: 90 CreateTime: 1724817020796 size: 91 magic: 2 compresscodec: none crc: 3694315316 isvalid: true
| offset: 1 CreateTime: 1724817020796 keySize: -1 valueSize: 23 sequence: 1 headerKeys: [] payload: This is my second event
baseOffset: 2 lastOffset: 6 count: 5 baseSequence: 2 lastSequence: 6 producerId: 0 producerEpoch: 0 partitionLeaderEpoch: 0 isTransactional: false isControl: false deleteHorizonMs: OptionalLong.empty position: 181 CreateTime: 1724817208866 size: 115 magic: 2 compresscodec: none crc: 2750341639 isvalid: true