Kafka工作原理
Kafka工作原理
1. Kafka核心概念
Message(消息):Kafka中的数据单元称为消息。可以将消息看成是数据库中的一条“数据行”或一条“记录”。消息是Kafka中最基本的单位,每一条消息都是一个独立的记录,包含消息的键、值、时间戳等信息。
Batch(批次):为了提高效率,Kafka将消息进行批量处理。消息被分批写入Kafka,这种方式提高了吞吐量,但也会增加响应时间。批次处理使得Kafka能够以更高效的方式进行I/O操作,从而提升整体性能。
Producer(生产者):Producer即生产者,消息的产生者,是消息的入口。
Broker:Broker是kafka实例,每个服务器上有一个或多个kafka的实例,我们姑且认为每个broker对应一台服务器。每个kafka集群内的broker都有一个不重复的编号,如图中的broker-0、broker-1等……
主题(Topic):主题是Kafka中用于消息分类的概念,类似于数据库中的表。每个主题都可以包含多个消息,生产者将消息发送到主题,消费者从主题中读取消息。通过主题,我们可以将不同类型的消息分开管理。在每个Broker都可以创建多个Topic。
分区(Partition):为了方便扩展和管理,Kafka中的每个主题可以分为多个分区。分区使得Kafka能够横向扩展,将消息分布在多个节点上。单个分区内的消息是有序的,但在多个分区间无法保证全局有序。如果希望消息全局有序,可以将分区数设置为一。
每个topic可以有多个partition,partition的作用是做负载,提高kafka的吞吐量。同一个topic在不同的分区的数据是不重复的,partition的表现形式就是一个一个的文件夹!
副本(Replication):每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)。副本机制是Kafka高可靠性的基础。
AR
:分区中所有副本称为 ARISR
:所有与主副本保持一定程度同步的副本(包括主副本)称为 ISROSR
:与主副本滞后过多的副本组成 OSR
消费者(Consumer):消费者,即消息的消费方,是消息的出口。
消费者组(Consumer Group):我们可以将多个消费者组成一个消费者组,在kafka的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量!
2. Kafka的优点
- 高性能、高吞吐量、低延迟:Kafka 生产和消费消息的速度都达到每秒10万级
- 高可用:所有消息持久化存储到磁盘,并支持数据备份防止数据丢失
- 高并发:支持数千个客户端同时读写
- 容错性:允许集群中节点失败(若副本数量为n,则允许 n-1 个节点失败)
- 高扩展性:Kafka 集群支持热伸缩,无须停机
3. Kafka如何避免消息丢失?
引用:JavaGuide
3.1 生产者丢失消息的情况
生产者(Producer) 调用send
方法发送消息之后,消息可能因为网络问题并没有发送过去。
所以,我们不能默认在调用send
方法发送消息之后消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 send
方法发送消息实际上是异步的操作,我们可以通过 get()
方法获取调用结果,但是这样也让它变为了同步操作.
但是一般不推荐这么做!可以采用为其添加回调函数的形式
如果消息发送失败的话,我们检查失败的原因之后重新发送即可!
3.2 消费者丢失消息的情况
我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。
当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
解决办法也比较粗暴,我们手动关闭自动提交 offset,每次在真正消费完消息之后再自己手动提交 offset 。 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。
3.3 Kafka弄丢了消息
我们知道 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。
试想一种情况:假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。
设置 acks = all
解决办法就是我们设置 acks = all。acks 是 Kafka 生产者(Producer) 很重要的一个参数。
acks 的默认值即为 1,代表我们的消息被 leader 副本接收之后就算被成功发送。当我们配置 acks = all 表示只有所有 ISR 列表的副本全部收到消息时,生产者才会接收到来自服务器的响应. 这种模式是最高级别的,也是最安全的,可以确保不止一个 Broker 接收到了消息. 该模式的延迟会很高
Kafka的Producer有三种ack机制,参数值有0、1 和 -1
- 0: 相当于异步操作,Producer 不需要Leader给予回复,发送完就认为成功,继续发送下一条(批)Message。此机制具有最低延迟,但是持久性可靠性也最差,当服务器发生故障时,很可能发生数据丢失
- 1: Kafka 默认的设置。表示 Producer 要 Leader 确认已成功接收数据才发送下一条(批)Message。不过 Leader 宕机,Follower 尚未复制的情况下,数据就会丢失。此机制提供了较好的持久性和较低的延迟性
- -1: Leader 接收到消息之后,还必须要求ISR列表里跟Leader保持同步的那些Follower都确认消息已同步,Producer 才发送下一条(批)Message。此机制持久性可靠性最好,但延时性最差
设置 replication.factor >= 3
为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 replication.factor >= 3。这样就可以保证每个 分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。
设置 min.insync.replicas > 1
一般情况下我们还需要设置 min.insync.replicas> 1 ,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。min.insync.replicas 的默认值为 1 ,在实际生产中应尽量避免默认值 1。
但是,为了保证整个 Kafka 服务的高可用性,你需要确保 replication.factor > min.insync.replicas 。为什么呢?设想一下假如两者相等的话,只要是有一个副本挂掉,整个分区就无法正常工作了。这明显违反高可用性!一般推荐设置成 replication.factor = min.insync.replicas + 1。
设置 unclean.leader.election.enable = false
多个 follower 副本之间的消息同步情况不一样,当我们配置了unclean.leader.election.enable = false 时,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。
4. Kafka如何保证消息的消费顺序
每次添加消息到 Partition(分区) 的时候都会采用尾加法,如上图所示。 Kafka 只能为我们保证 Partition(分区) 中的消息有序。
方法一:既然消费顺序不一致是由于一个Topic有多个Partition,而Kafka只能保证一个Partition内有序,那么令一个Topic只对应一个Partition就可以保证消息消费顺序。
方法二:发送消息时指定Partition,消息就会被发送到对应的Partition;发送消息时指定Key,同一个Key的消息会被发送到同一个Partition。
选择的Partition = hashcode(key) % Partition总数
5. Kafka如何保证消息不被重复消费
kafka 出现消息重复消费的原因:
服务端侧已经消费的数据没有成功提交 offset(根本原因)。
Kafka 侧 由于服务端处理业务时间长或者网络链接等等原因让 Kafka 认为服务假死,触发了分区 rebalance。
为了维持Consumer与Consumer Group之间的关系,Consumer 会周期性地发送 hearbeat 到 coodinator(协调者),如果有 hearbeat 超时或未收到 hearbeat,coordinator 会认为该Consumer已经退出,那么它所订阅的Partition会分配到同一组内的其他Consumer上,这个过程称为 rebalance(再平衡)
解决方案:
- 消费消息服务做幂等校验,比如 Redis 的 set、MySQL 的主键等天然的幂等功能。这种方法最有效。
- 将
enable.auto.commit
参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。那么这里会有个问题:什么时候提交 offset 合适?- 处理完消息再提交:依旧有消息重复消费的风险,和自动提交一样
- 拉取到消息即提交:会有消息丢失的风险。允许消息延时的场景,一般会采用这种方式。然后,通过定时任务在业务不繁忙(比如凌晨)的时候做数据兜底。
6. Kafka重试机制
6.1 消费失败会怎么样?
在消费过程中,当其中一个消息消费异常时,会不会卡住后续队列消息的消费?这样业务岂不是卡住了?
在默认配置下,当消费异常会进行重试,重试多次后会跳过当前消息,继续进行后续消息的消费,不会一直卡在当前消息。
6.2 默认重试多少次?
Kafka 消费者在默认配置下会进行最多 10 次 的重试,每次重试的时间间隔为 0,即立即进行重试。如果在 10 次重试后仍然无法成功消费消息,则不再进行重试,消息将被视为消费失败。
6.3 重试失败后的消息如何再次处理?
死信队列(Dead Letter Queue,简称 DLQ) 是消息中间件中的一种特殊队列。它主要用于处理无法被消费者正确处理的消息,通常是因为消息格式错误、处理失败、消费超时等情况导致的消息被”丢弃”或”死亡”的情况。
当消息进入队列后,消费者会尝试处理它。如果处理失败,或者超过一定的重试次数仍无法被成功处理,消息可以发送到死信队列中,而不是被永久性地丢弃。在死信队列中,可以进一步分析、处理这些无法正常消费的消息,以便定位问题、修复错误,并采取适当的措施。