一. Kafka基本介绍
Kafka是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统。具有:高吞吐量、低延迟、可扩展性、持久性、可靠性、容错性、高并发等特性。常见的应用场景有:日志收集、消息系统、流式处理等。
二. Kafka的基本架构
Producer
:生产者,也就是发送消息的一方。生产者负责创建消息,然后将其发送到 Kafka。Consumer
:消费者,也就是接受消息的一方。消费者连接到 Kafka 上并接收消息,进而进行相应的业务逻辑处理。Consumer Group
:一个消费者组可以包含一个或多个消费者。使用多分区 + 多消费者方式可以极大提高数据下游的处理速度,同一消费组中的消费者不会重复消费消息,同样的,不同消费组中的消费者消息消息时互不影响。Kafka 就是通过消费组的方式来实现消息 P2P 模式和广播模式。Broker
:服务代理节点。Broker 是 Kafka 的服务节点,即 Kafka 的服务器。Topic
:Kafka 中的消息以 Topic 为单位进行划分,生产者将消息发送到特定的 Topic,而消费者负责订阅 Topic 的消息并进行消费。Partition
:Topic 是一个逻辑的概念,它可以细分为多个分区,每个分区只属于单个主题。同一个主题下不同分区包含的消息是不同的,分区在存储层面可以看作一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。Offset
:offset 是消息在分区中的唯一标识,Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,Kafka 保证的是分区有序性而不是主题有序性。Replication
:副本,是 Kafka 保证数据高可用的方式,Kafka 同一 Partition 的数据可以在多 Broker 上存在多个副本,通常只有主副本对外提供读写服务,当主副本所在 broker 崩溃或发生网络一场,Kafka 会在 Controller 的管理下会重新选择新的 Leader 副本对外提供读写服务。Record
:实际写入 Kafka 中并可以被读取的消息记录。每个 record 包含了 key、value 和 timestamp。
三. Kafka如何保证消息顺序消费
Kafka 在 Topic
级别本身是无序的,只有 partition
上才有序,所以为了保证处理顺序,可以自定义分区器,将需顺序处理的数据发送到同一个 partition
。自定义分区器需要实现接口 Partitioner
接口并实现 3 个方法:partition
,close
,configure
,在partition
方法中返回分区号即可。
Kafka 中发送 1 条消息的时候,可以指定(topic, partition, key)
3 个参数,partiton
和 key
是可选的。
Kafka 分布式的单位是 partition
,同一个 partition
用一个 write ahead log
组织,所以可以保证FIFO
的顺序。不同 partition
之间不能保证顺序。因此你可以指定 partition
,将相应的消息发往同 1个 partition
,并且在消费端,Kafka 保证1 个 partition
只能被1 个 consumer
消费,就可以实现这些消息的顺序消费。
另外,也可以指定 key(比如 order id),具有同 1 个 key 的所有消息,会发往同 1 个partition
,那这样也实现了消息的顺序消息。
四. Kafka发送消息选择分区的逻辑
Kafka在数据生产的时候,有一个数据分发策略。默认的情况使用org.apache.kafka.clients.producer.internals.DefaultPartitioner
类,这个类中就是定义数据分发的策略。默认策略为:
- 如果在发消息的时候指定了分区,则消息投递到指定的分区
- 如果没有指定分区,但是消息的key不为空,则基于key的哈希值来选择一个分区
- 如果既没有指定分区,且消息的key也是空,则用轮询的方式选择一个分区
五. Kafka如何避免消息丢失
Kafka的消息避免丢失可以从三个方面考虑处理:Producer发送消息避免失败、Broker能成功保存接收到的消息、Consumer确认消费消息。
-
Producer发送消息避免失败
想要Produce发送消息不失败,那就得知道发送结果,网络抖动这些情况是无法避免的,只能是发送后获取发送结果,那么最直接的方式就是把Kafka默认的异步发送改为同步发送(Broker收到消息后ack
回复确认),这样就能实时知道消息发送的结果,但是这样会让Kafka的发送效率大大降低,因为Kafka在默认的异步发送消息的时候可以批量发送,以此大幅度提高发送效率,因此一般很少使用同步发送的方式,除非消息很重要绝不允许丢失。
但是我们可以采用添加异步或调函数,监听消息发送的结果,如果失败可以在回调中重试,以此来达到尽可能的发送成功。同时Producer
本身提供了一个retries
的机制,如果因为网络问题,或者Broker故障 导致发送失败,就是重试。一般这个retries
设置3-5次或者更高,同时重试间隔时间也随着次数增长。 -
Broker能成功保存接收到的消息
Broker要成功的保存接收到的消息并且不丢失,就需要把接收到的消息保存到磁盘。Kafka为了提高性能采用的是异步批量,存储到磁盘的机制,就是有一定的消息量和时间间隔要求的,刷磁盘的这个动作是操作系统来调度的,如果在刷盘之前系统就崩溃了,就会数据丢失。
针对这个情况,Kafka采用Partition
分区ack
机制,Partition
分区是指一个Topic下的多个分区,有一个Leader
分区,其他的都是Follower
分区,Leader
分区负责接收和被读取消息,Follower
分区会通过Replication
机制同步Leader
的数据,负责高可用(Kafka在2.4之后,Kafka提供了读写分离,Follower
也可以提供读取),当Leader
出现故障时会从Follower
中选取一个成为新的Leader
。那么当一个消息发送到Leader
分区之后,Kafka提供了一个acks
的参数,Producer
可以设置这个参数,去结合broker
的Partition
机制来共同保障数据的可靠性,这个参数的值有三个0
,表示Producer
不需要等待broker
的响应,就认为消息发送成功了(可能存在数据丢失)1
,表示Leader
收到消息之后,不等待其他的Follower
的同步就给Producer
发一个确认,如果Leader
和Partition
挂了就可能存在数据丢失-1
,表示Leader
收到消息之后还会等待ISR
列表(与Leader
保持正常连接的Follwer
节点列表)中的Follower
同步完成,再给Producer
返回一个确认,也就是所有分区节点都确认收到消息,保证数据不丢失
-
Consumer确认消费消息
当Producer
确定发送消息成功并且Broker
成功保存消息之后,基本上Consumer
就肯定能消费到消息。Kafka在消费者消费时有一个offset
机制,代表了当前消费者消费到了Partition
的哪一条消息。kafka的Consumer
的配置中,默认的enable.auto.commit = true
,表示在Consumer
通过poll
方法 获取到消息以后,每过5秒(通过配置项可修改)会自动获取poll
中得到的最大的offset
, 提交给Partition
中的offset_consumer
(存储 offset 的特定topic)。如果enable.auto.commit = false
时,则关闭了自动提交,需要手动的通过应用程序代码进行提交。
所以在Consumer
消费消息时,丢失消息的可能会有两种,比如开启了offset
自动提交,但是消息消费失败;或者没有开启自动提交offset
,但是在消费消息之前提交了offset
。针对这两种情况,可以设置在消息消费完成后手动提交offset
。总之Consumer
端确认消息消费成功后再提交offset
即可保证消息正常消费。
六. Kafka的offset机制
Kafka中的每个Partition
都由一系列有序的、不可变的消息组成,这些消息被连续的追加到Partition
中。Partition
中的每个消息都有一个连续的序号,用于Partition
唯一标识一条消息。