大数据面试(kafka)
1.怎么解决kafka数据丢失的问题?
kafka有两种发送数据的模式,异步和同步,默认选择的是同步发送消息。
同步:在同步模式如果ack消息确认机制为1只保证主节点写入成功,在进行主从复制如果主节点宕机,从节点将没有数据,数据就会丢失。
所以设置ack消息确认机制为-1,消息写入主节点和从节点才算成功。
异步:在异步模式当缓冲区满了,如果ack=0就会清空缓冲池消息。
所以在kafka配置文件设置成不限制阻塞超时时间,一直等待就保证数据不会丢失。
同步和异步区别:
同:发送消息到分区再进行主从复制,客户端收到服务器确认。
异:发送消息到缓冲区,然后写入集群中,速度比较快。
ack=0只发送一次,不管是否成功。
ack=1主写入就成功。
ack=-1主副本都写入成功。
2.kafka消费组?
消息组里面有多个消费者,消费topic下面所有分区,
每个分区只能由同一个消费组内一个消费者来消费。
3.kafka为什么高吞吐量。
1)顺序读写:数据写入磁盘分随机读写和顺序读写, 随机读写会有一个寻址和写入的过程比较消耗时间, 顺序读写没有寻址过程提高读写速度。
2)零拷贝。引用sendfile系统调用跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射。
3)分区。并行消费。
4)批量发送消息。生产者发送消息的时候,可以将消息缓存在本地,等到了固定条件发送到kafka。
- 等消息条数到固定条数
- 一段时间发送一次
5)数据压缩。生产者可以通过GZIP或Snappy格式对消息集合进行压缩。压缩的好处就是减少传输的数据量,减轻对网络传输的压力。
4.zookeeper在kafka中的作用?
老版本: 存储topic分区信息和消费topic记录offset偏移量。
新版本(kafka-0-9): 进行leader选举和检测broker是否存活。
5.kafka为什么是有序的?
1)kafka默认保证同一个分区内消息是有序的,设置topic只用一个分区这样就可以保证全局消息有序,但是并发量低。
2)指定消息发送到同一个分区,保证消息有序。
8.kafka的topic partition ar isr osr分别是什么?
broker:kafka集群中的服务器,一台服务器就一个broker。
topic: 消息类别。
partition: 一个topic包含一个或多个partition分区,物理概念。partition有一个leader副本,其余的都是follower,leader负责读与写,follower同步leader的数据。
produce: 消息生产者。
consumer: 消息消费者。
consumer group: 每个consumer属于特定的consumer group,一个topic中每一个分区同一个时间只能被一个消费组里面一个线程消费。
offset: 每一个消息添加到分区时都会被分配一个offset,它是消息在分区中唯一编号,kafka通过offset保证消息在分区内顺序是有序的。
hw: 高水位, 标识一个特定的offset, 消费者只能拉取这个offset之前的消息。
ar:分区中的所有副本。
isr:所有与leader副本保持一定程度同步的副本。
osr:leader副本同步滞后过多的副本。
一个分区中有5个副本, 其中1个是leader, 4个是follower。如果3个follower副本时刻与leader保持一致, 3个follower副本和leader副本组成ISR。
1个follower副本没有与leader副本同步, 这个属于OSR副本。
AR = ISR + OSR
9.kafka分布式分区存储?
https://www.cnblogs.com/yitianyouyitian/p/10287293.html
4个broker 4个分区 2个副本
一个topic包含4个Partition,2 Replication(拷贝),也就是说全部的消息被放在了4个分区存储,为了高可用,将4个分区做了2份冗余,然后根据分配算法.将总共8份数据,分配到broker集群上.
结果就是每个broker上存储的数据比全量数据要少,但每份数据都有冗余,这样,一旦一台机器宕机,并不影响使用.比如图中的Broker1,宕机了.那么剩下的三台broker依然保留了全量的分区数据.
所以还能使用,如果再宕机一台,那么数据不完整了.当然你可以设置更多的冗余,比如设置了冗余是4,那么每台机器就有了0123完整的数据,宕机几台都行.需要在存储占用和高可用之间做衡量.
10.kafka文件存储机制?
Kafka中读写message有如下特点:
写message
消息从java堆转入page cache(即物理内存)。
由异步线程刷盘,消息从page cache刷入磁盘。
读message
消息直接从page cache转入socket发送出去。
当从page cache没有找到相应数据时,此时会产生磁盘IO,从磁
盘Load消息到page cache,然后直接从socket发出去
11.kafka过期消息删除机制?
kafka作为一个消息中间件,是需要定期处理数据的,否则磁盘就爆了。
1)根据数据的时间长短进行清理,例如数据在磁盘中超过多久会被清理(默认是7天)
2)根据文件大小的方式给进行清理,例如数据大小超过多大时,删除数据(大小是按照每个partition的大小来界定的)。
12.kafka分区策略 ?
Kafka分区分配策略,就是它的内部的默认的分区分配策略:Range 和 RoundRobin(轮询)。当下面的事情发生的时候,Kafka将会进行一次分区分配。
1、当同一个Consumer Group 内新增消费者。(消费者层面发生改动)
2、消费者离开当前所属的Consumer Group,包括Shuts down(关闭、停工)或者是crashes(崩溃)。(消费者层面发生改动)
3、就是消费者订阅的主题新增的分区的时候!
第一个默认的分区策略:Range Startegy(根据范围消费)
Range startegy是对每个主题而言的 , 首先对同一个主题里面的分区按照序号进行排序,并对消费者按照字母进行排序。在对十个分区排序的话是0-9;消费者线程排完序是C1-0,C2-0,C2-1。然后用partitions的总数除以消费者的总数来决定每个消费者线程消费几个分区。如果有余数,那么前面的几个消费者线程将会多消费一个分区。在我们的例子里面,我们有十个分区,三个消费者线程,10/3=3---1,那么消费者线程C1-0 将会多消费一个分区,所以最后分区分配的结构看起来是这样的:
C1-0将消费0,1,2,3分区 C2-0将消费4,5,6分区 C2-1将消费7,8,9分区
如果有第十一个分区的话,那么分区是这样的:
C1-0将消费0,1,2,3分区 C2-0将消费4,5,6,7分区 C2-1将消费8,9,10分区
如果我们有2个主题(T1和T2),分别都有十个分区,那么最后的分配结果是:
C1-0将消费T1主题中的0,1,2,3分区以及T2主题中0,1,2,3分区 C2-0将消费T1主题中的4,5,6分区以及T2主题中的4,5,6,分区 C2-1将消费T1主题中的7,8,9分区以及T2主题中的7,8,9分区
这就是消费的策略! 就是用总的分区数/消费者线程总数=每个消费者线程应该消费的分区数。当还有余数的时候就将余数分别分发到另外的消费组线程中。
在这里我们不难看出来。C1-0消费者线程比其他消费者线程多消费了两个分区,这就是Range Strategy的一个明显的弊端。 当分区很多的时候,会有个别的线程压力巨大!
第二个默认的分区策略:RoundRobin strategy(轮询的消费策略)
原理是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询方式逐个将分区以此分配给每个消费者。
如果同一个消费组内所有的消费者的订阅topic都是相同的,那么RoundRobinAssignor策略的分区分配会是均匀的。举例,假设消费组中有2个消费者C0和C1,都订阅了主题t0和t1,并且每个主题都有3个分区,那么所订阅的所有分区可以标识为:t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终的分配结果为:
消费者C0:t0p0、t0p2、t1p1
消费者C1:t0p1、t1p0、t1p2
如果同一个消费组内的消费者所订阅的信息是不相同的,那么在执行分区分配的时候就不是完全的轮询分配,有可能会导致分区分配的不均匀。
遗憾的是,目前我们还不能自定义分区分配策略,只能通过partition.assignment.strategy参数选择range或roundrobin。 partition.assignment.strategy参数默认的值是range。
13.kafka消费策略
1.单线程消费
依次遍历topic下的多个分区,效率低, 如果分区数几十上百个,单线程无法高效的取出数据。
2.多线程消费
(1)独立消费者模式
独立消费者可以不设置 group.id 属性。支持多线程的方式,每个线程消费指定分区进行消费。
但这种方式有一个问题:可用性不高,当其中一个进程挂掉之后;该进程负责的分区数据没法转移给其他进程处理。
(2)消费组模式
消费组模式应当是使用最多的一种消费方式。
我们可以创建 N 个消费者,当这些实例都用同一个 group.id
来创建时,他们就属于同一个消费组。
topic一个分区消息只会被同一个消费组下一个消费者消费。
14.kafka如何保证不丢数据?
这个问题要从3个方面来保证数据不丢失:生产者、服务端、消费者。
1.producer 生产端是如何保证数据不丢失的?
(1)ack = -1
主、副本都写入成功,才会向ack发送消息。
(2)重试机制 retries
“retries”和“retries.backoff.ms”决定了重试机制,也就是如果一个请求失败了可以重试几次,每次重试的间隔是多少毫秒。
如何选取:
1.高可用型 配置:acks = all,retries > 0 retry.backoff.ms=100(毫秒) (并根据实际情况设置retry可能恢复的间隔时间) 优点:这样保证了producer端每发送一条消息都要成功,如果不成功并将消息缓存起来,等异常恢复后再次发送。 缺点:这样保证了高可用,但是这会导致集群的吞吐量不是很高,因为数据发送到broker之后,leader要将数据同步到fllower上,如果网络带宽、不稳定等情况时,ack响应时间会更长 2.折中型 配置:acks = 1 retries > 0 retries 时间间隔设置 (并根据实际情况设置retries可能恢复的间隔时间) 优点:保证了消息的可靠性和吞吐量,是个折中的方案 缺点:性能处于2者中间 3.高吞吐型 配置:acks = 0 优点:可以相对容忍一些数据的丢失,吞吐量大,可以接收大量请求 缺点:不知道发送的消息是 否成功
2.consumer端是如何保证数据不丢失的?
(1)设置手动提交
在consumer消费阶段,对offset的处理,关系到是否丢失数据,是否重复消费数据,因此,我们把处理好offset就可以做到exactly-once && at-least-once(只消费一次)数据。
当enable.auto.commit=true时:
表示由kafka的consumer端自动提交offset,当你在pull(拉取)30条数据,在处理到第20条时自动提交了offset,但是在处理21条的时候出现了异常,当你再次pull数据时,由于之前是自动提交的offset,
所以是从30条之后开始拉取数据,这也就意味着21-30条的数据发生了丢失。
当enable.auto.commit=false时:
由于上面的情况可知自动提交offset时,如果处理数据失败就会发生数据丢失的情况。那我们设置成手动提交。
当设置成false时,由于是手动提交的,可以处理一条提交一条,也可以处理一批,提交一批,由于consumer在消费数据时是按一个batch来的,当pull了30条数据时,如果我们处理一条,
提交一个offset,这样会严重影响消费的能力,那就需要我们来按一批来处理,或者设置一个累加器,处理一条加1,如果在处理数据时发生了异常,
那就把当前处理失败的offset进行提交(放在finally代码块中)注意一定要确保offset的正确性,当下次再次消费的时候就可以从提交的offset处进行再次消费。
(2)保证确保消息只被处理一次处理,同时确保幂等性
exactly-once & at-least-once
如何保证消息只获取一次并且确定被处理呢?这就需要我们在处理消息的时候要添加一个unique key,
假如pull 一个batch 100条的消息,在处理到第80条的时候,由于网络延迟、或者crash的原因没有来得及提交offset,被处理的80条数据都添加了unique key, 可以存到到DB中或者redis中(推荐,因为这样更快),
当consumer端会再次poll消费数据时,因为没有提交offset,所以会从0开始消费数据,如果对之前已经消息过的数据没有做unique key的处理,那么会造成重复消息之前的80条数据,但是如果把每条对应的消息都添加了unique key,
那就只需要对被处理的消息进行判断,有没有unique key 就可以做到不重复消费数据的问题,这样也同时保证了幂等性。
3.broker端是如何保证数据不丢失的?
(1)设置副本数,提高kafka高可用,通常是3个,极限是5个,如果多了也会影响开销。
(2)unclean.leader.election.enable=false。
15.kafka事务机制?
Kafka中的事务特性主要用于以下两种场景:
1.生产者发送多条消息可以封装在一个事务中,形成一个原子操作。多条消息要么都发送成功,要么都发送失败。
2.read-process-write模式:将消息消费和生产封装在一个事务中,形成一个原子操作。在一个流式处理的应用中,常常一个服务需要从上游接收消息,然后经过处理后送达到下游,这就对应着消息的消费和生成。
16.kafka的leader选举过程?
topic每个分区都有一个leader和零或多个followers.所有的读写操作都由leader处理,一般分区的数量都比broker的数量多的多,各分区的leader均匀的分布在brokers中。所有的followers都复制leader的日志,日志中的消息和顺序都和leader中的一致。
Kafaka动态维护了一个同步状态的副本的集合,简称ISR,在这个集合中的节点都是和leader保持高度一致的。因此这个集合中的任何一个节点随时都可以被选为leader, ISR在ZooKeeper中维护。ISR中有f+1个节点,就可以允许在f个节点down掉的情况下不会丢失消息并正常提供服。ISR的成员是动态的,如果一个节点被淘汰了,当它重新达到“同步中”的状态时,他可以重新加入ISR.这种leader的选择方式是非常快速的,适合kafka的应用场景。
17.kafka控制器 controller?
在Kafka集群中会有一个或者多个broker,其中有一个broker会被选举为控制器(Kafka Controller),它负责管理整个集群中所有分区和副本的状态。当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本。当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有broker更新其元数据信息。当使用kafka-topics.sh脚本为某个topic增加分区数量时,同样还是由控制器负责分区的重新分配。
Kafka中的控制器选举的工作依赖于Zookeeper,成功竞选为控制器的broker会在Zookeeper中创建/controller这个临时(EPHEMERAL)节点,此临时节点的内容参考如下:
{"version":1,"brokerid":0,"timestamp":"1529210278988"}
在任意时刻,集群中有且仅有一个控制器。每个broker启动的时候会去尝试去读取/controller节点的brokerid的值,如果读取到brokerid的值不为-1,则表示已经有其它broker节点成功竞选为控制器,所以当前broker就会放弃竞选;如果Zookeeper中不存在/controller这个节点,或者这个节点中的数据异常,那么就会尝试去创建/controller这个节点,当前broker去创建节点的时候,也有可能其他broker同时去尝试创建这个节点,只有创建成功的那个broker才会成为控制器,而创建失败的broker则表示竞选失败。每个broker都会在内存中保存当前控制器的brokerid值,这个值可以标识为activeControllerId。
18.kafka控制器controller选举机制?
如果在Kafka系统运行过程中,当前的控制器出现故障导致不可用,那么Kafka系统会从其他正常运行的Broker中重新选举出新的控制器。
1.第一个启动的代理节点,会在Zookeeper系统里面创建一个临时节点/controller,并写入该节点的注册信息,使该节点成为控制器;
2.其他的代理节点陆续启动时,也会尝试在Zookeeper系统中创建/controller节点,但是由于/controller节点已经存在,所以会抛出“创建/controller节点失败异常”的信息。创建失败的代理节点会根据返回的结果,判断出在Kafka集群中已经有一个控制器被成功创建了,所以放弃创建/controller节点, 这样就确保了Kafka集群控制器的唯一性;
3.其他的代理节点,会在控制器上注册相应的监听器,各个监听器负责监听各自代理节点的状态变化。当监听到节点状态发生变化时,会触发相应的监听函数进行处理。
18.kafka对接sparkstreaming保证不丢数据?
sparkStreaming接收kafka数据的方式有两种:
(1)利用receiver接收数据
(2)直接从kafka读取数据(Direct方式)
1.数据不丢失
(1)receiver方式确保数据零丢失, 必须在Spark Streaming中另外启用预写日志。这将同步保存所有接收到的kafka数据到分布式文件系统(hdfs)上, 以便在发生故障时可以恢复所有数据。
(2)Direct方式手动提交offset,每次streaming消费了kafka数据后将offset值更新到kafka; 当程序挂点或者升级的时候, 就可以接着上次读取, 实现数据的零丢失。
spark-streaming-kafka-0-10在这个集成包的下面, sparkstreaming和kafka集成只有一种方式,那就是direct模式, 而且这个版本的消费者偏移量和zookeeper没有任何关系!
(Direct需要用户采用checkpoint或者第三方存储kafka来维护offsets, 而不像Receiver-based那样, 通过hdfs来维护offsets, 提高用户的开发成本。)
2.数据不重复
这里业务场景被区分为两个:
(1)幂等操作, 所谓幂等操作就是重复执行不会产生问题。如果是这种场景下, 你就不需要做额外的任何工作了。
(2)业务代码需要自身添加事务操作。如果应用场景不允许数据被重复执行的, 那只能通过业务自身的逻辑代码来解决。
dstream.foreachRDD {(rdd, time) = rdd.foreachPartition { partitionIterator => val partitionId = TaskContext.get.partitionId() val uniqueId = generateUniqueId(time.milliseconds,partitionId) //use this uniqueId to transationally commit the data in partitionIterator } }