Kafka核心知识总结!

Apache Kafka是一个开源流处理平台,提供高吞吐量、低延迟的消息传递。其核心特性包括分布式、发布/订阅模式、消息持久化、副本机制等。Kafka支持多种分区策略,如轮询、随机和消息键保序。生产者可以启用压缩算法,如GZIP、Snappy、LZ4和zstd。消费者组确保每个分区只被一个消费者实例消费,通过Consumer Offset实现消息追踪。Kafka通过Controller组件实现集群管理和协调。日志存储使用日志分段技术,通过配置参数如log.retention.ms来控制消息保留时间。常见面试题涉及Kafka的工作模式、高可用性保障和使用场景。
摘要由CSDN通过智能技术生成

基本简介

Apache Kafka是由LinkedIn采用Scala和Java开发的开源流处理软件平台,并捐赠给了Apache Software Foundation。

该项目旨在提供统一的、高吞吐量、低延迟的平台来处理实时数据流。

Kafka可以通过Kafka Connect连接到外部系统,并提供了Kafka Streams。

「Kafka的特性」

Kafka是一种分布式的,基于发布/订阅的消息系统,主要特性如下:

特性 分布式 「高性能」 「持久性和扩展性」
描述 多分区 高吞吐量 数据可持久化
多副本 低延迟 容错性
多订阅者 高并发 支持水平在线扩展
基于ZooKeeper调度 时间复杂度为O(1) 消息自动平衡

版本号

「Kafka版本命名」

我们在官网上下载Kafka时,会看到这样的版本:

前面的版本号是编译Kafka源代码的Scala编译器版本。

Kafka服务器端的代码完全由Scala语言编写,Scala同时支持面向对象编程和函数式编程,用Scala写成的源代码编译之后也是普通的.class文件,因此我们说Scala是JVM系的语言。

真正的Kafka版本号实际上是2.1.1

那么这个2.1.1又表示什么呢?

前面的2表示大版本号,即Major Version;中间的1表示小版本号或次版本号,即Minor Version;最后的1表示修订版本号,也就是Patch号。

Kafka社区在发布1.0.0版本后写过一篇文章,宣布Kafka版本命名规则正式从4位演进到3位,比如0.11.0.0版本就是4位版本号。

有个建议,不论用的是哪个版本,都请尽量保持服务器端版本和客户端版本一致,否则你将损失很多Kafka为你提供的性能优化收益。

「版本演进」

0.7版本:只提供了最基础的消息队列功能。

0.8版本:引入了副本机制,至此kafka成为了一个整整意义上完备的分布式可靠消息队列解决方案

0.9.0.0版本:增加了基础的安全认证/权限功能;使用Java重新了新版本消费者API;引入了Kafka Connect组件。

0.11.0.0版本:提供了幂等性Producer API以及事务API;对Kafka消息格式做了重构。

1.0和2.0版本:主要还是Kafka Streams的各种改进

基本概念

 

「主题」

发布订阅的对象是主题(Topic),可以为每 个业务、每个应用甚至是每类数据都创建专属的主题

「生产者和消费者」

向主题发布消息的客户端应用程序称为生产者,生产者程序通常持续不断地 向一个或多个主题发送消息

订阅这些主题消息的客户端应用程序就被称为消费者,消费者也能够同时订阅多个主题的消息

「Broker」

集群由多个 Broker 组成,Broker 负责接收和处理客户端发送过来的请求,以及对消息进行持久化

虽然多个 Broker 进程能够运行在同一台机器上,但更常见的做法是将 不同的 Broker 分散运行在不同的机器上,这样如果集群中某一台机器宕机,即使在它上面 运行的所有 Broker 进程都挂掉了,其他机器上的 Broker 也依然能够对外提供服务

「备份机制」

备份的思想很简单,就是把相同的数据拷贝到多台机器上,而这些相同的数据拷贝被称为副本

定义了两类副本:领导者副本和追随者副本

前者对外提供服务,这里的对外指的是与 客户端程序进行交互;而后者只是被动地追随领导者副本而已,不能与外界进行交互

「分区」

分区机制指的是将每个主题划分成多个分区,每个分区是一组有序的消息日志

生产者生产的每条消息只会被发送到一个分区中,也就是说如果向一个双分区的主题发送一条消息,这条消息要么在分区 0 中,要么在分区 1 中

每个分区下可以配置若干个副本,其中只能有 1 个领 导者副本和 N-1 个追随者副本

生产者向分区写入消息,每条消息在分区中的位置信息叫位移

「消费者组」

多个消费者实例共同组成一个组来 消费一组主题

这组主题中的每个分区都只会被组内的一个消费者实例消费,其他消费者实例不能消费它

同时实现了传统消息引擎系统的两大模型:

如果所有实例都属于同一个 Group, 那么它实现的就是消息队列模型;

如果所有实例分别属于不 同的 Group,那么它实现的就是发布/订阅模型

「Coordinator:协调者」

所谓协调者,它专门为Consumer Group服务,负责为Group执行Rebalance以及提供位移管理和组成员管理等。

具体来讲,Consumer端应用程序在提交位移时,其实是向Coordinator所在的Broker提交位移,同样地,当Consumer应用启动时,也是向Coordinator所在的Broker发送各种请求,然后由Coordinator负责执行消费者组的注册、成员管理记录等元数据管理操作。

所有Broker在启动时,都会创建和开启相应的Coordinator组件。

也就是说,「所有Broker都有各自的Coordinator组件」

那么,Consumer Group如何确定为它服务的Coordinator在哪台Broker上呢?

通过Kafka内部主题__consumer_offsets

目前,Kafka为某个Consumer Group确定Coordinator所在的Broker的算法有2个步骤。

  • 第1步:确定由__consumer_offsets主题的哪个分区来保存该Group数据:partitionId=Math.abs(groupId.hashCode() % offsetsTopicPartitionCount)

  • 第2步:找出该分区Leader副本所在的Broker,该Broker即为对应的Coordinator。

首先,Kafka会计算该Group的group.id参数的哈希值。

比如你有个Group的group.id设置成了test-group,那么它的hashCode值就应该是627841412。

其次,Kafka会计算__consumer_offsets的分区数,通常是50个分区,之后将刚才那个哈希值对分区数进行取模加求绝对值计算,即abs(627841412 % 50) = 12

此时,我们就知道了__consumer_offsets主题的分区12负责保存这个Group的数据。

有了分区号,我们只需要找出__consumer_offsets主题分区12的Leader副本在哪个Broker上就可以了,这个Broker,就是我们要找的Coordinator。

「消费者位移:Consumer Offset」

消费者消费进度,每个消费者都有自己的消费者位移。

「重平衡:Rebalance」

消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。

Rebalance是Kafka消费者端实现高可用的重要手段。

「AR(Assigned Replicas)」:分区中的所有副本统称为AR。

所有消息会先发送到leader副本,然后follower副本才能从leader中拉取消息进行同步。

但是在同步期间,follower对于leader而言会有一定程度的滞后,这个时候follower和leader并非完全同步状态

「OSR(Out Sync Replicas)」:follower副本与leader副本没有完全同步或滞后的副本集合

「ISR(In Sync Replicas):「AR中的一个子集,ISR中的副本都」是与leader保持完全同步的副本」,如果某个在ISR中的follower副本落后于leader副本太多,则会被从ISR中移除,否则如果完全同步,会从OSR中移至ISR集合。

在默认情况下,当leader副本发生故障时,只有在ISR集合中的follower副本才有资格被选举为新leader,而OSR中的副本没有机会(可以通过unclean.leader.election.enable进行配置)

「HW(High Watermark)」:高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个水位 offset 之前的消息

下图表示一个日志文件,这个日志文件中只有9条消息,第一条消息的offset(LogStartOffset)为0,最有一条消息的offset为8,offset为9的消息使用虚线表示的,代表下一条待写入的消息。

日志文件的 HW 为6,表示消费者只能拉取offset在 0 到 5 之间的消息,offset为6的消息对消费者而言是不可见的。

「LEO(Log End Offset)」:标识当前日志文件中下一条待写入的消息的offset

上图中offset为9的位置即为当前日志文件的 LEO,LEO 的大小相当于当前日志分区中最后一条消息的offset值加1

分区 ISR 集合中的每个副本都会维护自身的 LEO ,而 ISR 集合中最小的 LEO 即为分区的 HW,对消费者而言只能消费 HW 之前的消息。

系统架构

「kafka设计思想」

一个最基本的架构是生产者发布一个消息到Kafka的一个Topic ,该Topic的消息存放于的Broker中,消费者订阅这个Topic,然后从Broker中消费消息,下面这个图可以更直观的描述这个场景:

「消息状态:」 在Kafka中,消息是否被消费的状态保存在Consumer中,Broker不会关心消息是否被消费或被谁消费,Consumer会记录一个offset值(指向partition中下一条将要被消费的消息位置),如果offset被错误设置可能导致同一条消息被多次消费或者消息丢失。

「消息持久化:」 Kafka会把消息持久化到本地文件系统中,并且具有极高的性能。

「批量发送:」 Kafka支持以消息集合为单位进行批量发送,以提高效率。

「Push-and-Pull:」 Kafka中的Producer和Consumer采用的是Push-and-Pull模式,即Producer向Broker Push消息,Consumer从Broker Pull消息。

「分区机制(Partition):」 Kafka的Broker端支持消息分区,Producer可以决定把消息发到哪个Partition,在一个Partition中消息的顺序就是Producer发送消息的顺序,一个Topic中的Partition数是可配置的,Partition是Kafka高吞吐量的重要保证。

「系统架构」

通常情况下,一个kafka体系架构包括「多个Producer」「多个Consumer」「多个broker」以及「一个Zookeeper集群」

「Producer」:生产者,负责将消息发送到kafka中。

「Consumer」:消费者,负责从kafka中拉取消息进行消费。

「Broker」:Kafka服务节点,一个或多个Broker组成了一个Kafka集群

「Zookeeper集群」:负责管理kafka集群元数据以及控制器选举等。

生产者分区

「为什么分区?」

Kafka的消息组织方式实际上是三级结构:主题-分区-消息。

主题下的每条消息只会保存在某一个分区中,而不会在多个分区中被保存多份。

其实分区的作用就是提供负载均衡的能力,或者说对数据进行分区的主要原因,就是为了实现系统的高伸缩性(Scalability)。

不同的分区能够被放置到不同节点的机器上,而数据的读写操作也都是针对分区这个粒度而进行的,这样每个节点的机器都能独立地执行各自分区的读写请求处理,并且,我们还可以通过添加新的节点机器来增加整体系统的吞吐量。

「都有哪些分区策略?」

「所谓分区策略是决定生产者将消息发送到哪个分区的算法。」

Kafka为我们提供了默认的分区策略,同时它也支持你自定义分区策略。

「自定义分区策略」

如果要自定义分区策略,你需要显式地配置生产者端的参数partitioner.class

在编写生产者程序时,你可以编写一个具体的类实现org.apache.kafka.clients.producer.Partitioner接口。

这个接口也很简单,只定义了两个方法:partition()和close(),通常你只需要实现最重要的partition方法。

我们来看看这个方法的方法签名:

int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);

这里的topic、key、keyBytes、value和valueBytes都属于消息数据,cluster则是集群信息(比如当前Kafka集群共有多少主题、多少Broker等)。

Kafka给你这么多信息,就是希望让你能够充分地利用这些信息对消息进行分区,计算出它要被发送到哪个分区中。

只要你自己的实现类定义好了partition方法,同时设置partitioner.class参数为你自己实现类的Full Qualified Name,那么生产者程序就会按照你的代码逻辑对消息进行分区。

「轮询策略」

也称Round-robin策略,即顺序分配。

比如一个主题下有3个分区,那么第一条消息被发送到分区0,第二条被发送到分区1,第三条被发送到分区2,以此类推。当生产第4条消息时又会重新开始,即将其分配到分区0

这就是所谓的轮询策略。轮询策略是Kafka Java生产者API默认提供的分区策略。

「轮询策略有非常优秀的负载均衡表现,它总是能保证消息最大限度地被平均分配到所有分区上,故默认情况下它是最合理的分区策略,也是我们最常用的分区策略之一。」

「随机策略」

也称Randomness策略。所谓随机就是我们随意地将消息放置到任意一个分区上。

如果要实现随机策略版的partition方法,很简单,只需要两行代码即可:

List partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());

先计算出该主题总的分区数,然后随机地返回一个小于它的正整数。

本质上看随机策略也是力求将数据均匀地打散到各个分区,但从实际表现来看,它要逊于轮询策略,所以「如果追求数据的均匀分布,还是使用轮询策略比较好」。事实上,随机策略是老版本生产者使用的分区策略,在新版本中已经改为轮询了。

「按消息键保序策略」

Kafka允许为每条消息定义消息键,简称为Key。

这个Key的作用非常大,它可以是一个有着明确业务含义的字符串,比如客户代码、部门编号或是业务ID等;也可以用来表征消息元数据。

特别是在Kafka不支持时间戳的年代,在一些场景中,工程师们都是直接将消息创建时间封装进Key里面的。

一旦消息被定义了Key,那么你就可以保证同一个Key的所有消息都进入到相同的分区里面,由于每个分区下的消息处理都是有顺序的,故这个策略被称为按消息键保序策略

实现这个策略的partition方法同样简单,只需要下面两行代码即可:

List partitions = cluster.partitionsForTopic(topic);
return Math.abs(key.hashCode()) % partitions.size();

前面提到的Kafka默认分区策略实际上同时实现了两种策略:如果指定了Key,那么默认实现按消息键保序策略;如果没有指定Key,则使用轮询策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值