Kafka 是采用 Scala 语言开发的一个多分区、多副本且基于 ZooKeeper 协调的分布式消息系统。其定位是一个分布式流式处理平台,它以高吞吐、可持久化、可水平扩展、支持流数据处理等多种特性而被广泛使用。
kafka 本质是一个消息队列(MessageQueue,MQ)。消息队列就是存放消息的队列,用于不同服务、进程、线程间的通信。
消息队列使用场景 *
kafka 主要的功能有
一个典型的 Kafka 体系架构包括
**主题 topic:**逻辑概念,可以理解为队列。Kafka 中消息以主题分类,每条消息都要指定一个主题,生产者将消息发送到特定的主题(每条消息都要指定一个主题),而消费者负责订阅主题并进行消费。
**分区 partition:**物理概念,可以理解为日志。主题可以分为多个分区,同一主题在不同分区包含的消息不同。每个分区在存储层面可以看作一个可追加的日志文件,生产者生产的消息追加到分区日志文件的时候会分配一个特定的偏移量 offfset。offset 是消息在分区中的唯一标识,Kafka 通过 offset 保证消息在分区的顺序性。注意:Kafka只能保证分区消息的有序性,而不能保证主题消息的有序性。
Kafka 的消息组织方式实际上是三级结构:主题 - 分区 - 消息。主题下的每条消息只会保存在某一个分区中,而不会在多个分区中被保存多份。
分区的作用主要提供负载均衡的能力,能够实现系统的高伸缩性(水平扩展)。不同的分区能够被放置到不同节点的机器上,而数据的读写操作也都是针对分区这个粒度而进行的,这样每个节点的机器都能独立地执行各自分区的读写请求处理。这样,当性能不足的时候可以通过添加新的节点机器来增加整体系统的吞吐量。
副本 Replica:是指分布式系统对数据和服务提供的一种冗余方式。副本包括数据副本和服务副本。
使用副本的好处是:
目前 Kafka 的多副本机制只实现了第一点,通过增加副本数量来提升数据容灾能力,实现故障自动转移。
副本是相对于分区而言的,副本是特定分区的副本。
Kafka 采取的是基于领导者 leader-based 的副本机制 ,副本分成两类领导 leader 副本和追随者 follower 副本。每个分区在创建时选举一个 leader 副本,其余的副本为 follower 副本。副本之间是一主多从的关系,其中 leader 副本负责处理读写请求,follower 副本只负责与 leader 副本的消息同步,不对外提供服务。
各个副本处于不同的 broker 中,当 leader 副本出现故障,Kafka 依托于 ZooKeeper 提供的监控功能实时感知到,从 follower 副本中重新选举新的 leader 对外提供服务。Kafka 通过多副本机制实现了故障的自动转移,当 Kafka 集群中某个 broker 失效仍能保证服务可用。同时,消费者采用 pull 模式从服务端拉取消息,并且保存消费的具体位置 offset,消费者宕机恢复上线时可以根据之前保存的消费者位置重新拉取需要的消息进行消费,这样就不会造成消息丢失。
分区中的所有副本统称为 AR (Assigned Replicas)。所有与 leader 副本保持一定程度同步的副本(包括 leader 副本)组成 ISR(In-Sync Replicas),ISR 集合是 AR 集合中的一个子集合。消息首先发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步,同步期间内 follower 副本相对于 leader 副本而言会有一定程度的滞后(可以通过参数配置)。leader 副本负责维护跟踪 ISR 集合中所有 follower 副本的滞后状态。
分区策略:决定生产者将消息发送到某个分区的算法。
轮询策略,即顺序分配,默认策略,总能保证消息最大限度平均分配到所有分区上。
随机策略,随意地将消息放置到任意一个分区上。
Kafka 允许为每条消息定义消息键,简称 key。key 可以是一个有明确业务含义的字符串,也可以用来表征消息源数据。一旦消息被定义了 key,就可以保证同一个 key 的所有消息都进入到相同的分区里面,由于每个分区下的消息处理都是有顺序的,所以被称为按消息键保序策略。
点对点消息模型:1;1,消息一旦被消费,就会从队列中删除,而且只能被下游的一个消费者消费。例如:传统的消息队列。伸缩性差。
发布订阅消息模型:1:n,允许同一消息被多个消费者重复消费。每个订阅者必须订阅主题的全部分区。伸缩性差,不灵活。
Kafka 采用消费者组机制,组内的消费者共用一个 group id,对订阅主题的所有分区进行消费,逻辑上消费者组等于订阅者。一个分区只能由组内一个消费者消费,消费者不一定要订阅主题的所有分区。消费者组间彼此独立,能够订阅相同的一组主题。当消息发布主题后,只会被投递给订阅它的每个消费组中的一个消费者。
Kafka 的消费者组机制,同时实现了传统消息系统的两大模型:如果所有实例属于同一个组,那么它实现的就是消息队列模型。如果所有实例分别属于不同的组,那它实现的就是发布订阅模型。
Consumer 采用 pull 模式从 Broker 中读取数据,这样可以根据消费者的消费能力匹配消费信息的速率。但是因为消费者从 Broker 主动拉取数据,需要维护一个长轮询,这也造成若 Kafka 没有数据时,消费者可能会陷入循环中,一直返回空数据的情况发生。针对这一点,Kafka 消费者在消费数据是会传入 timeout 参数,当没有数据可供消费时,消费者等待 timeout 后再返回。
消费者组订阅主体的分区 partition 交由组内哪个消费者 consumer 消费。
按照主题划分,分区数量 / 组内消费者线程数量,决定每个消费者消费的分区数。
如图所示:topic A | topic B 有 3 个分区,消费者组有 2 个消费者,可能存在分区不均匀的问题。
将所有分区作为整体进行 hash 排序,解决多消费者分配不均的问题,可能造成消费混乱。
为保证生产者发送的数据能可靠地发送到指定的主题,主题的每个分区收到生产者发送的数据后,都需要向生产者发送 ACK。当生产者收到 ACK,就会进行下一轮的发送,否则重发数据。确保有 follower 与 leader 同步完成,leader 再发送 ACK。