一、Kafka概述
1.1 定义与定位
Apache Kafka 是一个分布式、高吞吐、可扩展的流式数据处理平台,最初由LinkedIn开发,后成为Apache顶级项目。其核心定位是作为高可靠的消息队列(MQ)和实时数据管道,支撑海量数据的生产-消费场景,广泛应用于日志收集、实时监控、事件溯源、流处理(如Flink/Spark Streaming对接)等场景。
1.2 核心特性
-
高吞吐:单机支持百万级消息/秒的读写(依赖分区与批量处理)。
-
低延迟:消息从生产到消费的端到端延迟通常在毫秒级。
-
持久化存储:消息默认持久化到磁盘(非仅内存),支持长期保存(可配置保留时间或大小)。
-
分布式与高可用:通过分区和副本机制实现水平扩展与故障容错。
-
流式能力:支持“消息流”的持续处理(与Kafka Streams、Flink等集成)。
二、Kafka核心架构与组件
Kafka的核心设计围绕**生产者(Producer)、Broker集群、消费者(Consumer)三大角色,结合主题(Topic)、分区(Partition)、副本(Replica)**等抽象概念实现高效通信。
2.1 基础概念
(1)主题(Topic)
-
定义:消息的分类单元,生产者向特定Topic发送消息,消费者从特定Topic订阅消息。 类似消息队列中的“队列名称”或“频道”。
(2)分区(Partition)
-
定义:每个Topic被拆分为多个Partition(分区),是Kafka实现并行处理与扩展性的关键。
-
特点:
-
每个Partition是一个有序、不可变的消息序列(类似日志文件)。
-
消息在Partition内通过**偏移量(Offset)**唯一标识(从0开始的递增整数)。
-
Partition是物理存储的最小单位(数据实际存储在Partition对应的文件中)。
-
-
分区器(partitioner)
-
生产者发送消息时,由分区器决定消息写入哪个分区
-
默认分区策略(DefaultPartitioner)基于消息的key进行分区计算
-
有key的消息:对key进行Murmur2Hash计算,再对分区的总数取模,保证相同key的消息会进入到同一分区
-
无key的消息:默认采取轮询
-
粘性分区:批次写入同一分区,然后再进行轮询
-
-
-
可以通过指定分区号强制写入目标分区
-
自定义分区策略:实现Partitioner接口自定义分区逻辑
-
实现partition方法
-
需要在生产者配置中指定自定义分区器类:props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, ConsistentHashPartitioner.class.getName())
-
特别的,此处介绍一下非常重要的分区算法(分库分表时也会用到)-一致性哈希
-
一致性hash是为了减少节点变动时数据迁移量的算法,特别适用于kafka
-
哈希环:2^ 32,将哈希值空间组织成一个虚拟的环状结构,使得节点和数据更均匀的分布,极大降低了哈希冲突的概率。而且2^32可以更方便的进行计算
-
虚拟节点:不再将物理节点直接映射到哈希环,而是为每个物理节点创建多个虚拟节点,并将虚拟节点散列到换上
-
改善负载均衡:虚拟节点越多,物理节点在换上的分布就越均匀
-
平滑对应节点变动:新增或删除物理节点时可以避免所有数据只向一个相邻节点迁移的压力,使得负载变化更平滑
-
-
分区扩容:
-
新增一个节点(例如 Node X)到环上时,它只会接管环上在它和它逆时针方向上一个节点之间原本属于那个节点的数据。环上其他大部分区间的数据不受影响,因此仅需迁移少量数据
-
-
分区映射:
-
使用hash函数计算每个分区标识符(如Broker id或topic partition id)的哈希值
-
将分区放置到环上:映射到哈希环的对应位置
-
计算数据key的哈希值:映射到环上
-
分区定位:从数据键的哈希值在环上的位置出发,沿顺时针方向寻找第一个遇到的分区节点,该节点即为该数据项应该存储的位置
-
-
-
-
(3)副本(Replica)
-
定义:为保证高可用,每个Partition可以有多个副本(Replica),分布在不同的Broker上。
-
Leader Replica:负责处理所有读写请求(生产者的写入、消费者的读取)。
-
Follower Replica:被动同步Leader的数据,不直接响应请求(仅在Leader宕机时参与选举)。
-
-
作用:通过多副本机制实现数据冗余,避免单点故障。
(4)Broker
-
定义:Kafka集群中的单个服务器节点,负责存储消息、处理生产/消费请求。
-
集群:多个Broker组成分布式集群,共同管理所有Topic的分区。
-
消息存储:接收 Producer 发送的消息,将其持久化到磁盘上的分区日志文件中。Kafka 采用顺序写入和索引机制,因此能实现很高的吞吐性能
-
消息检索:响应 Consumer 的拉取请求,将存储的消息发送给 Consumer。
-
副本同步:每个分区的多个副本(Replica)中,Leader 副本(位于某个 Broker)负责所有读写请求,Follower 副本(位于其他 Broker)则从 Leader 同步数据,提供数据冗余和高可用性
-
参与集群管理:Broker 之间需要协调通信。一个 Broker 会被选举为 Controller,它负责管理分区状态和副本领导选举等集群级操作
。所有 Broker 都需要与协调服务(ZooKeeper 或 KRaft)保持连接,上报状态和获取元数据。
-
(5)生产者(Producer)
-
负责将消息发送到指定的Topic(可指定Partition策略,默认轮询或按Key哈希)。
(6)消费者(Consumer)
-
订阅一个或多个Topic,从Partition中拉取消息(通过Offset记录消费进度)。
-
消费者通常以**消费者组(Consumer Group)**的形式协作。
2.2 核心交互流程
|
阶段 |
角色 |
关键动作 |
数据形态变化 |
|---|---|---|---|
|
发送端 (Producer) |
序列化器 |
将对象转换为字节数组 |
Object→ byte[] |
|
分区器 |
决定消息前往哪个分区(基于Key或轮询) |
通过网络发送 | |
|
Broker |
存储原始的字节数据,不关心其具体含义 |
byte[](存储) | |
|
接收端 (Consumer) |
反序列化器 |
将字节数组转换回对象(需要指定争取的反序列化器和目标对象) |
byte[]→ Object |
|
业务逻辑 |
处理还原后的对象 |
object→计算逻辑 |
-
生产者发送消息:
-
生产者选择目标Topic,并根据分区策略(如轮询、Key哈希)确定消息写入哪个Partition。
-
消息被发送到该Partition的Leader Broker,Leader将消息追加到本地日志文件(持久化),随后异步同步给Follower副本。
-
消息的可靠性保证:
-
kafka通过Producer、Broker和Consumer三端的协同来保障消息的可靠性,实现至少一次(At least once)的投递语义 ,并且可以通过额外配置实现恰好一次(exactly once)
-
producer侧:
-
acks确认机制:控制消息写入的确认,是可靠性的基石
-
acks=0,生产者不等待任何确认,发送即视为成功。吞吐量最高、可靠性最低,服务区丢失消息无感知
-
acks=1(默认):生产者等待leader副本成功写入本地日志后返回确认。平衡性能和可靠性,若leader写入后但Follower还未开始同步前leader宕机,消息会丢失
-
选举新的leader后,数据丢失
-
-
acks=all(-1): 生产者必须等待 Leader 和所有 ISR (In-Sync Replicas) 列表中的 Follower 副本都成功写入消息后才收到确认。提供最高的可靠性,确保只要有一个 ISR 副本存活,消息就不会丢失。这是生产环境要求数据不丢失的推荐配置
-
-
重试机制:配合retries和retry.backoff.ms参数,producer会自动处理可以重试的异常
-
幂等性:避免生产者重试导致的消息重复写入
-
broker已经写入,但是ACK响应丢失,producer重试。可以通过enable.idempotence=true。为对每个producer分配一个唯一id,每个消息会带一个序列号,broker会进行去重,从而实现单分区的精确一次写入
-
要求必须启用acks=all
-
-
事务:实现跨分区和topic的原子性写入,配合消费者可以实现端到端Exactly-once
-
使用kafka事务来保障跨多个分区和topic的原子性写入
-
-
-
Broker侧:
-
副本机制:通过多副本实现数据冗余和高可用
-
每个 Topic 分区配置多个副本(replication.factor> 1,通常至少为 3)。其中一个为 Leader,负责处理读写;其他为 Follower,从 Leader 异步拉取数据进行同步。即使某个 Broker 宕机,其他 Broker 上的副本也能保证数据可用性
-
-
持久化:消息持久化到磁盘
-
:Leader 维护一个同步副本列表 (ISR),指那些与 Leader 数据差落后不多的 Follower。只有 ISR 中的副本才有资格在 Leader 宕机时被选举为新 Leader。消息只有在被所有 ISR 副本都确认接收(acks=all)后才被视为“已提交”,从而保证即使 Leader 宕机,消息也不会丢失。Kafka 的消息最终会持久化到磁盘
-
-
禁用“脏”选举:配置unclean.leader.election.enable=false
-
这意味着如果所有 ISR 副本都不可用,Kafka 不会从非 ISR 副本(这些副本数据可能远落后于原 Leader)中选举新 Leader,从而防止数据丢失。但这会以牺牲该分区的可用性为代价,直到 ISR 副本恢复
-
-
-
consumer:
-
反序列化器:
-
需要获知到目标类型
-
消息头信息:生产者可能将类型信息放入消息头
-
配置默认类型:通过spring.json.value.default.type指定
-
-
如果反序列化器失败,会被ErrorHandingDeserializer捕获(需要配置,否则默认会抛出),防止其向上传播导致消费者容器崩溃
-
异常信息会被序列化后存入消息的springDeserializerExceptionValue头(记录完整的字节错误信息),并将消息的value属性设置为null
-
-
-
手动提交偏移量:避免自动提交可能带来的消息丢失,确保业务消费完成后再提交
-
默认是自动提交的,配置enable.auto.commit=false,至少可以确保至少一次语义,消息绝不会丢,但是可能因重试重复处理
-
需要consumer自己来保证不重复消费信息,业务逻辑设置成幂等的
-
-
-
-
端到端Exactly-once语义(本质上是精确一次处理,需要保障一个消息不写多次(幂等),需要保障一个业务多条消息写多个分区时是原子的)
-
Producer:开启幂等性 (enable.idempotence=true) 并配置 transactional.id
-
保证不会在broker上产生重复消息
-
transaction保证消息原子性写入,并且保证消息的消费和偏移量提交都在同一个事务中完成
-
-
Consumer:
-
配置 isolation.level=read_committed,这样 Consumer 只会读取已提交的事务消息(避免读到 abort 的事务消息)
-
将消费位移的提交也纳入到 Producer 的事务中(消费-处理-生产模式),实现端到端的原子性。这通常需要借助 Kafka Streams 或类似框架来简化实现
-
-
两阶段提交:
-
第一阶段:生产者告知事务协调器(cooridator)准备提交事务,协调器将事务状态标记为“准备提交”并持久化
-
第二阶段:如果所有消息都成功写入,生产者发起提交请求,协调器将事务状态标记为“已提交”,并向所有涉及的分区发送一条控制消息,标记该事务内的所有消息对消费者可见(配置了read commited)。如果任何消息写入失败或者生产者决定终止,那么协调器会把事务标记为“已终止”,该事务内的所有消息对消费者不可见;
-
-
-
-
消费者消费消息:
-
消费者组中的每个消费者负责消费一个或多个Partition(组内分区分配策略避免重复消费)。
-
消费者通过Offset标记已读位置(Offset由消费者自己维护或交由Kafka的__consumer_offsets Topic管理)。
-
-
副本同步与高可用:
-
Follower Replica定期从Leader拉取最新消息,保持与Leader的数据一致。
-
若Leader宕机,Kafka通过Controller(集群协调者,由某个Broker担任)选举新的Leader(从ISR列表中选择,即与Leader同步的Follower集合)。
-
三、关键原理深度解析
3.1 分区与并行化
-
为什么需要分区?
单个Topic的消息若只存储在一个Broker上,会成为性能瓶颈。通过分区,Kafka将Topic的数据分散到多个Broker的不同Partition上,实现:-
写入并行:生产者可同时向多个Partition发送消息(不同Broker处理)。
-
消费并行:消费者组中的不同消费者可同时消费不同Partition(提升吞吐)。
-
-
分区策略:
-
默认策略:轮询(Round Robin)均匀分配消息到各Partition。
-
自定义策略:通过消息的Key(如用户ID)计算哈希值,相同Key的消息固定写入同一Partition(保证顺序性)。
-
3.2 消息存储机制
(1)存储结构
-
每个Partition对应一个分段日志文件(Segment Log),由多个大小固定的日志段(Segment File)组成(默认1GB)。
-
每个Segment包含:
-
.log 文件:实际存储消息的二进制数据(包含Offset、消息体、时间戳等)。
-
.index 文件:记录Offset到日志文件物理位置的映射(加速查找)。
-
.timeindex 文件:按时间戳索引(用于按时间范围查询)。
-
(2)持久化与清理
-
持久化:消息默认持久化到磁盘(非内存),即使Broker重启也不会丢失。
-
清理策略:
-
基于时间:保留最近N小时的消息(如7天)。
-
基于大小:保留最近N GB的数据(超过则删除旧Segment)。
-
压缩策略(Compact):针对Key-Value消息,保留每个Key的最新Value(适用于状态更新场景)。
-
3.3 高可用与副本机制
(1)副本角色与ISR
-
Leader-Follower模型:每个Partition有1个Leader和多个Follower。
-
ISR(In-Sync Replicas):与Leader保持同步的Follower集合(通过心跳和延迟阈值判断)。只有ISR中的Follower才可能被选为新的Leader。
(2)故障恢复流程
-
Leader宕机:Controller检测到Leader失效,从ISR中选举新的Leader(优先选择同步最接近的Follower)。
-
Follower同步延迟:若Follower长时间未追上Leader(超过replica.lag.time.max.ms配置),会被移出ISR,避免影响写入性能。
(3)数据一致性保证
-
Kafka通过ACK机制控制写入可靠性:
-
acks=0:生产者不等待Broker确认(最高吞吐,可能丢数据)。
-
acks=1:Leader写入成功即返回(平衡吞吐与可靠性)。
-
acks=all(或-1):Leader和所有ISR副本均写入成功才返回(最强一致性,但延迟较高)。
-
3.4 消费者组与位移管理
(1)消费者组(Consumer Group)
-
多个消费者可以组成一个组(通过group.id标识),组内消费者共同消费一个Topic的所有Partition(每个Partition只能被组内的一个消费者消费)。
-
优势:实现负载均衡(多个消费者并行处理不同Partition)和横向扩展(增加消费者即可提升消费能力)。
(2)位移(Offset)管理
-
Offset是消费者在Partition中的消费位置(类似“游标”)。
-
管理方式:
-
早期版本:消费者自行维护Offset(需持久化到外部存储,如ZooKeeper)。
-
当前版本:Offset默认由Kafka的**内部Topic(__consumer_offsets)**管理(消费者定期提交Offset到该Topic)。
-
-
重置策略:消费者可指定从最早(earliest)、最新(latest)或指定Offset开始消费。
3.5 消费传递核心机制
底层的默认实现就是epoll,而kafka本身的线程模型是Reactor多线程模型,类似于Netty(1个Acceptor线程 + N个Processor线程 + M个业务处理线程)。
Acceptor 线程:唯一的线程,负责监听客户端的新连接请求。一旦接受连接,它会采用轮询(Round-Robin) 的方式将新连接分配给某个 Processor 线程处理。
Processor 线程(通常有多个,由 num.network.threads配置):每个 Processor 线程都有自己的 Selector(在 Linux 上即 epoll)。它负责从连接中读取请求数据(注册 OP_READ事件)并将响应写回客户端(注册 OP_WRITE事件)。它们将收到的请求放入一个共享的请求队列
KafkaRequestHandler 线程(通常有多个,由 num.io.threads配置):它们从请求队列中取出请求,交给 KafkaApis类进行业务逻辑处理(如消息写入磁盘、从磁盘读取消息等),然后将响应放回对应 Processor 的响应队列,最后由 Processor 线程发送给客户端
1 Producer如何将二进制数据传输到Broker
1.1 消息创建与序列化
Producer应用程序首先创建ProducerRecord对象,包含目标topic、可选key、value、partition和timestamp等信息。Key和value会通过指定的序列化器(Serializer)被转换为字节数组。Kafka提供了多种默认序列化器,如StringSerializer、IntegerSerializer等。
1.2 确定目标分区
序列化后的消息需要确定发送到Topic的哪个分区:
-
指定分区:若ProducerRecord中明确指定了partition,则直接使用该分区。
-
有Key的消息:使用默认分区器时,会对key进行哈希(通常采用Murmur2Hash算法),然后对分区数取模,决定目标分区。这确保了相同key的消息总是进入同一分区
-
无Key的消息:默认采用轮询(Round-Robin)方式将消息均匀分布到所有分区。
1.3 消息累加与批量发送
消息不会立即发送,而是先进入客户端的消息累加器(RecordAccumulator):
-
累加器按分区维护双端队列,每个队列存放ProducerBatch。
-
新消息会尝试放入其分区对应的最新Batch中。若Batch空间不足或不存在,则创建新的Batch
-
批量发送条件:满足任一条件即触发Sender线程发送批次:
-
某个Batch达到batch.size(默认16KB)
-
某个Batch的创建时间超过linger.ms(默认0,表示立即发送)。
-
1.4 发送与确认
Sender线程将准备好的Batch按目标Broker分组,通过NetworkClient发送ProduceRequest给对应Partition的Leader Broker。
Producer可通过acks配置确认级别,平衡可靠性与延迟:
-
acks=0:不等待Broker确认。延迟最低,但可能丢失消息。
-
acks=1:等待Leader成功写入本地日志即返回确认。均衡选择,但Leader故障可能导致最新消息丢失。
-
acks=all(或-1):等待ISR中所有副本都成功写入后才确认。可靠性最高,延迟也最高。
为了应对可重试的异常(如LeaderNotAvailableException, NetworkException),Producer可配置retries参数
2 Broker与Consumer之间的通信
2.1 Consumer的初始化与分区分配
Consumer通过group.id加入消费组。Consumer启动时,会向Group Coordinator(集群中的一个Broker)发送JoinGroup请求。Group Coordinator负责为消费组中的所有Consumer分配分区。默认策略有RangeAssignor、RoundRobinAssignor等,旨在均衡分配分区给消费者。
2.2 消息拉取(Polling)
Kafka采用拉取模型(Pull),Consumer主动从Broker获取消息:
-
Consumer在其主循环中调用poll()方法,向它分配到的各分区的Leader Broker发送FetchRequest。
-
FetchRequest中包含希望从每个分区拉取的消息偏移量(Offset)。
-
Broker收到请求后,从对应分区的日志文件中读取消息,通过FetchResponse返回给Consumer。
2.3 偏移量(Offset)管理
Consumer需要跟踪每个分区当前已消费的位置,即偏移量。
偏移量可提交至Kafka内部主题__consumer_offsets:
-
自动提交:开启enable.auto.commit=true(默认)时,Consumer会定期自动提交偏移量。
-
手动提交:更可靠的方式是手动提交。
-
同步提交(commitSync()):阻塞直到提交成功或发生不可恢复错误。
-
异步提交(commitAsync()):非阻塞,搭配回调函数处理结果。
-
2.4 消费者语义与可靠性
Consumer的处理语义很大程度上取决于处理消息与提交偏移量的时机:
-
至少一次(At-least-once):先处理消息,成功后提交偏移量。若处理成功后提交前Consumer宕机,新Consumer会重新消费该消息。消息不会丢,但可能重复消费。
-
至多一次(At-most-once):先提交偏移量,再处理消息。若提交后处理失败,消息不会被重试。消息可能丢失,但不会重复。
-
精确一次(Exactly-once):需要Kafka事务与幂等性配合,或Consumer将处理结果和偏移量在外部系统(如数据库)中原子性提交
四、Kafka与其他消息队列对比
|
特性 |
Kafka |
RabbitMQ |
RocketMQ |
|---|---|---|---|
|
设计目标 |
高吞吐、流式处理 |
低延迟、任务队列 |
金融级可靠、顺序消息 |
|
消息模型 |
发布-订阅(多订阅者可重复消费) |
点对点(Queue)或发布-订阅 |
发布-订阅/队列混合 |
|
吞吐量 |
百万级/秒(依赖分区) |
万级~十万级/秒 |
十万级/秒 |
|
延迟 |
毫秒级(通常<10ms) |
微秒级~毫秒级(更低) |
毫秒级 |
|
持久化 |
默认持久化到磁盘(长期保存) |
可配置(内存或磁盘) |
默认持久化 |
|
适用场景 |
日志收集、实时分析、流处理 |
任务异步处理、业务解耦 |
金融交易、订单状态同步 |
五、典型应用场景
-
日志收集:将多台服务器的日志统一发送到Kafka,由下游系统(如ELK)实时分析。
-
实时监控:将业务指标(如用户点击、API调用)实时推送至Kafka,供监控系统聚合计算。
-
事件溯源:记录业务操作的事件流(如订单创建、支付),支持后续审计或状态回溯。
-
流处理:作为Flink/Spark Streaming的数据源,实现实时计算(如用户行为分析、实时推荐)。
六、总结
Kafka的核心原理围绕分布式、分区化、副本机制构建,通过Topic-Partition-Offset的抽象实现了高吞吐、低延迟的消息传递,同时通过ISR、Leader选举等机制保障高可用。理解其存储结构(分段日志)、副本同步逻辑(ISR)、以及消费者组的协调机制,是掌握Kafka的关键。在实际应用中,需根据业务需求合理配置分区数、副本数、ACK策略等参数,以平衡性能与可靠性。
8468

被折叠的 条评论
为什么被折叠?



