没想到 Kafka 还会这样问,学会这些带你轻松搞定大厂面试!
一、前言
自上次师兄遭受了面试官 「Kafka」 的暴击追问后,回来发奋图强,企图“「吊打面试官」”,奈何还是面试官套路深啊,最近的面试,又被问到「知识盲点」了!让我们一起来看看,可怜的师兄又遇到了哪些让人头秃的问题 {{{(>_<)}}} !
二、面试题
「关于消息队列(基础)」
❝常用的消息队列有哪些 ? 为何使用消息队列 ? Kafka与传统的消息队列服务有什么不同?
❞
「关于Kafak存储机制与读写流程」
❝Kafka的读写流程
❞
Kafka的文件结构 broker存储数据有哪些?Kafka怎么保证partition的偏移量?follower参加读写吗?Kafka为什么只让leader进行读写?如果让follower也可以读写,会怎么样?
「关于Kafak数据可靠性的保证」
❝Kafak是怎么实现对offset的索引 ack设为1的时候,producer生产数据时候,leader接受到数据,返回ack之后,leader宕机了,那么数据是否就丢失了?ack配置成-1是不是数据一定会进磁盘?是不是不管怎么是设置ack还是有概率丢失消息?
❞
「关于Kafak高可用性」
❝简要描述Kafak数据高可用的原理是什么 一个topic三个broker一个leader,leader所在的broker挂了,选举原则是什么?Kafka api low-level与high-level有什么区别,使用low-level api需要处理哪些细节?
❞
❝以上,「便是和师兄聊到了一些他踩坑的面试题和一些同类型的面试题」,我进行了整理。说实话,如果不深入的了解Kafak,我想小伙伴们在面试时多半是要踩雷的,当然也包括我!(ノへ ̄、) 所以,为了避免被面试官疯狂吊打,我们还做好充分准备,不求”翻身做主“,但求旗鼓相当,那样Offer轻松到手岂不妙哉!
❞
三、思路
还是和上一篇博客一样 师兄大厂面试遇到面试官的 Kafka 暴击三连问,快面哭了!我个人建议,大家最好先搭建一个技术栈完整的知识框架,这样在面试时就能做到无懈可击啦!
四、关于消息队列
4.1 Kafka创建背景
Kafka是一个消息系统,原本开发自LinkedIn,用作LinkedIn的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。现在它已被多家不同类型的公司「作为多种类型的数据管道和消息系统」使用。
「活动流数据」是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分。「这种数据通常的处理方式是先把各种活动以日志的形式写入某种文件,然后周期性地对这些文件进行统计分析」。近年来,活动和运营数据处理已经成为了网站软件产品特性中一个至关重要的组成部分,这就需要一套稍微更加复杂的基础设施对其提供支持,Kafka也就应运而生。
4.2 为何使用消息队列
「解耦」在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
「冗余」有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的“插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
「扩展性」因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。
「灵活性 & 峰值处理能力」在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
「可恢复性」系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
「顺序保证」在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。Kafka保证一个Partition内的消息的有序性。
「缓冲」在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。该缓冲有助于控制和优化数据流经过系统的速度。
「异步通信」很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
4.3 和常用消息队列的对比
「RabbitMQ」RabbitMQ是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正因如此,它非常重量级,更适合于企业级的开发。同时实现了Broker构架,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或者数据持久化都有很好的支持。
「Redis」Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。
「ZeroMQ」ZeroMQ号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演这个服务器角色。你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。但是ZeroMQ仅提供非持久性的队列,也就是说如果宕机,数据将会丢失。其中,Twitter的Storm 0.9.0以前的版本中默认使用ZeroMQ作为数据流的传输(Storm从0.9版本开始同时支持ZeroMQ和Netty作为传输模块)。
「ActiveMQ」ActiveMQ是Apache下的一个子项目。类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。
「Kafka/Jafka」Kafka是Apache下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制统一了在线和离线的消息处理。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。
4.4 总结
关于 Kafka 和传统消息队列有何不同,在上一篇文章中已经做出部分解答,这里我就不再赘述。「一般情况下,Kafka应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用」。
五、关于Kafka存储机制与读写流程
5.1 Kafka存储机制
在Kafka 架构深入已经讲明了这个问题,但这张图的结构更易于理解,如下。
「Broker」:消息中间件处理结点,一个Kafka节点就是一个broker,多个broker可以组成一个Kafka集群。 「Topic」:一类消息,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能够同时负责多个topic的分发。 「Partition」:topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列。 「Segment」:partition物理上由多个segment组成。
Kafka用topic对消息进行归类,每一个topic可以分为多个分区,分区中的消息不重复,每个分区又有很多个segment(段),「segment是在磁盘上就是一对文件,包含index和log文件,两种文件名相同,后缀不同」。
「每个topic的第一个segment的两种文件都是00000000000000000000.index和00000000000000000000.log,后来新产生的文件名都以上一个segment中最后一条消息的offset(偏移量)结尾,不足20个字符的用0填充。」
「Tip:Kafka数据被消费后虽然不会被立即删除,但不可能一直不删除,Kafka根据两个设置定时检测做删除操作」:
基于时间:log.retention.hours=168 基于大小:log.retention.bytes=1073741824
「满足任何一个都会删除之前的segment,记住不是删除某一个消息,删除的最小单位是segment。」
以上面的Segment文件为例,展示出Segment:00000000000000170410的index文件和log文件的对应的关系,如下图:
如上图,「index索引文件存储大量的元数据,log数据文件存储大量的消息,索引文件中的元数据指向对应数据文件中message的物理偏移地址。」
❝❞
「如何根据索引文件元数据定位数据位置?」
如:index索引文件元数据[3,348],在log数据文件中表示第3个消息,在全局partition中表示170410+3=170413个消息,该条消息在相应log文件中的物理偏移地址为348。「那么如何从partition中通过offset查找message呢?」
如:读取offset=170418的消息,查找segment文件,其中,
α. 00000000000000000000.index为最开始的文件,
β. 00000000000000170410.index(start offset=170410+1=170411),
γ. 00000000000000239430.index(start offset=239430+1=239431),
因此,定位offset=170418在00000000000000170410.index索引文件中。其他后续文件可以依次类推,以偏移量命名并排列这些文件,然后根据二分查找法就可以快速定位到具体文件位置。其次,根据00000000000000170410.index文件中的[8,1325]定位到00000000000000170410.log文件中的1325的位置进行读取。「那么怎么知道何时读完本条消息,否则就读到下一条消息的内容了?」
因为消息都具有固定的物理结构,包括:offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段,可以确定一条消息的大小,即读取到哪里截止。
5.2 读写流程
「写流程:」
1.连接ZK集群,从ZK中拿到对应topic的partition信息和partition的Leader的相关信息
2.连接到对应Leader对应的broker
3.将消息发送到partition的Leader上
4.其他Follower从Leader上复制数据
5.依次返回ACK
6.直到所有ISR中的数据写完成,才完成提交,整个写过程结束
因为是描述写流程,没有将replica与ZK的心跳通讯表达出来,心跳通讯就是为了保证kafka高可用。一旦Leader挂了,或者Follower同步超时或者同步过慢,都会通过心跳将信息报告给ZK,由ZK做Leader选举或者将Follower从ISR中移动到OSR中。
「读流程:」
1.连接ZK集群,从ZK中拿到对应topic的partition信息和partition的Leader的相关信息
2.连接到对应Leader对应的broker
3.consumer将自己保存的offset发送给Leader
4.Leader根据offset等信息定位到segment(索引文件和日志文件)
5.根据索引文件中的内容,定位到日志文件中该偏移量对应的开始位置读取相应长度的数据并返回给consumer
5.3 Kafka数据一致性
「数据一致性:Kafka是保存副本 leader读写,follower只备份;而 zookeeper是 leader读写,follower负责读」 。
看到一位博主对 Kafka和Zookeeper 数据一致性 分析的很详细,大家可以学习下,这里我参考了关于Kafka的部分。
「Kafka」,「只有leader 负责读写,follower只负责备份!!!」,「如果leader宕机的话,Kafaka动态维护了一个同步状态的副本的集合(a set of in-sync replicas)」,简称「ISR」,「ISR中有f+1个节点,就可以允许在f个节点down掉的情况下不会丢失消息并正常提供服」。「ISR的成员是动态的,如果一个节点被淘汰了,当它重新达到“同步中”的状态时,他可以重新加入ISR。因此如果leader宕了,直接从ISR中选择一个follower就行。」
Kafka在引入Replication之后,同一个Partition可能会有多个Replica,而这时需要「在这些Replication之间选出一个Leader」,「Producer和Consumer只与这个Leader交互」,「其它Replica作为Follower从Leader中复制数据」。「因为需要保证同一个Partition的多个Replica之间的数据一致性(其中一个宕机后其它Replica必须要能继续服务并且即不能造成数据重复也不能造成数据丢失)」。
如果没有一个Leader,所有Replica都可同时读/写数据,那就需要保证多个Replica之间互相(N×N条通路)同步数据,数据的一致性和有序性非常难保证,「大大增加了Replication实现的复杂性,同时也增加了出现异常的几率」。
而引入Leader后,只有Leader负责数据读写,Follower只向Leader顺序Fetch数据(N条通路),系统更加简单且高效。
「Kafka:由于Kafka的使用场景决定,其读取数据时更关注数据的一致性。」
从leader读取和写入可以保证所有客户端都得到相同的数据,否则可能存在一些在ISR中注册的节点(replication-factor大于min.insync.replicas),因未来得及更新副本而无法提供的数据。相应的为了规避都从leader上读取带来的资源竞争,可以根据不同topic和不同partition设置不同的leader。
「如下所示:leader==>负责读写,follower 负责同步,只负责备份。」
六、关于kafka数据可靠性的保证
为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到 producer 发送的数据后,都需要向 producer 发送 ack(acknowledgement 确认收到),如果 producer 收到 ack,就会进行下一轮的发送,否则重新发送数据。
6.1 副本数据同步策略
Kafka 选择了第二种方案,原因如下:
同样为了容忍 n 台节点的故障,第一种方案需要 2n+1 个副本,而第二种方案只需要 n+1个副本,而 Kafka 的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。 虽然第二种方案的网络延迟会比较高,但网络延迟对 Kafka 的影响较小。
6.2 ISR集合
采用第二种方案之后,设想以下情景:leader 收到数据,所有 follower 都开始同步数据,但有一个 follower,因为某种故障,迟迟不能与 leader 进行同步,那 leader 就要一直等下去,直到它完成同步,才能发送 ack。这个问题怎么解决呢?
Leader 维护了一个动态的 in-sync replica set (ISR),意为和 leader 保持同步的 follower 集合。当 ISR 中的 follower 完成数据的同步之后,leader 就会给 follower 发送 ack。如果 follower长时间 未 向 leader 同 步 数 据 , 则 该 follower 将 被 踢 出 ISR , 该 时 间 阈 值 由replica.lag.time.max.ms 参数设定。Leader 发生故障之后,就会从 ISR 中选举新的 leader。
6.3 ACK应答机制
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等 ISR 中的 follower 全部接收成功。
所以 Kafka 为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置。
「acks 参数配置:」
「0」:producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据;
「1」:producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,「如果在 follower同步成功之前 leader 故障,那么将会丢失数据」;
「-1(all)」:producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盘成功后才返回 ack。「但是如果在 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,那么会造成数据重复」。
6.4 故障处理细节
「LEO:指的是每个副本最大的 offset;HW:指的是消费者能见到的最大的 offset,ISR 队列中最小的 LEO。」
(1)follower 故障 follower 发生故障后会被临时踢出 ISR,待该 follower 恢复后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。等该 follower 的 LEO 大于等于该 Partition 的 HW,即 follower 追上 leader 之后,就可以重新加入 ISR 了。
(2)leader 故障 leader 发生故障之后,会从 ISR 中选出一个新的leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader同步数据。
注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
七、关于Kafak高可用性
关于 Kafak高可用性,前面我已经写了部分,例如:「Kafka的存储机制、如何保证数据一致性,ISR集合,ACK应答机制等」,这些在面试时被问到都可以谈一谈,那就面试题,我这里再和大家讲讲 Kafka的选举机制。
7.1 Leader 选举机制
「Kafka的Leader是什么?」
首先Kafka会将接收到的消息分区(partition),每个主题(topic)的消息有不同的分区。这样一方面消息的存储就不会受到单一服务器存储空间大小的限制,另一方面消息的处理也可以在多个服务器上并行。
其次为了保证高可用,每个分区都会有一定数量的副本(replica)。这样如果有部分服务器不可用,副本所在的服务器就会接替上来,保证应用的持续性。
但是,「为了保证较高的处理效率,消息的读写都是在固定的一个副本上完成。这个副本就是所谓的Leader」,而其他副本则是Follower。而Follower则会定期地到Leader上同步数据。
「Leader选举」
如果某个分区所在的服务器出了问题,不可用,Kafka会从该分区的其他的副本中选择一个作为新的Leader。之后所有的读写就会转移到这个新的Leader上。现在的问题是应当选择哪个作为新的Leader。显然,只有那些跟Leader保持同步的Follower才应该被选作新的Leader。
Kafka会在Zookeeper上针对每个Topic维护一个称为ISR(in-sync replica,已同步的副本)的集合,该集合中是一些分区的副本。只有当这些副本都跟Leader中的副本同步了之后,Kafka才会认为消息已提交,并反馈给消息的生产者。如果这个集合有增减,Kafka会更新Zookeeper上的记录。
如果某个分区的Leader不可用,Kafka就会从ISR集合中选择一个副本作为新的Leader。
显然通过ISR,Kafka需要的冗余度较低,可以容忍的失败数比较高。「假设某个topic有f+1个副本,Kafka可以容忍f个服务器不可用。」
「为什么不用少数服从多数的方法?」
少数服从多数是一种比较常见的一致性算法和Leader选举法。它的含义是只有超过半数的副本同步了,系统才会认为数据已同步;选择Leader时也是从超过半数的同步的副本中选择。这种算法需要较高的冗余度。譬如只允许一台机器失败,需要有三个副本;而如果只容忍两台机器失败,则需要五个副本。而kafka的ISR集合方法,分别只需要两个和三个副本。
「如果所有的ISR副本都失败了怎么办?」
此时有两种方法可选,一种是等待ISR集合中的副本复活,一种是选择任何一个立即可用的副本,而这个副本不一定是在ISR集合中。这两种方法各有利弊,实际生产中按需选择。如果要等待ISR副本复活,虽然可以保证一致性,但可能需要很长时间。而如果选择立即可用的副本,则很可能该副本并不一致。
— 【 THE END 】— 本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取! 3T技术资源大放送!包括但不限于:Java、C/C++,Linux,Python,大数据,人工智能等等。在公众号内回复「1024」,即可免费获取!!