关于kafka消息队列的一些常见知识点总结:
Apache Kafka是由Apache开发的一种发布订阅消息系统,它是一个分布式的、分区的和重复的日志服务。kafka是由scala语言编写。
topic: kafka已topic为单位归类消息,每个topic可以有多个partition分区,每个分区可以有0到N个消息副本。消息由leader决定往哪个分区写入,每一个分区中消息内容也是不同的,主题多分区设计可以增加消息的并发处理能力。而多副本一方面可以提高消息的读取能力,另一方面也提高了容灾能力。
producer: 消息的生产者
consumer:消息的消费者
broker:一个单独的kafka server就是一个broker ,负责消息的读取和存储,一个broker可以管理多个partition。
kafka的消息都是以(key,value)形式存在,生产者决定数据写到那个partition,每个消息的大小不超过1M。producer直接将消息发送到broker的leader,对于消息的ack机制,主要有三种:不等待broker的回应,等待leader的回应以及等待leader和所有follower的回应。每个消息在被添加到分区时,都会被分配一个offset,它是此消息在分区中的唯一编号,kafka通过offset保证消息在分区内的顺序,offset顺序不跨分区,即kafka只保证在同一个分区内的消息是有序的,在不同的分区中,消息可能是无序的。
kafka消息以固定格式存储在磁盘上:消息由一个固定长度的头部和可变长度的字节数组组成。头部包含了一个版本号和CRC32校验码。
消费者消费消息的时候,会记录消费的物理偏移量位置(offset),下一次消费的时候,会接着上一次的位置继续消费。consumer采用pull模式拉取消息,好处是可以根据consumer的消费能力决定消息消费速度,但缺点是如果broker没有消息,会不断轮询,直到新的消息产生,所以kafka可以通过设置让consumer阻塞。kafka支持多个consumer组成一个consumer group,在一个group中,每个partition只会被分配到一个consumer,如果组中members太多,可能会有member空闲。
Kafka将一个topic的partition文件分拆成多个小文件,通过index信息可以快速定位到message的位置和确定response的大小,并且通过index可以将数据全部映射到memory,避免segment file的IO磁盘操作。
topic的多个partition以文件夹的形式存储到broker,每个分区序号从0递增,每个partition文件夹下有多个segment(xxx.index,xxx.log),segment文件的大小可以通过配置设置,默认为1G。大小大于1g时,会滚动一个新的segment并且以上一个segment最后一条消息的偏移量命名。
创建topic时,第一个分区的第一个副本,会从brokerlist中随机选取一个broker;其它分区的第一个副本,会在紧随第一个分区的broker后。剩余的副本则相对于第一个副本放置位置其实是由 nextReplicaShift 决定的,而这个数也是随机产生的。
分区文件夹的创建,会根据启动kafka时配置的log.dirs参数决定。如果log.dirs值配置了一个参数目录,则分配到broker上的partition也会在这个目录下创建。如果log.dirs配置了多个目录,Kafka 会在含有分区目录最少的文件夹中创建新的分区目录,分区目录名为 Topic名+分区ID。
为了避免磁盘移除,kafka会根据策略定期清除过期消息,策略包括:根据消息的保存时间个根据topic存储数据的大小。
日志压缩是:在很多场景下,消息的key与value对应关系是不断变化的,消费者只关心key对应的最新value,此时,可以开启kafka的日志压缩功能,kafka会在后台启动一个线程,定期将相同key的消息进行合并,只保留最新的value值。
ISR(In-Sync Replica),表示目前可用的并且消息与leader差不多的副本集合,是整个副本集合的一个子集。ISR的副本节点必须与zookeeper保持连接,并且副本的最后一条消息与leader副本消息的offset不能超过一个阈值。每个分区的leader副本都会维护一个ISR,写请求先交给leader处理,follower副本会从leader那里拉取写入的消息。这个过程可能会有一定的延迟,导致followers的副本消息落后于leader,如果超过阈值后,follower会从ISR中移除到OSR中。通常情况下,ISR一个该包含所有的副本。当ISR中所有Replica都向Leader发送ACK时,leader才commit。
如果leader挂掉了,会从replica中选取一个做为新的leader,并且从ISR中移除。如果所有的副本都宕机后,有两种策略:1、等待ISR中的任意一个replica恢复,并选举为leader;如果ISR中一个也不能恢复,该Partition永久不可用;2、选取第一个回复的replica作为leader,不论是不是在ISR中。
消息丢失从生产端和消费端分析。
生产端:1) 使用同步模式时,有3种状态保证消息被安全生产,在配置为1(只保证写入leader成功的话),如果刚好leader partition挂了,数据就会丢失;
2)使用异步模式的时候,当缓存区满了,如果配置为0(还没收到确认的情况下,缓冲池一满,就清空缓冲池里的消息),数据就会被立马丢弃掉;
消费端:1)consumer从集群中拉取到到消息后,消息还没有消费成功,自动提交了offset;
对于重复消费,可以对每一个消费的消息在外部加一个去重处理,或者幂等处理。
kafka副本以topic的分区为单位,在正常情况下,每个分区都有一个单独的leader,0个或多个follower。副本的总数包括leader。所有的读取和写入到该分区的leader,leader均匀分布在broker。follower的日志完全等同于leader的日志 — 相同的顺序相同的偏移量和消息。Followers作为普通的消费者从leader中消费消息并应用到自己的日志中,并允许follower从leader拉取批量日志应用到自己的日志,这样具有良好的性能。
对于kafka的节点活着有2个条件:一个节点必须能维持与zookeeper的会话;如果它是一个slave,它必须复制leader并且不能落后"太多"。如果一个follower死掉,卡住,或落后,leader将从同步副本列表中移除它。
当leader故障后,我们需要从follower中选出新的leader,但是follower自己可能落后或崩溃,所以我们必须选择一个最新的follower。日志复制算法必须提供保证,如果我们告诉客户端消息是已发送,leader故障了,我们选举的新的leader必须要有这条消息,这就产生一个权衡:如果leader等待更多的follwer声明已提交之前,应答消息的话,将会有更多有资格的leader。一种常见的方法,用多数投票决定leader选举。kafka不是这样做的,假如我们有2f+1副本,如果f+1副本在leader提交之前必须收到消息,并且如果我们选举新的leader,至少从f+1副本选出最完整日志的follwer,并且不大于f的失败,leader担保所有已提交的信息。这是因为任何f+1副本中,必须至少有一个副本,其中包含所有已提交的消息。该副本的日志是最完整的,因此选定为新的leader。这种投票表决的方式有一个非常好的特性:仅依赖速度最快的服务器,也就是说,如果复制因子为三个,由最快的一个来确定。
kafka采用了一种稍微不同的方法选择quorum,而不是多数投票,kafka动态维护一组同步leader数据的副本(ISR),只有这个组的成员才有资格当选leader,kafka副本写入不被认为是已提交,直到所有的同步副本已经接收才认为。这组ISR保存在zookeeper,正因为如此,在ISR中的任何副本都有资格当选leader,这是kafka的使用模型,有多个分区和确保leader平衡是很重要的一个重要因素。有了这个模型,ISR和f+1副本,kafka的主题可以容忍f失败而不会丢失已提交的消息。
请注意,kafka对数据丢失的保障是基于至少有一个副本在保持同步。如果分区的所有复制节点都死了,这保证就不再成立。
。如果我们等待ISR中的副本,那么只要副本不可用,我们将保持不可用,如果这些副本摧毁或数据已经丢失,那么就是永久的不可用。另一方面,如果non-in-sync(非同步)的副本,我们让它成为leader,让它的日志成为`源`,即使它不能保证承诺的消息不丢失。在我们当前的版本中我们选择第2种方式,支持选择在ISR中所有副本死了时候可选择不能保证一致的副本。可以通过配置unclean.leader.election.enable禁用此行为,以支持停机优先于不一致。
参考链接:https://www.orchome.com/22