kafka学习笔记
写在前面
我会反复维护这篇笔记,时不时记录新的认知以及理解。这是第二次update这篇文章了
笔记内容
-
kafka是一个集 消息系统,流处理系统的流式数据处理系统
-
流处理系统会解决类似TCP的:“消息顺序”“错误重发”“滑动窗口”“通讯状态”等问题。
-
kafka消息会集中后垃圾回收。
-
kafka的游标是让消费者维护,目的是为了提高吞吐量。消费者可访问历史消息。
-
生产的消息要经过足够的同步后才可以被消费者读取,即移动watermark指针,而且读取方式是主动pull。
-
生产者把消息发给某一个主题后,该消息被消费者组中的一个consumer读取。这个时候生产者和消费组的关系就是队列模型。如果每个消费组只有一个成员,且消息发给多个消费组,那么这就形成了订阅推送模式。
-
每个分区只能被一个消费者访问,分区是最小的kafka粒度,每个分区都有副本在其他broker服务器用来备灾 ,可用理解为分区就是消费者的一个逻辑概念,一个消费者可用拥有多个分区,但一个分区只能有一个消费者。
-
分区副本只是一个灾备概念,为了实现高可用,所以分区的数量要足够多,为了实现强一致性,那么意味着足够多的分区同步好了才能用。确保任何时候都有灾备机可以用,代价就是同步数据的时候不对外访问。(有点极端)
-
一个分区有多个消息,消息是按照分区为单位分配给消费组的,一个分区只能给一个消费组,因此,不存在竞争消息的情况出现,即,不存在两个消费者竞争一个分区的同一消息的情况。 这种无锁编程就是kafka快的核心原因。代价就是有些消费者容易解饿,而有些消费会被撑死。
-
研究producer, consumer,broker,topic, consumer group, 分区的概念之间的关系时最好在一台机器上讨论。研究副本的时候才会考虑集群的情况。
-
如果主题的消息没有指定key,则会被轮训分发给某个分区。如果有指定key则会被分发给特定分区。
-
消息流转过程:生产者-> 主题 -> 消费组consumer group -> 分区 -> 消费者consumer。生产者消息会丢给一个主题,主题会分发给多个consumer group,消费组consumer group 只会根据key传给一个分区,分区只能被一个消费者消费。
-
批量和压缩传输是kafka网络io的核心优化手段。批发发送是牺牲延迟来提高吞吐量的手段,批量发送可用指定发送等待时间和发送buffer大小(批处理的惯用伎俩)。
-
kafka采用了直接内存技术数据从磁盘到IO过程不需要经过用户空间,实现零拷贝。
-
生产者在发生消息前会请求任意broker获取元数据用来定义目标分区所在的broker,实现精准发送,但是需要解决元数据同步问题。这里体现了分区是逻辑概念,并不一定要和broker绑定,所以初始化之前可以灵活规划,一旦绑定了,后面需要重新规划,就只能以消费的形式读取转移到其他空闲机器上去了。
-
消息分发过程中,需要做负载的地方有两个:请求元数据规划负载均衡,消费组分发分区负载均衡。消费组分发分区的负载均衡是根据key来实现的,如果没有key,默认就是robin轮训了。(robin适合消费者消费能力一样的场景)
-
分区赋予了kafka在分区队列消费时无锁的特性,从而提高吞吐量。而拉取的方式使得kafka不需要维护复杂的滑动窗口机制,也是提高了吞吐量。
-
标准的做法,一个消费组的所有分区应该分散到各个broker中,每个分区的副本也应该做到散落在各个broker 服务器上,这样是为了负载均衡和分区备灾。
-
备份分区副本的同步方式跟消费者消费分区一样,只不过发送在broker,而且还会把消息同步到日志文件(也就是分区队列的文件),而普通消费者则是直接做业务处理。所以我们可以理解主分区除了被有且只有一个普通的消费者线程访问之外还会被0~n个副本备份消费者线程访问。(同步分区会加水位,消费队列会调游标,这是两种区别)
-
判断节点是否存活的依据是:节点是否与ZK保持会话(如是否有心跳),如果是ISR(in sync replicas)节点还要求的副本是否与主副本的差异是否太大。(一个是物理可用性,一个是逻辑可用性)。无效节点的分区备份会被主分区从副本索引集合中移除(即不参与备灾活动)。
-
尽管有主备分区的机制,但是一旦主备分区同时挂掉的小概率事件出现了,消息也就丢失了。
-
kafka集群的配置和元数据存放在zk中,包括broker服务器列表,topic信息,分区信息偏移量等。
-
kafka的producer直接与broker交互。
-
kafka的consumer直接与zk交互,消费者分组和偏移量也会放在zk。
-
kafka的producer通过只会面向主题和主分区进行消息投放,并且会进行负载。消费组的分区只是对主题分区的一份全量映射。比如1个主题有6个分区,如果创建3个consumer的消费组GA,那么每个consumer会获得2个分区,整个消费组有6个分区。如果继续在这个集群中创建一个6个consumer的消费组GB,那么每个consumer获得一个分区,且GB和GA一样有6个分区。这是因为消费组的元数据没有包括分区拷贝,只是简单的生产一份偏移量和消费组成员表。而真实的分区元数据是由topic来管理的。一个topic在创建的时候就需要制定分区的元数据,包括了主分区个数以及每个主分区的备份副本分区的个数。(为什么分区个数由topic来规划呢?因为topic比较清楚消息批次的最小粒度的下限有多小。如果消费组不满意这个粒度,可以减少组内consumer的人数,从而提高consumer人均消息批的粒度)
-
每个消费者可用从属多个消费组(前期不大建议这么做,容易给管理带来成本,其次就是万一是同一个主题的消费组就会出现重复消费了),这种设计并不会非法消息队列的原则,在消息队列,我们的消费者确实可以消费多个队列的消息,而且也允许读两个同一topic的队列(就是会重复消息而已)。
-
consumer从属的消费组可以都是同一个主题或者是不同主题的(consumer从属的消费组属于同一个主题就会重复消费,所以这种基本不可能存在啦)。逻辑上消费者和分区绑定(1对n关系),分区和消费组绑定(m对1关系, m>=n)。
-
消费组订阅的主题分区会平均的负载给所有的消费者。(看起来同一个消费组不允许有消费者占用太多分区,这就是传说中的balance原则)
-
消费者与分区的绑定关系是动态的,因为主题可能会调整分区的数量,消费组的成员也会动态的下线或者死机或者加入新成员。所以需要再平衡rebalance 。
-
由于分区可能会发生再平衡,所以分区偏移是属于Consumer Group级别的(group会记录各个分区的当前游标)。
-
一个分区只能分配给一个消费者,从线程模型来看,一个分区只能分配给一个消费者线程,然后消费者线程以pull的方式形成一个消息流。
-
ConsumerConfig实际上是一个ZK连接器配置(他配置了zk参数,ConsumerGroup参数),他表示一个消费者的元数据,他最终还可以通过ConsumerConfig.createMessageStream(topicThreadCount)来控制这个消费者拥有多少个分区。每个分区最终被pull程一个MessageStream.
-
从图可以看出,消费者mq的元对象不是线程对象。
-
消费者会定时的提交消费进度给消费组协调器或者偏移管理器。这个协调器可以是broker节点或者是zk,优先使用broker节点可以减少zk压力。因为再平衡算法由协调器进行,消费偏移量也是由协调器负责。broker充当协调节点是新版本的功能,对偏移量提交相当于是提交消息到主题一样。每个消费组的协调节点不一定相同,因为消费组之间从逻辑上是独立的。
-
高级API消费者只需要制定主题以及线程数就可以直接获取需要的消息流,不需要人工维护偏离量,也不需要和协调器交互。而低级API需要指定主题与分区,然后自己控制读取消息的偏移量的起点,读取后偏移量会被重新后移。
-
ISR和Replicas以及分区leader和kafka控制器(服务分区管理和broker管理)都是broker节点,而不是分区。这个很重要。
-
控制器的选举算是:看谁先在ZK集群创建zk node “/controller”,创建成功的就是控制器节点,其他失败的就,其他失败的就变为监听器。