本人小白一个,不能保证博客中内容都准确,如果博客中有错误的地方,望各位多多指教,请指正。欢迎找我一起讨论
kafka知识总结
一、kafa:高性能跨语言的分布式 发布/订阅的消息系统。
生产者发送的消息,同一个group中的多个消费者只能有一个消费者接收消息
多个group,每个group接收到的消息是一样的
高吞吐、消息持久化(存入磁盘)、完全分布式(producer、broker、consumer水平扩展(集群))
二、producer:消息生产者/发布者 push
为了有效利用 broker 集群的性能,提高消息的吞吐量,producer 可以通过随机或者hash等方式,将消息平均发送到多个 partition 上,以实现负载均衡。
生产者producer发送数据的方式:发送并忘记(fire-and-forget)、同步、异步
1、发送并忘记(fire-and-forget):把消息发送给服务器,并不关心它是否正常到达,大多数情况下,消息会正常到达,因为kafka是高可用的,而且生产者会自动 尝试重发,使用这种方式有时候会丢失一些信息
2、同步发送:使用send()方法发送,它会返回一个Future对象,调用get()方法进行等待
低延迟、低吞吐、无数据丢失
默认等待10s,10sbroker还没给出ack应答,就认为发送失败,重试。
默认重试3次,3次broker都没给出ack应答,就报错。
3、异步发送:调用send()方法,并指定一个回调函数,服务器在返回响应时调用函数。
高延迟、高吞吐、可能丢失数据(ack没收到,但是buffer缓存满了,如果此时设置了在这种情况下直接情况buffer数据)
有一个后台线程去buffer缓存中拿消息发个broker(批量发送,有延迟)
分区存储的原则:消息到底到哪个partition中去?
1.如果生产者指定放到broker的哪个partition中了,那就会按指定的分区存储
2.如果生产者没有指明partition但存储的内容含有key,则把key的hash值与partition数进行取模,然后放到对应分区中
3.如果既没有指明partition也没有存储key,则第一次存储会随机生成一个整数,让这个整数和partition数取模,然后放到对应的分区中。第二次存储时再第一次随机生成的随机数上自增再取模执行同样的操作。
4.实现partitioner接口,自定义消息去哪个partition。
为什么producer push消息到broker中需要进行高可用处理?
为了保证producer发送的数据,能够可靠的进入到指定的broker分区中,broker的每一个分区收到producer发送的数据后都需要向producer发送ack确认收到标志,如果producer收到了ack,才会进行下一轮的数据发送,否则需要重新发送数据。
kafka提供了三种可靠性级别(ack应答机制),根据对可靠性和延时的要求进行权衡
acks为0:producer不需要等待broker的ack应答,延迟最低,broker故障可能对丢失数据
acks为1:producer等待leader的ack应答,此时如果follower还同步成功之前,leader宕机,可能会丢失数据
acks为-1 / all(默认):producer等待leader和follower全部ack应答,但是如果follower同步完成之后,在broker发送ack之前,leader发生故障,那么会出现数据的重复,但不会造成数据丢失
三、broker:接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存(消息持久化)
四、consumer:消息消费者/订阅者 poll
push有实时性,为什么不用push,而是poll呢?
1、设计简单,broker不需要知道有多少consumer
2、当数据量过大时,如果使用push,broker push大量的数据过来,consumer消费不了,可能直接就把consumer干没了,数据也可能丢失
所以使用poll,可以根据consumer的消费能力去broker拿数据
五、topic:消息分门别类,每一类的消息称之为一个topic
同一个topic的消息可分布在一个或多个broker上
topic中的数据分割为一个或多个partition。每个topic至少有一个partition
producer发布消息时,必须指定该消息属于哪个topic
consumer订阅消息时,必须指定订阅哪个topic的消息
六、partition分区
topic可以被分为若干个分区(partition),同一个topic中的分区可以不在一个机器上,有可能会部署在多个机器上,由此来实现 kafka 的伸缩性。
topic中的数据分割为一个或多个partition。每个topic至少有一个partition。
partition中的数据是有序的,partition之间的数据是没有顺序的。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1
对于每一个partition而言,有一个leader副本和多个follower,可以将leader当作中心节点,可以认为每一个partition就是一个小的集群。不管是push还是poll,都是找的leader,follower知识进行数据复制,保证在leader挂掉时,follower能顶上。
七、replica副本
replica数要不能大于broker的数量,不然会报错。
所有partition的所有replica默认情况下会均匀分布在所有broker上。如果一个partition的两个replica都在同一个broker上,一旦这个broker挂了,两个replica全部不可用,那就达不到数据的可用性、达不到让数据不丢失的目的。
Kafka通过多副本复制技术,实现kafka集群的高可用和稳定性。每个partition都会有多个数据副本,每个副本分别存在于不同的broker。所有的数据副本中,有一个数据副本为Leader,其他的数据副本为follower。不论是producer端还是consumer端发往partition的请求,皆通过leader数据副本所在的broker进行处理。当broker发生故障时,对于leader数据副本在该broker的所有partition将会变得暂时不可用。
八、offset偏移量:每一个分区都有一个offset,一个不断递增的整数值。offset是message的唯一标识。
我的理解就是说,对于一个topic下的一个partition来说,这个partition就相当于数组,offset就相当于数组的index,message就相当于存入数组的value。如果消费者发生崩溃或有新的消费者加入群组,就会触发再均衡,完成再均衡之后,每个消费者可能分配到新的分区,消费者需要读取每个分区最后一次提交的偏移量,然后从偏移量指定的地方继续处理。
如果提交偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理。
如果提交的偏移量大于客户端的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。
offset的存储位置?
0.8.2.3之前,offset存在zookeeper中
0.8.2.3之后的版本就存在Kafka 一个名为 __consumer_offsets 的Topic中
0.10.1后就可以放在自定义的地方(比如:数据库、redis、文件中)
为什么不将offset存在zookeeper中了呢?
频繁更新offset导致zookeeper负载变大
offset commit
1、自动commit:enable.auto.commit设置为true。
每隔5秒消费者会自动把从poll()方法接收的最大偏移量提交上去。提交时间间隔有auto.commit.interval.ms控制,默认值是5秒。
注意:某个消费者poll消息后,应用正在处理消息,在3秒后Kafka进行了重平衡,那么由于没有更新位移导致重平衡后这部分消息重复消费
2、手动commit----同步commit:enable.auto.commit设置为false
使用commitSync()提交偏移量,commitSync()将会提交poll返回的最新的偏移量,所以在处理完所有记录后要确保调用了commitSync()方法。否则还是会有消息丢失的风险。只要没有发生不可恢复的错误,commitSync()方法会一直尝试直至提交成功,如果提交失败也可以记录到错误日志里。
3、手动commit----异步commit:enable.auto.commit设置为false
手动提交有一个缺点,那就是当发起提交调用时应用会阻塞。当然我们可以减少手动提交的频率,但这个会增加消息重复的概率(和自动提交一样)。另外一个解决办法是,使用异步+回调的commit方式。
4、手动commit----同步+异步组合commit
异步提交也有个缺点,那就是如果服务器返回提交失败,异步提交不会进行重试。相比较起来,同步提交会进行重试直到成功或者最后抛出异常给应用。异步提交没有实现重试是因为,如果同时存在多个异步提交,进行重试可能会导致位移覆盖。
举个例子,假如我们发起了一个异步提交commitA,此时的提交位移为2000,随后又发起了一个异步提交commitB且位移为3000;commitA提交失败但 commitB提交成功,此时commitA进行重试并成功的话,会将实际上将已经提交的位移从3000回滚到2000,导致消息重复消费。
九、Rebalance重平衡
消费者组内组成员个数发生变化(某个消费者实例挂掉后或者新增了消费者)或者Topic 的分区数发生变化,其他消费者实例自动重新分配订阅主题分区的过程。 Rebalance 是 Kafka 消费者端实现高可用的重要手段。 Rebalance 发生时, 所有的 Consumer Group 都停止工作, 直到 Rebalance 完成。
如果消费者数量或者分区数量产生了变化,打破了现有的平衡(动态平衡),那该怎么办呢?
①静态设置,直接指定consumer消费哪个分区 ----- - 不符合高可用
② 使用coordinator协调器:具体执行rebalance和group管理。
对于每个consumer group子集,都会在服务端对应一个Coordinator进行管理, Coordinator会在zookeeper上添加watcher,当消费者加入或者退出consumer group时,会修改zookeeper上保存的数据,从而触发GroupCoordinator开始Rebalance操作
十、consumer group
在 Kafka 中, 一个 Topic 是可以被一个消费组消费, 一个Topic 分发给 Consumer Group 中的 Consumer 进行消费, 保证同一条 Message 不会被通过一个consumer group下的不同的 Consumer 消费
注意: 当Consumer Group的 Consumer 数量大于 Partition 的数量时, 超过 Partition 的数量将会拿不到消息
同一个 Consumer Group 里面的 Consumer 是如何知道该消费哪些分区里面的数据呢?
1、Range策略 --- 默认
Range策略是对每个主题而言的,首先对同一个主题里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。
比如:
实例一:现在有1个topic、10个partition、3个consumer
①partition 排序 ---------> p1 p2 p3 ......... p10
②consumer排序 ----------> c1 c2 c3
③ partition总个数 / 消费者总个数 = 10 / 3 =3 ------ 每个consumer最少有的partition个数
④ partition总个数 % 消费者总个数 = 10 % 3 =1 ------- 平均分配后剩余的partition 个数 (此处的个数 按consumer顺序分一个,直到分完)
⑤按顺序给每个consumer 分配对应的partition
结果如下:
c1 ----> p1 p2 p3 p4 3+1个partition
c2 ----> p5 p6 p7 3+0 个partition
c3 ----> p8 p9 p10 3+0 个partition
实例二:现在有2个topic、每个topic都有 10个partition, 3个consumer同时消费这2个topic
结果如下:
topic 1 的partition t1p1 t1p2 t1p3 ........ t1p10
topic 2 的partition t2p1 t2p2 t2p3 ........ t2p10
consumer c1 c2 c3
c1 ----> topic1的 t1p1 t1p2 t1p3 t1p4 3+1个partition 以及 topic2的 t2p1 t2p2 t2p3 t2p4 3+1个partition
c2 ----> topic1的 t1p5 t1p6 t1p7 3+0 个partition 以及 topic2的 t2p5 t2p6 t2p7 3+0 个partition
c3 ----> topic1 的 t1p8 t1p9 t1p10 3+0 个partition 以及 topic2 的 t2p8 t2p9 t2p10 3+0 个partition
=====》》》 综上所述,如果 partition的数量不是consumer数量的倍数,那么排序在前面的consumer的消费压力会随着topic的个数增大而增大
2、RoundRobin策略
把所有partition和所有consumer线程都列出来,然后按照hashcode进行排序。最后通过轮询算法分配partition给消费线程。如果所有consumer实例的订阅是相同的,那么partition会均匀分布。
比如:
实例一:现在有1个topic、10个partition、3个consumer
①以partition的名字按hashCode排序: p1 p2 p3 ......... p10
②consumer排序:c1 c2 c3
③依次轮询
结果:
c1 ------>p1 p4 p7 p10
c2 ------>p2 p5 p8
c3 ------>p3 p6 p9
========>>>>>只要不发生动态平衡,每个consumer就固定消费对应序号的partition
3、stricky策略 0.11.x版本出来的(粘滞策略)
它的设计主要实现了两个目的(如果两个目的发生了冲突,优先实现第一个目的):
1、分区的分配要尽可能的均匀
2、分区的分配尽可能的与上次分配保持相同
比如:
实例一:4个topic、每个topic两个partition (共8个partition) , 3个consumer 同时消费这四个topic
topic:t1 t2 t3 t4
partition:
topic t1 的两个partition :t1p1 t1p2
topic t2 的两个partition :t2p1 t2p2
topic t3 的两个partition :t3p1 t3p2
topic t4 的两个partition :t4p1 t4p2
consumer:c1 c2 c3
结果:
c1 ------>t1p1 t2p2 t4p1
c2 ------> t1p2 t3p1 t4p2
c3 ------>t2p1 t3p2
========>>>>>上面看着有些像轮询,他俩的区别就在于Rebalance时两者的思想不同,stricky它是一种粘滞策略,所以它会满足分区的分配尽可能和上次 分配保持相同(也就是说如果发生rebalance,原先consumer已经匹配了的partition尽量不改变)。比如在某一时刻C2挂了,所以要重新分区根据stricky策略分配的结果应该是:
c1 ------>t1p1 t2p2 t4p1 t3p1
c3 ------>t2p1 t3p2 t1p2 t4p2
========>>>>>综上所述,c2挂了,c1,c3之前消费的是哪些partition继续消费这些partition,然后在这基础上进行partition分配均匀,将挂了的c2消费的partition轮询均匀分配,尽量让剩下的consumer消费的partition的数量平均,这种策略的好处是使得分区发生变化时,由于分区的“粘性,减少了不必要的分区移动
========>>>>>为什么要这么处理呢?
这是因为发生分区重分配后,对于同一个分区而言有可能之前的消费者和新指派的消费者不是同一个,对于之前消费者进行到一半的处理还要在新指派的消费者中再次处理一遍,这时就会浪费系统资源。而使用Sticky策略就可以让分配策略具备一定的“粘性”,尽可能地让前后两次分配相同,进而可以 减少系统资源的损耗以及其它异常情况的发生。
十一、kafka中的选举:控制器类选举、partition的leader选举、consumer选举
控制器类选举:
kafka集群中多个broker中选举一个broker作为控制器(kafka controller),它负责管理整个集群中所有分区和副本的状态等工作。比如当某个分区的leade副本出现故障时,由控制器负责为该分区选举新的leader副本。再比如当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有broker更新其元数据信息。
Kafka Controller的选举是依赖Zookeeper来实现的,在Kafka集群中哪个broker能够成功创建 /controller 这个临时节点他就可以成为Kafka Controller。其他的broker叫Kafka Broker follower。
这个Controller会监听其他的Kafka Broker的所有信息,例如:一旦有一个broker宕机了,这个kafka broker controller会读取该宕机broker上所有的partition在zookeeper上的状态,并选取ISR列表中的一个replica作为partition leader
partition的leader选举:
基本思路是按照AR集合(所有的副本集合)中副本的顺序查找第一个存活的副本,并且这个副本在ISR集合中。
分区进行重分配(reassign)的时候也需要执行leader的选举动作。这个思路比较简单:从重分配的AR列表中找到第一个存活的副本,且这个副本在目前的ISR列表中。
若 ISR中此时为空,则需要判断unclean.leader.election.enable 属性;如果unclean.leader.election.enable参数的值为false,那么就意味着非ISR中的副本不能够参与选举。此时无法进行新的选举,此时整个分区处于不可用状态,等待isr中的副本“复活”,第一个“复活”的副本就是leader。如果unclean.leader.election.enable参数的值为 true(unclean.leader.election.enable参数默认为true),那么可以从非ISR集合中选举follower副本成为新的leader。在 Kafka 中,选举这种副本的过程称为 Unclean 领导者选举。
consumer选举:
如果消费组内还没有leader,那么第一个加入消费组的消费者即为消费组的leader。如果某一时刻leader消费者由于某些原因退出了消费组,那么会重新选举一个新的leader。
在GroupCoordinator中消费者的信息是以HashMap的形式存储的,其中key为消费者的member_id,而value是消费者相关的元数据信息。leaderId表示leader消费者的member_id,它的取值为HashMap中的第一个键值对的key
十二、ISR队列
leader会维护一个与其基本保持同步的副本列表,也就是ISR(in-sync replica)
如果follower比leader落后太多,或者超过一定时间未发起数据复制请求,则leader会将follower从ISR中移除 ,存入 OSR(Outof-Sync Replicas)列表,新加入的 follower 也会先存放在 OSR 中。AR=ISR+OSR。倘若该副本后面慢慢地追上了 Leader 的进度,那么它是能够重新被加回 ISR 的。
当ISR所有的replica都向leader发送ack时,leader就commit
为什么follower会比leader落后太多?
因为可以批量发送数据,所以存在落后太多的情况。比如这时候leader发现自己有1000条数据,flower只有500条数据,落后了500条数据,就把它从ISR中移除出去。Kafka 判断 Follower 是否与 Leader 同步的标准就是 Broker 端参数 replica.lag.time.max.ms 参数值。这个参数的含义是 Follower 副本能够落后 Leader 副本的最长时间间隔,当前默认值是 10 秒。这就是说,只要一个 Follower 副本落后 Leader 副本的时间不连续超过 10 秒,那么 Kafka 就认为该 Follower 副本与 Leader 是同步的,即使此时 Follower 副本中保存的消息明显少于 Leader 副本中的消息。
ISR队列 中怎么处理replica恢复?
比如 : 此时ISR中有ISR三个replica A(leader) B(follower) C(follower)
此时 A 复制了消息 m1 m2 m3 三条消息到自己的本地日志文件 B复制成功了 m1 m2 两条消息 到自己的本地日志文件 C 复制成功了 m1 一条消息到自己的本地日志文件 --------- 此时 A (leader) 只会commit m1一条消息
此时 A挂了,B选为了leader , 此时 B复制成功了 m1 m2 两条消息到自己的本地日志文件 C 复制成功了 m1 m2 两条消息到自己的本地日志文件
------- 此时 B (leader) 只会 commit m2一条消息 ,不会commit m3 (因为B根本就没写入过m3,此时m3的数据丢失了)
此时 A还没恢复,BC继续复制消息commit消息 m4 m5到自己的本地日志文件 ,就是不会复制commit 消息 m3 (因为m3在A上,但A挂了)
此时 A 恢复了,此时B是leader ,A会丢弃之前没有commit的消息,此时A只有m1,然后重新从 B(leader)中拉取其它的消息m2 m4 m5
replica全部挂了怎么办?
通过设置 unclean.leader.election.enable 的值来控制 ,默认为true ,也就是第二种方法
第一种方法:等待ISR中任意一个replica恢复,并选它为leader
等待时间长、降低可用性、保证数据不会丢失
如果ISR中的所有replica都无法恢复,则该partition将永不可以
第二种方法:选择第一个恢复过来的replica,不管它是否在ISR中 (默认方法)
并不一定包含所有被之前leader commit的消息,因此可能造成数据丢失
可用性较高
十三、LEO
指的是每个副本最大的 offset
十四、HW
指的是消费者能见到的最大的offset,也就是说消费者最多能消费到HW,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同步数据。注意: 这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
十五、 low lever consumer (simpleConsumer)
可以控制我们需要将消息消费多少次,同一个partition可以通过控制offset乱序消费,可以精确控制消费哪个partition。
需要自己去判断leader是谁,自己去处理leader的变化,自己去处理多个consumer之间的协作。
可以每次指定希望消费哪个topic的哪个partition,从哪个offset开始之后的多少字节(大小size)的message。
由于它并不知道message的大小,直接给定一个size大小,如果message没这么大怎么办?
比如,指定size为100,就最大只能拿100,如果message大于100,就拿不到message ,所以不好控制取一条消息。
十六、high lever consumer
只要指定了topic,一旦消费,就必须需要消费topic中所有的数据(partition),同一个partition顺序消费
十七、收集到的一些关于kafka的问题
1、单节点kafka的吞吐量为什么也比其他消息队列的大
零拷贝 、顺写日志、预读、后写、分段日志、批处理、压缩
2、kafka的offset放在哪里?
zookeeper 0.8.2.3之前
topic(__consumer_offset) 0.8.2.3之后
自定义(mysql、redis、本地文件) 好像是0.10.1之后
3、如何保证数据不会出现丢失或者重复消费的情况?
同步发送数据
ack = -1
自己维护offset避免重复消费(低级API)
4、kafka元数据存在哪里?
zookeeper (/controller 、/cluster 、 /consumer 、 /broker)
5、 如何保证不同的订阅源收到相同的一份内容
HW 、LEO
6、 kafka的消费速度怎么提高
增加分区和消费者
增加拉去数据的大小
增加批处理的大小
7、ISR中什么情况下brokerId会消失
副本down掉 、 网络阻塞 、 落后
8、kafka消费的用的高举API和低级API
kafkaConsumer.poll
SimpleConsumer.fetch.send
9、怎么手动维护offset
关闭自动commit ,配置手动commit