Apache Kafka是由Apache开发的一种发布订阅消息系统,它是一个分布式的、分区的和可复制的提交日志服务。
目录
- 特点
- 使用场景
- 架构
- 消息分发策略
- 消费者和分区数量的建议
- 分区分配策略
- RangeAssignor(范围分区)
- RoundRobinAssignor(轮询分区)
- StrickyAssignor (粘滞策略)
- Coordinator
- 如何选取
- Rebalance执行过程
- Join(客户端完成)
- 分区策略选取
- Syn
- 总结
- Offset的存储以及计算
- 消费者提交的位移量
- 如何维护
- Zookeeper的作用
- 面试问题
特点
- 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition,
consumer group 对partition进行consume操作。 - 可扩展性:kafka集群支持热扩展
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
- 高并发:支持数千个客户端同时读写
使用场景
- 日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、HBase、Solr等。
- 消息系统:解耦和生产者和消费者、缓存消息等。
- 用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
- 运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
- 流式处理:比如spark streaming和 Flink
架构
- Producer :消息生产者,就是向 kafka broker 发消息的客户端。
- Consumer :消息消费者,向 kafka broker 取消息的客户端。
- Topic :可以理解为一个队列,一个 Topic 又分为一个或多个分区
- Consumer Group:这是 kafka 用来实现一个 topic 消息的广播(发给所有的 consumer)和单播(发给任意一个 consumer)的手段。一个 topic 可以有多个 Consumer Group。
- Broker :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
- Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker上,每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的id(offset)。将消息发给 consumer,kafka 只保证按一个 partition 中的消息的顺序,不保证一个 topic 的整体(多个 partition 间)的顺序。并且一个Partition只能被一个消费者消费
- Offset:kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。例如你想找位于 2049 的位置,只要找到 2048.kafka 的文件即可。当然 the first offset 就是 00000000000.kafka。
消息分发策略
默认情况下,kafka采用的是hash取模的分区算法。如果Key为null,则会随机分配一个分区。这个随机是在这个参数”metadata.max.age.ms”的时间范围内随机选择一个。对于这个时间段内,如果key为 null,则只会发送到唯一的分区。这个值默认情况下是10分钟更新一次。
消费者和分区数量的建议
- 如果consumer比partition多,是浪费,因为kafka的设计是在一个partition上是不允许并发的, 所以consumer数不要大于partition数
- 如果consumer比partition少,一个consumer会对应于多个partitions,这里主要合理分配 consumer数和partition数,否则会导致partition里面的数据被取的不均匀。最好partiton数目是 consumer数目的整数倍,所以partition数目很重要,比如取24,就很容易设定consumer数目
- 如果consumer从多个partition读到数据,不保证数据间的顺序性,kafka只保证在一个partition 上数据是有序的,但多个partition,根据你读的顺序会有不同
- 增减consumer,broker,partition会导致rebalance,所以rebalance后consumer对应的 partition会发生变化
分区分配策略
RangeAssignor(范围分区)
Range策略是对每个主题而言的,首先对同一个主题里面的分区按照序号进行排序,并对消费者按照字 母顺序进行排序。
- 假设n = 分区数/消费者数量
- m= 分区数%消费者数量
- 那么前m个消费者每个分配n+l个分区,后面的(消费者数量-m)个消费者每个分配n个分区
RoundRobinAssignor(轮询分区)
轮询分区策略是把所有partition和所有consumer线程都列出来,然后按照hashcode进行排序。最后通 过轮询算法分配partition给消费线程。如果所有consumer实例的订阅是相同的,那么partition会均匀分布。
使用轮询分区策略必须满足两个条件
- 每个主题的消费者实例具有相同数量的流
- 每个消费者订阅的主题必须是相同的
StrickyAssignor (粘滞策略)
- 分区的分配尽可能的均匀
- Reblance的时候分区的分配尽可能和上次分配保持相同
假设消费组有3个消费者:C0,C1,C2,它们分别订阅了4个Topic(t0,t1,t2,t3),并且每个主题有两个分区(p0,p1),也就是说,整个消费组订阅了8个分区:tOpO 、 tOpl 、 tlpO 、 tlpl 、 t2p0 、 t2pl、t3p0 、 t3pl
- 那么最终的分配场景结果为
- CO: tOpO、tlpl 、 t3p0
- Cl: tOpl、t2p0 、 t3pl
- C2: tlpO、t2pl
这种分配方式有点类似于轮询策略,但实际上并不是,因为假设这个时候,C1这个消费者挂了,就势必会造成重新分区(reblance)
- 如果是轮询,那么结果应该是
- CO: tOpO、tlpO、t2p0、t3p0
- C2: tOpl、tlpl、t2pl、t3pl
- strickyAssignor它是一种粘滞策略,所以它会满足
分区的分配尽可能和上次分配保持相同
,所以分配结果应该是
- 消费者CO: tOpO、tlpl 、 t3p0、t2p0
- 消费者C2: tlpO、t2pl、tOpl、t3pl
也就是说,C0和C2保留了上一次是的分配结果,并且把原来C1的分区分配给了C0和C2,这种策略的好处是使得分区发生变化时,由于分区的“粘性,减少了不必要的分区移动
Coordinator
Coordinator是执行对于consumer group的管理
如何选取
消费者向kafka集群中的任意一个broker发送一个 GroupCoordinatorRequest请求,服务端会返回一个负载最小的broker节点的id,并将该broker设置 为coordinator
Rebalance执行过程
在rebalance之前,需要保证coordinator是已经确定好了的,整个rebalance的过程分为两个步骤, Join和Sync
Join(客户端完成)
表示加入到consumer group中,在这一步中,所有的成员都会向coordinator发送joinGroup的请求。一旦所有成员都发送了joinGroup请求,那么coordinator会选择一个consumer担任leader角色,并把组成员信息和订阅信息发送消费者
选举算法比较简单,如果消费组内没有leader,那么第一个加入消费组的消费者就是消费者 leader,如果这个时候leader消费者退出了消费组,那么重新选举一个leader,这个选举很随意,类似于随机算法
- protocol_metadata: 序列化后的消费者的订阅信息
- leader_id:消费组中的消费者,coordinator会选择一个作为leader,对应的就是member_id
- member_metadata:对应消费者的订阅信息
- members:consumer group中全部的消费者的订阅信息
- generation_id: 年代信息,类似于zookeeper的epoch,对于每一轮 rebalance,generation_id都会递增。主要用来保护consumer group。隔离无效的offset提交。也就 是上一轮的consumer成员无法提交offset到新的consumer group中。
分区策略选取
每个消费者都可以设置自己的分区分配策略,对于消费组而言,会从各个消费者上报过来的分区分配策略中选举一个彼此都赞同的策略来实现整体的分区分配
- 在joingroup阶段,每个consumer都会把自己支持的分区分配策略发送到coordinator
- Coordinator收集到所有消费者的分配策略,组成一个候选集
- 每个消费者需要从候选集里找出一个自己支持的策略,并且为这个策略投票
- 最终计算候选集中各个策略的选票数,票数最多的就是当前消费组的分配策略
Syn
主要逻辑是向Coordinator发送 SyncGroupRequest请求,并且处理SyncGroupResponse响应
每个消费者都会向coordinator发送syncgroup请求,不过只有leader节点会发送分配方案,其他消费者发送的消息不重要。当leader把方案发给coordinator以后,coordinator会把结果设置到 SyncGroupResponse中,这样所有成员都知道自己应该消费哪个分区
总结
- 对于每个consumer group子集,都会在服务端对应一个Coordinator进行管理, Coordinator会在zookeeper上添加watcher,当消费者加入或者退出consumer group时,会修改zookeeper上保存的数据,从而触发GroupCoordinator开始Rebalance操作
- 当消费者准备加入某个Consumer group或者GroupCoordinator发生故障转移时,消费者并不知道GroupCoordinator的在网络中的位置,这个时候就需要确定GroupCoordinator,消费者会向集群中的 任意一个Broker节点发送ConsumerMetadataRequest请求,收到请求的broker会返回一个response 作为响应,其中包含管理当前ConsumerGroup的GroupCoordinator
- 消费者会根据broker的返回信息,连接到groupCoordinator,并且发送HeartbeatRequest,发送心跳的目的是GroupCoordinator这个消费者是正常在线的。当消费者在指定时间内没有发送心跳请求,则GroupCoordinator会触发Rebalance操作
- 如果Coordinator返回的心跳包数据包含异常,说明Coordinator因为前面说的几种情况导致了Rebalance操作,那这个时候,consumer会发起join group请求
- 新加入到consumer group的consumer确定好了Coordinator以后
- 消费者会向Coordinator发起join group请求, Coordinator会收集全部消费者信息之后,来确认可用的消费者,并从中选取一个消费者成为group_leader
- Coordinator把相应的信息(分区分 配策略、leader_id、…)封装成response返回给所有消费者,但是只有group leader会收到当前 consumer group中的所有消费者信息。
- 当消费者确定自己是group leader以后,会根据消费者的信息以及选定分区分配策略进行分区分配
- 接着进入Synchronizing Group State阶段,每个消费者会发送SyncGroupRequest请求到 Coordinator,但是只有Group Leader的请求会存在分区分配结果, Coordinator会 根据Group Leader的分区分配结果形成SyncGroupResponse返回给所有的Consumer。 consumer根据分配结果,执行相应的操作
Offset的存储以及计算
每个topic可以划分多个分区(每个Topic至少有一个分区),同一topic下的不同分区包含的消息是不同的。每个消息在被添加到分区时,都会被分配一个 offset(称之为偏移量),它是消息在此分区中的唯一编号,kafka通过offset保证消息在分区内的顺序,offset的顺序不跨分区,即kafka只保证在同一个分区内的消息是有序的;
消费者提交的位移量
但是对于消费者本身而言,offset是存储的下一次需要消费的位移量,也就是说,消费者需要提交保存的offset并不是当前消费的offset,而是offset+1
如果对上面那一段话理解有点困难,可以先看下面这个例子,
分区中的消息目前为:a-1、a、a+1、a+2,然后我们当前消费者到达的位置是a
那么,如果这个消费者下次需要消费的消息是(a+1),或者说这个时候发生了重分区,吧当前分区分发给其他消费者了,那么其他消费者需要消费的也是(a+1)这个消息,那么也就是需要我们的kafka返回的位移量是(a+1),而不是a,所以我们也可以就可以理解,为什么我们消费者提交的位移量是(offset+1,而不是offset了)
如何维护
在kafka中,提供了一个consumer_offsets_* 的一个topic,把offset信息写入到这个topic中。 也就是表示分区的offset是由消费者维护,而不是由服务端维护
__consumer_offsets 默认有50个分区,所以需要通过一定的计算,才能计算出某个group是存放在那个分区里的,计算公式如下:
- Math.abs(“groupid”.hashCode())%groupMetadataTopicPartitionCount ;
- 由于默认情况下 groupMetadataTopicPartitionCount有50个分区,计算得到的结果为:35
- 意味着当前的 consumer_group的位移信息保存在__consumer_offsets的第35个分区
Zookeeper的作用
zookeeper 是一个分布式的协调组件,早期版本的kafka用zk做meta信息存储,consumer的消费状态,group的管理以及 offset的值。考虑到zk本身的一些因素以及整个架构较大概率存在单点问题,新版本中逐渐弱化了zookeeper的作用。新的consumer使用了kafka内部的coordination协议,也减少了对zookeeper的依赖,但是broker依然依赖于ZK
面试问题
看了以上的讲解,下面的面试题应该已经没啥问题了,如果有问题,可以回过头再看看哦
- Kafka中是怎么体现消息顺序性的
- 消费组中的消费者个数和topic的分区怎么分配最好
- 消费者提交消费位移时提交的是当前消费到的最新消息的offset还是offset+1,以及为什么
- topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?(从数据层面考虑)
- Kafka有内部的topic吗?如果有是什么?有什么所用?
- kafka中的 zookeeper 起到什么作用,可以不用zookeeper么
- kafka的分区策略有那些
- 为什么使用kafka,kafka和其他MQ有什么区别
- offset的作用,以及存储
- broker、消费者组、消费者、分区有什么联系
- 重分区流程