努力学习都是为了自己的名字有一个好的归宿。加油!
“士由百折不挠之真心,方有万变无穷之妙用”
为本次kafka专栏做一个强化记忆专栏,都是自己看完以后理解,用尽量最少的语言对问题进行解答,第一次发表文章,难免有考虑不周的地方,还请见谅。
kafka的基本架构
基础概念扫盲
消息生产者,是消息的产生源头,负责生成消息并发送给Kafka
消息消费者,是消息的使用方,负责消费Kafka服务器上的消息
主题,由用户自定义,并配置在Kafka服务器,用于建立生产者和消费者之间的订阅关系,生产者将消息发送到指定的Topic,然后消费者再从该Topic下去取消息。
消息分区,一个Topic下面会有多个Partition,每个Partition都是一个有序队列,Partition中的每条消息都会被分配一个有序的id
这个其实就是Kafka服务器了,无论是单台Kafka还是集群,被统一叫做Broker,有的资料上把它翻译为代理或经纪人。
消费者分组,将同一类的消费者归类到一个组里。在Kafka中,多个消费者共同消费一个Topic下的消息,每个消费者消费其中的部分消息,这些消费者就组成了一个分组,拥有同一个组名。
对于每个消费者分组,Kafka都会为其分配一个全局唯一的Group ID,分组内的所有消费者会共享该ID, Kafka还会为每个消费者分配一个consumer ID,通常采用hostname:uuid的形式。
在kafka的设计中规定,对于topic的每个分区,最多只能被一个消费者进行消费,也就是消费者与分区的关系是一对多的关系。消费者与分区的关系也被存储在ZooKeeper中节点为{}该节点的内容就是消费者的Consumer ID。
若是下游数据处理不及时,则提高每批次拉取的数量。
原因:由于生产者是以push的方式将消息写入topic某个分区leader,写入之后Follower主动去拉取,需要设置参数保证消息不丢失
Asks这个参数是生产者客户端的重要配置,发送消息的时候就可设置这个参数。该参数有三个值可配置:0、1、All
第一种是设为0,意思是生产者把消息发送出去之后,之后这消息是死是活咱就不管了,有那么点发后即忘的意思,自然消息就有可能丢失。
第二种是设为1,意思是生产者把消息发送出去之后,这消息只要顺利传达给了Leader,其他Follower有没有同步就无所谓了。存在一种情况,Leader刚收到了消息,Follower还没来得及同步Broker就宕机了,但生产者已经认为消息发送成功了,那么此时消息就丢失了。
第三种是设为All(或者-1),意思是生产者把消息发送出去之后,不仅Leader要接收到,ISR列表中的Follower也要同步到,生产者才会任务消息发送成功。
我们进一步思考,设置为all就不会丢失了吗?答案是否!当ISR中只有Leader时,Asks=All
相当于Asks=1,这种情况下节点宕机,没有剩余节点顶替,就完蛋了。所以要想保证数据不丢失就必须在保证Acks=All时并且ISR列表中至少还有两个副本。
消费者消费的进度通过offset保存在Kafka集群的__consumer_offsets这个topic
消费者端配置参数:enable.auto.commit=false,禁止自动提交offset,避免多线程消费时出现消息丢失。
总而言之,kafka写入的数据不丢失要必须保证至少一个Follower在ISR里面,跟得上Leader数据的同步,每一条数据写入成功必须要有两个副本,万一宕机,还可以切换
一般在项目中都是flume和kafka联用,他们各自的特性可以进行互补,提高项目的运行效率和稳定性,以下几点是对此问题的一些粗浅理解:
从数据角度类型:.生产环境中都是读取日志进行分析,而这往往是多数据源的,如果kafka构建多个生产者方式向主题发送数据,无疑是很麻烦的
从拦截数据角度:.flume可以使用拦截器实时处理数据,而kafka必须依靠外部流处理系统。
从高可用角度:在flume中没有副本概念的,如果flume代理的一个节点崩溃了,恢复数据还要等到磁盘完全恢复,如果你需要一个高可靠行的管道,那么使用Kafka是个更好的选择。
从数据缓存角度:.如果flume直接对接实时计算框架,当数据采集速度大于数据处理速度时,必然会发生数据积压和数据丢失,而kafka当做一个消息缓存队列,从广义上理解,把他当做一个数据库,可以存放一段时间的数据
kafka的其他特性:
削峰
系统可以按照数据库能处理的并发量,从消息队列中慢慢拉取,这个短暂的高峰期积压是可以的。
高并发:支持数千个客户端同时读写
容错性:在kafka的一个topic中有多个分区,每个分区在若干服务中都有副本,这些持有副本的服务可以共同处理数据和请求,同时可以设置数量,保证kafka的一个容错性。
先不说他的数据到底存放在哪里,先说他为什么快?
你可能会认为:在磁盘上读写数据是会降低性能的,因为-!-寻址-!-会比较消耗时间,事实上,磁盘的读写速度关键在于你如何使用他
顺序写入
Kafka在设计时采用了文件追加的方式写入消息,即只能在日志文件的尾部追加新消息,并且不允许修改已写入的消息,这种方式属于典型的顺序写盘的操作,因此就算Kafka使用磁盘作为存储介质,它所能承载的吞吐量也很大。(磁盘顺序写比内存随机写还要快)
页缓存
虽然Kafka 会持久化所有数据到磁盘,但是本质上写入操作都是把他写到系统的页缓存,然后由操作系统来决定什么时候写入到磁盘,因为它是由内存分配的,不用直接和底层文件打交道
零拷贝
直接从磁盘文件复制到网卡设备,减少其中繁琐的多次复制和上下文切换
只是将包含图片数据的位置和长度信息的特殊描述符(Descriptor)加载到SocketBuffer中。 明显少了两次复制到socket套接字中的次数,并且减少了上下文切换的次数(具体描述自己CSDN),就不在此多余赘述,理解就好。
1)生产者定期向主题发送消息。
2)Kafka broker将所有消息存储在该特定主题配置的分区中。它确保消息在分区之间平等共享。如果生产者发送两个消息,并且有两个分区,则Kafka将在第一个分区中存储一个消息,在第二个分区中存储第二个消息。
3)消费者订阅一个特定的主题。
4)一旦消费者订阅了一个主题,Kafka将向消费者提供该主题的当前偏移量,并将偏移量保存在ZooKeeper中。
5)消费者将定期请求Kafka新消息。
6)一旦Kafka收到来自生产者的消息,它会将这些消息转发给消费者。
7)消费者将收到消息并处理它。
8)一旦消息被处理,消费者将向Kafka broker发送确认。
9)一旦Kafka收到确认,它会将偏移量更改为新值,并在ZooKeeper中进行更新。由于ZooKeeper中保留了偏移量,因此即使在服务器出现故障时,消费者也可以正确读取下一条消息
按照Kafka默认的消费逻辑设定,一个分区只能被同一个消费组(ConsumerGroup)内的一个消费者消费。假设目前某消费组内只有一个消费者C0,订阅了一个topic,这个topic包含7个分区,也就是说这个消费者C0订阅了7个分区,参考下图(1)
此时消费组内又加入了一个新的消费者C1,按照既定的逻辑需要将原来消费者C0的部分分区分配给消费者C1消费,情形上图(2),消费者C0和C1各自负责消费所分配到的分区,相互之间并无实质性的干扰。
接着消费组内又加入了一个新的消费者C2,如此消费者C0、C1和C2按照上图(3)中的方式各自负责消费所分配到的分区。
如果消费者过多,出现了消费者的数量大于分区的数量的情况,就会有消费者分配不到任何分区。参考下图,一共有8个消费者,7个分区,那么最后的消费者C7由于分配不到任何分区进而就无法消费任何消息。如下图:
该配置首先会对同一个topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。然后用分区总数初一消费者线程总数来决定每个消费者线程分到几个分区。如果除不尽,那么前几个消费者线程将会多消费一个分区。
C1-将消费0,1,2,3分区
C2-将消费4,5,6分区
C3-将分区7,8,9分区
Range 范围分区的弊端:
如上,只是针对 1 个 topic 而言,C1-0消费者多消费1个分区影响不是很大。如果有 N 多个 topic,那么针对每个 topic,消费者 C1-0 都将多消费 1 个分区,topic越多,C1-0 消费的分区会比其他消费者明显多消费 N 个分区。这就是 Range 范围分区的一个很明显的弊端了
RoundRobin 轮询分区
RoundRobin 轮询分区策略,是把所有的 partition 和所有的 consumer 都列出来,然后按照 hascode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。
轮询分区分为如下两种情况:①同一消费组内所有消费者订阅的消息都是相同的 ②同一消费者组内的消费者锁定月的消息不相同
①如果同一消费组内,所有的消费者订阅的消息都是相同的,那么 RoundRobin 策略的分区分配会是均匀的。
例如:同一消费者组中,有 3 个消费者C0、C1和C2,都订阅了 2 个主题 t0 和 t1,并且每个主题都有 3 个分区(p0、p1、p2),那么所订阅的所以分区可以标识为t0p0、t0p1、t0p2、t1p0、t1p1、t1p2。最终分区分配结果如下:
消费者C0 消费 t0p0 、t1p0 分区
消费者C1 消费 t0p1 、t1p1 分区
消费者C2 消费 t0p2 、t1p2 分区
②如果同一消费者组内,所订阅的消息是不相同的,那么在执行分区分配的时候,就不是完全的轮询分配,有可能会导致分区分配的不均匀。如果某个消费者没有订阅消费组内的某个 topic,那么在分配分区的时候,此消费者将不会分配到这个 topic 的任何分区。
例如:同一消费者组中,有3个消费者C0、C1和C2,他们共订阅了 3 个主题:t0、t1 和 t2,这 3 个主题分别有 1、2、3 个分区(即:t0有1个分区(p0),t1有2个分区(p0、p1),t2有3个分区(p0、p1、p2)),即整个消费者所订阅的所有分区可以标识为 t0p0、t1p0、t1p1、t2p0、t2p1、t2p2。具体而言,消费者C0订阅的是主题t0,消费者C1订阅的是主题t0和t1,消费者C2订阅的是主题t0、t1和t2,最终分区分配结果如下:
消费者C0 消费 t0p0
消费者C1 消费 t1p0 分区
消费者C2
消费 t1p1、t2p0、t2p1、t2p2 分区
RoundRobin轮询分区的弊端:
从如上实例,可以看到RoundRobin策略也并不是十分完美,这样分配其实并不是最优解,因为完全可以将分区 t1p1 分配给消费者 C1。
所以,如果想要使用RoundRobin 轮询分区策略,必须满足如下两个条件:
①每个消费者订阅的主题,必须是相同的
②每个主题的消费者实例都是相同的。(即:上面的第一种情况,才优先使用 RoundRobin 轮询分区策略)
什么时候触发分区分配策略?
当出现以下几种情况时,Kafka 会进行一次分区分配操作,即 Kafka 消费者端的 Rebalance 操作
① 同一个 consumer 消费者组 group.id 中,新增了消费者进来,会执行 Rebalance 操作
② 消费者离开当期所属的 consumer group组。比如 主动停机 或者 宕机
③ 分区数量发生变化时(即 topic 的分区数量发生变化时)
④ 消费者主动取消订阅
Kafka 消费端的 Rebalance 机制,规定了一个 Consumer group 下的所有 consumer 如何达成一致来分配订阅 topic 的每一个分区。而具体如何执行分区策略,就是上面提到的 Range 范围分区 和 RoundRobin 轮询分区 两种内置的分区策略。
Kafka 对于分区分配策略这块,也提供了可插拔式的实现方式,除了上面两种分区分配策略外,我们也可以创建满足自己使用的分区分配策略,即:自定义分区策略