博主闭关两个多月,查阅了数百万字的大数据资料,结合自身的学习和工作经历,总结了大厂高频面试题,里面涵盖几乎所有我见到的大数据面试题目。
《大厂高频面试题系列》目前已总结4篇文章,且在持续更新中✍。文中用最直白的语言解释了Hadoop、Hive、Kafka、Flume、Spark等大数据技术和原理,细节也总结的很到位,是不可多得的大数据面试宝典,强烈建议收藏,祝大家都能拿到心仪的大厂offer。下面是相关的系列文章:
Kafka是一个分布式的基于发布/订阅模式的消息队列,使用Scala编写,主要应用于大数据实时处理领域。离线场景一般是结合flume进行使用。
(1)Producer :消生产者,就是向kafka中发送消息(写入消息)
(2)consumer: 消费者,向kafka中拉取消息进行消费
(3)Consumer Group (CG):消费者组,由多个consumer组成。消费者组。消费者组中有多个消费者,一般是一个消费者组消费一个topic,消费者组中的所有的消费者就可以各自消费对应的分区数据就可以了。
一般group中的consumer的个数和Topic的分区数相等。如果group中的consumer大于Topic的分区数,多余的消费者不会工作。 如果group中的consumer小于Topic的分区数,则需要一个consumer去消费多个分区。
(4)broker: kafka的节点,partition数据就是保存在broker上面
(5)Topic(主题) :在工作中一般是一个业务一个主题,好处就是数据分类之后比较好处理。
topic是逻辑上的概念,而partition是物理上的概念,parrtition才是真正存储数据的。
(6)Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。这样做有2个好处(即分区的原因):
(7)Replica:副本,提高partition的数据的高可用。一个topic的每个分区都有若干个副本,其中包含一个leader和若干个follower。
(8)leader:每个分区多个副本的“主”,生产者向partition中写入消息的时候也是向leader写入,消费者消费partition数据的时候是找leader消费。
(9)follower:每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的leader。
(10)offset:用来标识消费者上次消费到了partition的哪条数据(每个消费者都有自己的offset)
(1)创建topic
kafka-topic.sh --create --topic topic名称 --partitions 分区数 --replication-factor 副本数 --bootstrap-server borker节点:端口<9092>
说明:
--create 表示是创建topic
--bootstrap-server 根据输入的broker来找到kafka集群
注意: 副本数不能不要大于kafka集群的borker个数(多余的副本没什么用)
--partitions --replication-factor 这两个参数在创建topic的时候可以不用写,默认值都是1
样例:
kafka-topics.sh --create --topic first --bootstrap-server wxler1:9092,wxler2:9092 --partitions 3 --replication-factor 3
(2)查看kafka集群所有的topic
kafka-topic.sh --list --bootstrap-server borker节点:端口<9092>
(3)查看某一个topic的信息
kafka-topic.sh --describe --topic topic名称 --bootstrap-server borker节点:端口<9092>
说明:
可以查看的信息包含topic的分区数、副本数、leader在哪个broker上。follwer在哪些broker节点上。ISR列表
样例:
[wxler@wxler1 ~]$ kafka-topics.sh --describe --bootstrap-server wxler1:9092,wxler2:9092 --topic first
Topic: first PartitionCount: 3 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: first Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Topic: first Partition: 1 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: first Partition: 2 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
(4)修改topic(只能够修改topic的分区,而且是只能增加分区,不能够减少分区)
kafka-topic.sh --alter --topic topic名称 --partitions 分区数 --bootstrap-server borker节点:端口<9092>
(5)模拟生产者
kafka-console-producer.sh --topic topic名称 --broker-list borker节点:端口<9092>
(6)模拟消费者
kafka-console-consumer.sh --topic topic名称 --bootstrap-server borker节点:端口<9092>
(7)查看kafka数据
kafka-dump-log.sh --files kafka数据文件 --print-data-log
每个分区对应一个文件夹,该文件夹的命名规则为:topic名称+分区序号,内部为对应分区的.log和.index文件
log文件中存储的就是producer生产的数据。Producer生产的数据会被不断追加到该log文件末端,且每条数据都有自己的offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个offset,以便出错恢复时,从上次的位置继续消费。
一条消息就有一个单独的offset
由于生产者生产的消息会不断追加到log文件末尾,为防止log文件过大导致数据定位效率低下,Kafka采取了分片和索引机制,将每个partition分为多个segment。每个segment对应两个文件——“.index”文件和“.log”文件(最新版本的Kafka还有一个.timeindex文件,即时间索引文件,根据数据的读取时间来建立索引),这些文件位于一个文件夹下,该文件夹的命名规则为:topic名称+分区序号。例如,first这个topic有三个分区,则其对应的文件夹为first-0,first-1,first-2。
segment(碎片)只是一种叫法,是逻辑上的,实际上是不存在的。之所以分为多个segement,是为了更够更好的找到索引对应消息的位置
index和log文件以当前segment的第一条消息的offset命名(就是上一个segment的最后一条消息的offset+1)。
#segment1
00000000000000000000.index
00000000000000000000.log
#segment2
00000000000000170410.index
00000000000000170410.log
#segment3
00000000000000239430.index
00000000000000239430.log
“.index”文件存储大量的索引信息(是稀疏索引,默认每隔4KB建立一个索引),“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中message的物理偏移地址。
index.interval.bytes
(索引间隔,默认值:4096,4K):可以理解为插了多少消息之后再建一个索引,由此可以看出Kafka的索引其实是稀疏索引,这样可以避免索引文件占用过多的内存,从而可以在内存中保存更多的索引。
segment.bytes
(默认值:1073741824,1G):控制着日志段文件的大小,默认是1G,即当文件存储超过1G之后就新起一个文件写入。这是以大小为维度的,还有一个参数是log.segment.ms,以时间为维度切分。
如果我指定了一个offset,Kafka Controller怎么查找到对应的消息?
1、根据offset找到数据在partition的哪个segement
2、从segement的index文件找到offset处于log文件的哪个区间
3、扫描log文件的区间找到对应的数据
我们知道,在启动 Kafka 集群之前,我们需要配置好 log.dirs
参数,其值是 Kafka 数据的存放目录,这个参数可以配置多个目录,目录之间使用逗号分隔,通常这些目录是分布在不同的磁盘上用于提高读写性能。当然我们也可以配置 log.dir
参数,含义一样。只需要设置其中一个即可。
如果 log.dirs
参数只配置了一个目录,那么分配到各个 Broker 上的分区肯定只能在这个目录下创建文件夹用于存放数据。
但是如果 log.dirs 参数配置了多个目录,那么 Kafka 会在哪个文件夹中创建分区目录呢?
答案是Kafka 会在含有分区目录最少的文件夹中创建新的分区目录,分区目录名为 topic名称+分区序号。注意,是分区文件夹总数最少的目录,而不是磁盘使用量最少的目录!也就是说,如果你给 log.dirs
参数新增了一个新的磁盘,新的分区目录肯定是先在这个新的磁盘上创建直到这个新的磁盘目录拥有的分区目录不是最少为止。
将producer发送的数据时,需要封装成一个ProducerRecord对象。ProducerRecord可以指定分区,key和value
key可以自定义(如果不指定,默认为null),value就是我们要发送的内容
(1)指明 partition 的情况下,直接将指明的值直接作为 partiton 值;
(2)没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值;
(3)既没有 partition 值又没有 key 值的情况下
数据可靠性指的是生产者发送的消息是否已经到达kafka
为保证producer发送的数据,能可靠的发送到指定的topic,topic的每个partition收到producer发送的数据后,都需要向producer发送ack(acknowledgement确认收到),如果producer收到ack,就会进行下一轮的发送,否则重新发送数据。
那么就会有几个问题,partition何时发送ack?leader或者follower挂了怎么办?又如何恢复呢?
(1)partition(即leader)何时发送ack?
kafka提供了三种配置:
(2)leader或者follower挂了怎么办?
设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?
Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给producer发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR列表,该时间阈值由replica.lag.time.max.ms
参数设定(默认为30s)。Leader发生故障之后,就会从ISR中选举新的leader。
这里有两个概念:
follower故障恢复
follower发生故障后会被临时踢出ISR,待该follower恢复后,follower会读取本地磁盘记录的上次的HW(即它挂掉时刻的HW),并将log文件高于HW的部分截取掉,从HW开始向leader进行同步。等该follower的LEO大于等于该Partition的HW(即该follwer的LEO>=当前Partition的HW的时候就可以重新加入),即follower追上leader之后,就可以重新加入ISR了。
leader故障恢复
leader发生故障之后,会从ISR中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截掉(新leader并没有截掉,这样做为了保证效率,如果截掉的数据太多,就得不偿失了),然后从新的leader同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不重复。
怎么解决数据的重复问题?可以用exactly-once或者从消费者(如spark或flink)那里去重
将服务器的ACK级别设置为-1,可以保证Producer到Server之间不会丢失数据,即At Least Once(最少一次,能够保证不丢失数据,但不能保证数据重复)语义。相对的,将服务器ACK级别设置为0,可以保证生产者每条消息只会被发送一次,即At Most Once(最多一次,不能保证丢失数据)语义。
At Least Once可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once可以保证数据不重复,但是不能保证数据不丢失。
在0.11版本以前的Kafka,对此是无能为力的,只能保证数据不丢失,再在下游消费者对数据做全局去重。对于多个下游应用的情况,每个都需要单独做全局去重,这就对性能造成了很大影响。
0.11版本的Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指Producer不论向Server发送多少次重复数据,Server端都只会持久化一条。幂等性结合At Least Once语义,就构成了Kafka的Exactly Once(有且仅有一次)语义。即:
At Least Once(设置ack=-1) + 幂等性 = Exactly Once
要启用幂等性,只需要将Producer的参数中enable.idompotence设置为true即可(默认为false)。Kafka的幂等性实现其实就是将原来下游(比如spark或Flink中)需要做的去重放在了数据上游。
Exactly Once,其实是借鉴的mysql的主键思想。
Producer在初始化的时候(只有初始化的时候会随机生成PID)会被分配一个PID(producerid),不同的分区有自己的paritionid(即分区号),发往同一Partition的消息会附带Sequence Number(即发送数据的编号,代表着向分区发送的第几条消息),这样
producerid在producer端生成的,如果producer挂了,重启之后,producer会重新生成producerid,这就使得Exactly Once无法保证数据不重复(即可能导致相同的数据被重新发送)。即producerid只能保证producer不挂的时候,数据不重复。
Kafka从0.11版本开始引入了事务支持。事务可以保证Kafka在Exactly Once语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。
为了实现跨分区跨会话的事务,需要引入一个全局唯一的Transaction ID,并将Producer获得的PID和Transaction ID绑定。这样当Producer重启后就可以通过正在进行的Transaction ID获得原来的PID。
为了管理Transaction,Kafka引入了一个新的组件Transaction Coordinator。Producer就是通过和Transaction Coordinator交互获得Transaction ID对应的任务状态。Transaction Coordinator还负责将事务所有写入Kafka的一个内部Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。
consumer采用pull(拉)模式从broker中读取数据。
push(推)模式很难适应消费速率不同的消费者,因为broker决定消息推送的速率。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。
**pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直等待数据到达。**针对这一点,Kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时长即为timeout。
分区分配策略:partition是有多个,consumer group中有多个消费者,这时候就涉及到消费者究竟消费哪个分区的数据
Kafka有两种分配策略,一是roundrobin,一是range。
(1)roundrobin(轮询)
一个个分配,直到分完为止
比如: topic[partition0,partition1,partition2,partition3,partition4]
consumer group[consumer1,consumer2]
此时:
consumer1:partition0,partition2,partition4
consumer2:partition1,partition3
(2)range(范围分配)
比如: topic[partition0,partition1,partition2,partition3,partition4]
consumer group[consumer1,consumer2]
(1)、分区数 / 消费者个数 = 每个消费者消费几个分区
5 / 2 = 2 代表 2个消费者最起码要每个都要消费2个分区
(2)、分区数 % 消费者个数 = 前面有几个消费者多消费一个分区
5 % 2 = 1 代码最前面一个消费者多消费一个分区
此时:
consumer1:partition0,partition1,partition2
consumer2:partition3,partition4
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets,里面记录了每个消费者消费的位置。
(1)顺序写磁盘
Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到600M/s,而随机写只有100k/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
(2)应用Pagecache(页面缓存,kafka在内存中开辟的一段空间)
这样会产生以下几个好处:
缺点: pagecache是内存,所以机器宕机就会数据丢失,但是问题不大,因为kafka数据有副本机制
(3)零拷贝<依赖于操作系统>
操作系统内存分为了两块: 内核区,用户区。
比如需求: 读取磁盘文件,将文件数据写到网络
正常情况下:
零拷贝:
注意:之前的Linux的零拷贝是将pagecache的数据写到scoket缓冲区,然后通过scoket缓冲区写入到网卡,但是最新的Linxu可以直接从内核空间的pagecache写入网卡。
(4)partition(即分区的原因):
Kafka集群中有一个broker(结点)会被选举为Controller,它负责管理集群broker的上下线,所有topic的分区信息维护和leader选举等工作。
Controller的管理工作都是依赖于Zookeeper的。
Controller选择是通过先到先得的方式,即谁先注册到zookeeper下,谁就是Controller,注册的结点路径是/kafka/controller
,内容如下:
[zk: localhost:2181(CONNECTED) 3] get /kafka/controller
{"version":1,"brokerid":2,"timestamp":"1627297118666"}
broker加入的监听和处理
控制器启动时就起一个监视器监视ZK/brokers/ids/子节点。当存在broker启动加入集群后都会在ZK/brokers/ids/增加一个子节点brokerId,控制器的监视器发现这种变化后,控制器开始执行broker加入的相关流程并更新元数据信息到集群。
broker崩溃的监听与处理
控制器启动时就起一个监视器监视ZK/brokers/ids/子节点。当一个broker崩溃时,该broker与ZK的会话失效导致ZK会删除该子节点,控制器的监视器发现这种变化后,控制器开始执行broker删除的相关流程并更新元数据信息到集群。
topic创建的监听与处理
控制器启动时就起一个监视器监视ZK/brokers/topics/子节点。当通过脚本或者请求创建一个topic后,该topic对应的所有分区及其副本都会写入该目录下的一个子节点。控制器的监视器发现这种变化后,控制器开始执行topic创建的相关流程包括leader选举和ISR并同步元数据信息到集群;且新增一个监视器监视ZK/brokers/topics/<新增topic子节点内容>
topic删除的监听与处理
控制器启动时就起一个监视器监视ZK/admin/delete_topics/子节点。当通过脚本或者请求删除一个topic后,该topic会写入该目录下的一个子节点。控制器的监视器发现这种变化后,控制器开始执行topic删除的相关流程包括通知该topic所有分区的所有副本停止运行;通知所有分区所有副本删除数据;删除ZK/admin/delete_topics/<待删除topic子节点>。
以下为partition的leader选举过程(里面涉及到broker的上下线和topic的分区信息):
kafaka集群中有多个结点,Kafka集群启动的时候,从向/kafka/brokers/ids
结点下去注册,注册成功后会生成对应的临时结点,结点名就是broker的编号:
[zk: localhost:2181(CONNECTED) 11] ls /kafka/brokers/ids
[1, 2, 3]
如果其中一个broker挂了,该broker注册的临时结点就消失了,由于Controller一直在监听/kafka/brokers/ids
,当某一节点下线时,controller就会知道。
每个broker有多个partition,partition也会在zookeeper进行注册,具体路径是/kafka/brokers/topics/topics的名字/partitions/分区号/state
,内容如下:
[zk: localhost:2181(CONNECTED) 13] get /kafka/brokers/topics/first/partitions/0/state
{"controller_epoch":12,"leader":3,"version":1,"leader_epoch":15,"isr":[2,3,1]}
一旦broker挂掉(/kafka/brokers/ids
下的临时结点就少了一个),剩下的follower会进行随机选举,选举出来新的leader后,kafka中的Controller会更新partition注册的结点/kafka/brokers/topics/topics的名字/partitions/分区号/state
,更新其leader和ISR列表信息。
控制器启动时就起一个监视器监视ZK/brokers/topics/子节点。当通过脚本或者请求创建一个topic后,该topic对应的所有分区及其副本都会写入该目录下的一个子节点。控制器的监视器发现这种变化后,控制器开始执行topic创建的相关流程包括leader选举和ISR并同步元数据信息到集群;且新增一个监视器监视ZK/brokers/topics/<新增topic子节点内容>
(1)会在zookeeper中的/kafka/brokers/topics
节点下创建一个新的topic节点,如:/brokers/topics/结点
(kafka Controller 负责topic的创建工作)
(2)触发Controller的监听程序,由于Controller一直在监听/kafka/brokers/ids
,当某一节点上线(或下线)时,controller就会知道。
(3)由于每个broker有多个partition,partition也会在zookeeper进行注册,具体路径是/kafka/brokers/topics/topics的名字/partitions/分区号/state
,内容如下:
[zk: localhost:2181(CONNECTED) 13] get /kafka/brokers/topics/first/partitions/0/state
{"controller_epoch":12,"leader":3,"version":1,"leader_epoch":15,"isr":[2,3,1]}
一旦broker上线(或下线)(/kafka/brokers/ids
下的临时结点就多(或少)了一个),【如果是下线,剩下的follower会进行随机选举,选举出来新的leader后】,kafka中的Controller会更新partition注册的结点/kafka/brokers/topics/topics的名字/partitions/分区号/state
,更新其leader和ISR列表信息。
Kafka的Producer发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程——main线程和Sender线程,以及一个共享变量——RecordAccumulator。main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。
main线程和Sender线程工作流程:
Producer将发送的消息封装成ProducerRecord对象,在ProducerRecord中可以指定发送的分区、key、value。key可以自定义(如果不指定,默认为null),value就是我们要发送的内容。
发送的消息经过Interceptors拦截器(拦截器可以对发送的消息进行修改)
消息根据key和value的类型进行序列化
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
消息经过Partitioner(分区器)发送到RecordAccumulator(共享变量)中,在RecordAccumulator
Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets,里面记录了每个消费者消费的位置
Consumer消费数据时的可靠性是很容易保证的,因为数据在Kafka中是持久化的,故不用担心数据丢失问题。
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
所以offset的维护是Consumer消费数据是必须考虑的问题。
Consumer有两种offset维护(提交)方式
自动提交offset
为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交offset的功能(基于时间提交的)。
enable.auto.commit:是否开启自动提交offset功能
auto.commit.interval.ms:自动提交offset的时间间隔
手动提交offset
虽然自动提交offset十分简介便利,但由于其是基于时间提交的,开发人员难以把握offset提交的时机。因此Kafka还提供了手动提交offset的API。
手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。两者的相同点是,都会将本次拉取的一批数据最高的偏移量提交;不同点是,commitSync(同步提交)阻塞当前线程,一直到offset提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而commitAsync(异步提交)则没有失败重试机制,故有可能提交失败。
像发送数据一样,Consumer每次拉取的也是一批数据,fetch.min.bytes设置消费者最低拉取多少条数据,如果数据不足,将等待这么多数据后再进行拉取,默认为1字节,即只要有数据就拉取。
对于Consumer而言,事务的保证就会相对较弱,尤其时无法保证Commit的信息被精确消费。这是由于Consumer可以通过offset访问任意信息,而不同的Segment File生命周期不同(Kafaka消息默认保存7天),所以有些消息可能会访问不到(比如访问很早的offset)。
消费者无论是同步提交还是异步提交offset,都有可能会造成数据的漏消费或者重复消费。先提交offset后消费,可能发生提交了offset,但还没有消费时挂了,下次启动后就漏消费了;而先消费后提交offset,在提交offset之前挂了,有可能会造成数据的重复消费。(精准一次性消费)
如果想完成Consumer端的精准一次性消费,那么需要kafka消费端将消费过程和提交offset过程做原子绑定。此时我们需要将kafka的offset保存到支持事务的自定义介质中(比如mysql)。或者在下游应用再做处理也行
生产者的可以用Exactly Once
使用Kafka Eagle主要监控:
ISR(In-Sync Replicas):与leader保持同步的follower集合(当然也有leader),当leader挂掉后,从ISR列表中随机选取Leader
OSR:Out-of-Sync Replicas(移除ISR列表的副本集合)
AR(Assigned Replicas):分区的所有副本
AR=ISR+OSR。
设想以下情景:leader收到数据,所有follower都开始同步数据,但有一个follower,因为某种故障,迟迟不能与leader进行同步,那leader就要一直等下去,直到它完成同步,才能发送ack。这个问题怎么解决呢?
Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给producer发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR列表【放入OSR(Out-of-Sync Replicas )列表】,该时间阈值由replica.lag.time.max.ms
参数设定(默认为30s)。Leader发生故障之后,就会从ISR中选举新的leader。
对LogStartOffset的理解
LogStartOffset,一般情况下,日志文件的起始偏移量 logStartOffset 等于第一个segment的.log的 baseOffset,但这并不是绝对的,logStartOffset 的值可以通过 DeleteRecordsRequest 请求、使用 kafka-delete-records.sh 脚本、日志的清理和截断等操作进行修改。
kafka 中的每个 partition 中的消息在写入时都是有序的,每条消息都有一个offset,故只能保证分区内有序。整体是无序的,如果要实现整体有序,就只能一个分区
一般group中的consumer的个数和Topic的分区数相等。如果group中的consumer大于Topic的分区数,多余的消费者不会工作。注意:多个消费者不能同时去消费一个分区(如果多个消费者消费一个分区,那不就重复消费了,因为每个消费者都有自己的offset)
如果group中的consumer小于Topic的分区数,则需要一个consumer去消费多个分区。
offset+1,即下一条记录的offset。
消费者无论是同步提交还是异步提交offset,都有可能会造成数据的漏消费或者重复消费。先提交offset后消费,可能发生提交了offset,但还没有消费时挂了,下次启动后就漏消费了;而先消费后提交offset,在提交offset之前挂了,有可能会造成数据的重复消费。
可以增加,不可以减少
bin/kafka-topics.sh --zookeeper localhost:2181/kafka --alter --topic topic-config --partitions 3
我们可以使用 kafka-topic.sh --alter
命令对 Kafka 增加 Kafka 的分区数据,但是 Kafka 不支持减少分区数。
Kafka 分区数据不支持减少是由很多原因的,比如减少的分区其数据放到哪里去?是删除,还是保留?删除的话,那么这些没消费的消息不就丢了。如果保留这些消息如何放到其他分区里面?追加到其他分区后面的话那么就破坏了 Kafka 单个分区的有序性。如果要保证删除分区数据插入到其他分区保证有序性,那么实现起来逻辑就会非常复杂。
Kafka 0.9版本之前,consumer默认将offset保存在Zookeeper中,从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets,里面记录了每个消费者消费的位置
partition leader (在ISR列表中随机选一个),controller(先到先得)
Controller选择是通过先到先得的方式,即谁先注册到zookeeper下,谁就是Controller,注册的结点路径是/kafka/controller
,内容如下:
[zk: localhost:2181(CONNECTED) 3] get /kafka/controller
{"version":1,"brokerid":2,"timestamp":"1627297118666"}
注意:zookeeper的
/kafka
根目录是在配置kafka的时候指定的,如果配置没有指定,默认把/
当做根目录
不能及时与leader同步,暂时踢出ISR,等其追上leader之后再重新加入。
Leader维护了一个动态的in-sync replica set (ISR),意为和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给producer发送ack。如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms
参数设定(默认为30s)。Leader发生故障之后,就会从ISR中选举新的leader。
如何恢复?可以看下 第7题: kafka数据可靠性保证 / kafka数据丢失问题,及如何保证?
(1)flume写数据到kafka
分为2种:
一般采用第二种方式(该方式不需要sink)
在Flume的Source中可以指定key的值,就可以指定将数据写的kafka的某个分区。
(2)flume从kafka读取数据
kafkaSource->memeory channel->Sink
1)副本因子不能大于 Broker 的个数;
2)第一个分区(编号为0)的第一个副本放置位置是随机从 brokerList 选择的;
3)其他分区的第一个副本放置位置相对于第0个分区依次往后移。也就是如果我们有5个 Broker,5个分区,假设第一个分区放在第四个 Broker 上,那么第二个分区将会放在第五个 Broker 上;第三个分区将会放在第一个 Broker 上;第四个分区将会放在第二个 Broker 上,依次类推;
4)剩余的副本相对于第一个副本放置位置其实是由 nextReplicaShift 决定的,而这个数也是随机产生的;
在Kafka中,当有新消费者加入或者消费者订阅的topic数发生变化(比如更改topic)时,会触发Rebalance(再均衡:在同一个消费者组当中,分区的所有权从一个消费者转移到另外一个消费者)机制,Rebalance顾名思义就是重新均衡消费者消费。Rebalance的过程如下:
第一步:所有成员都向coordinator发送请求,请求入组。一旦所有成员都发送了请求,coordinator会从中选择一个consumer担任leader的角色,并把组成员信息以及订阅信息发给leader。
第二步:leader开始分配消费方案,指明具体哪个consumer负责消费哪些topic的哪些partition。一旦完成分配,leader会将这个方案发给coordinator。coordinator接收到分配方案之后会把方案发给各个consumer,这样组内的所有成员就都知道自己应该消费哪些分区了。
所以对于Rebalance来说,Coordinator起着至关重要的作用。
补充一个小问题:
一个topic可以被多个消费者组消费吗?可以,每个消费者组中的每个消费者都有自己的offset,数据在kafka中默认保留7天,每个消费者组都完整的把每个消息都消费一遍(所以,每个消费者组消费的数据是重复的)
1)由于是批量发送,数据并非真正的实时; (生成者发送数据的时候是批量,消费者消费数据的时候也是批量)
2)不支持物联网传感数据直接接入;
3)仅支持统一分区内消息有序,无法实现全局消息有序;
4)监控不完善,需要安装插件;
5)依赖zookeeper进行集群管理。
传统的消息传递方法包括两种:
高性能:单一的Kafka代理可以处理成千上万的客户端,每秒处理数兆字节的读写操作,Kafka性能远超过传统的ActiveMQ、RabbitMQ等,而且Kafka支持Batch操作;
可扩展:Kafka集群可以透明的扩展,增加新的服务器进集群;
容错性: Kafka每个Partition数据会复制到几台服务器,当某个Broker失效时,Zookeeper将通知生产者和消费者从而使用其他的Broker。
Kafa consumer消费消息时,向broker发出fetch请求去消费特定分区的消息,consumer指定消息在日志中的偏移量(offset),就可以消费从这个位置开始的消息,customer拥有了offset的控制权,可以向后回滚去重新消费之前的消息,这是很有意义的。
旧的 Kafka 消费者 API 主要包括:SimpleConsumer(简单消费者) 和 ZookeeperConsumerConnectir(高级消费者)。SimpleConsumer 名字看起来是简单消费者,但是其实用起来很不简单,可以使用它从特定的分区和偏移量开始读取消息。
高级消费者和现在新的消费者有点像,有消费者群组,有分区再均衡,不过它使用 ZK 来管理消费者群组,并不具备偏移量和再均衡的可操控性。
现在的消费者同时支持以上两种行为,所以为啥还用旧消费者 API 呢?
Kafka服务器可以接收到的消息的最大大小是1000000(1G)字节。
不会,kafka中数据的删除跟有没有消费者消费完全无关。数据的删除,只跟kafka broker上面上面的这两个配置有关:
log.retention.hours=48 #老版本数据最多保存48小时,新版默认保存7天
可以通过两种API消费Spark的数据:
ReceiverAPI目前只有一个版本即0-8 ReceiverAPI, DirectAPI有两个版本,分别是0-8 DirectAPI 和 0-10 DirectAPI,它们区别如下:
(1)0-8 ReceiverAPI
(2)0-8 DirectAPI
08 DirectAPI的offset有两种存储方式
CheckPoint方式的缺点:
(3)0-10 DirectAPI
0-10 DirectAPI的offset存储方式
__consumer_offsets
系统主题中