这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
Kafka
使用场景
一般用于离线的消息处理。
- 用于传输日志信息
- 用于程序状态的采集
- 用户行为采集
如何使用kafka
- 创建集群
- 新增topic,设置好分片数
- 编写生产者逻辑,引入对应对应SDK,配置集群,topic等参数,初始化一个生产者,调用send方法
- 编写消费者逻辑,引入对应对应SDK,配置集群,topic等参数,初始化一个生产者,调用poll方法
基本概念
概念总览
Topic:Kakfa中的逻辑队列,可以理解成每一个不同的业务场景就是一个不同的topic,对于这个业务来说,所有的数据都存储在这个topic中Cluster:Kafka的物理集群,每个集群中可以新建多个不同的topicProducer:顾名思义,也就是消息的生产端,负责将业务消息发送到Topic当中Consumer:消息的消费端,负责消费已经发送到topic中的消息,不同消费者组消费topic是相互独立的Partition:通常topics会有多个分片,不同分片的消息是可以并发来处理的,这样提高单个Topic的吞吐
offset
Offset:对于每一个partition来说,每一条消息都有一个唯一的offset,消息在partition内的相对位置信息,可以理解为唯一ID,在partition内部严格递增。
Replica
leader:唯有leader副本可以提供对外写入,读取Replica:分片的副本,分布在不同的机器上,可用来容灾,Leader对外服务,Follower异步去拉取leader的数据进行一个同步,如果leader挂掉了,可以将Follower提升成leader再对外进行服务ISR:意思是同步中的副本,对于Follower来说,始终和leader是有一定差距的,但当这个差距比较小的时候,我们就可以将这个follower副本加入到ISR中,不在ISR中的副本是不允许提升成Leader的
数据复制
topic1:2分片,3副本
topic2:1分片,3副本
图里的broker代表每一个kafka的节点,所有broker节点最终组成一个集群,上图有4个broker机器节点,其中一个broker同时扮演controller角色,controller角色是集群的大脑,负责对副本和broker进行分配
架构
zookerper存储集群的元数据,比如副本的分配信息,controller节点计算好的方案都会放在那个地方
批量发送
kafka一般批量发送提高带宽,如果单个消息大小很大,并发也很大,kafka带宽可能不够用,kafka因此也提供了数据压缩功能。
数据压缩
数据存储
kafka存储的消息最终会写进磁盘
- 日志文件:存数据
- 偏移量索引文件:记录offset与文件真实位置的映射
- 时间戳索引文件:
命名用每个segment中第一条消息的offset作为他们的命名
顺序写
kafka通过顺序写的方式进行写入,提高了写入效率
移动磁头找到对应磁道,磁盘转动,找到对应扇区,最后写入。寻道成本比较高,因此顺序写可以减少寻道所带来的时间成本。
broker如何找到消息
Consumer通过发送FetchRequest请求消息数据,Broker会将指定Offset处的消息,按照时间窗口和消息大小窗口发送给Consumer,寻找数据这个细节是如何做到的呢?
broker偏移量索引文件
二分法找到小于目标offset的最大文件
kafka使用的是稀疏索引,也就是说不是所有数据都有索引,而是隔几个数据就有一个索引
时间戳索引
时间戳索引相当于2级索引
时间戳->offset->找到offset对应的position
数据拷贝
传统的数据拷贝
零拷贝
consumer从boker读取数据:通过sendfile的方式将磁盘读到os内核缓冲区后,直接转到socket buffer进行网络发送,而无需将传输到应用空间再额外进行数据拷贝
Producer生产的数据持久化到broker:采用mmap文件映射,实现顺序的快速写入
使用 mmap 系统调用可以将用户空间的虚拟内存地址与文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样。
读写文件都需要经过
页缓存,所以mmap映射的正是文件的页缓存,而非磁盘中的文件本身。
- 当向文件中写入数据时,如果要写入的数据所在的页缓存已经存在,那么直接把新数据写入到页缓存即可。否则,内核首先会申请一个空闲的内存页(页缓存),然后从文件中读取数据到页缓存,并且把新数据写入到页缓存中。对于被修改的页缓存,内核会定时把这些页缓存刷新到文件中。
consumer消费
对于一个consumer group来说,多个分片可以并发消费,对于每个分区该由那个consumer进行消费,有自动与手动两种方式
low-level
手动分配也就是Kafka中片说的Low Level消费方式进行消费,
- 好处就是启动比较快,因为对于每一个Consumer来说启动的时候就已经知道了自己应该去消费那个分区。
图中展示的Consumer Group1来说,
- Consumer1去消费Partition 1,2,3
- Consumer.2,去消费Partition 4,5,6,
- Consumer3去消费Partition 7,8。
这样分配也有缺点
- 当某个consumer挂了,对应的分区就无人消费了。需要重新创建新的消费者,无法自动容灾
- 新加入consumer无法自动进行分区的重新分配,需要重新进行停止与启动消费者
High-level
所以Kafka也提供了自动分配的方式,这里也叫做High Level的消费方式,简单的来说,就是在我们的Broker集群中,对于不同的Consumer Group来讲,都会选取一台Broker当做Coordinator。
Coordinator的作用:就是帮助Consumer Group进行分片的分配,也叫做分片的rebalance。
使用这种方式,如果Consumer Group中有consumer发生宕机,或者有新的Consumer加入,整个partition和Consumer都会重新进行分配来达到一个稳定的消费状态
Rebalance
- consumer刚进入会随机找broker发送一个请求来获取协调者是那个broker
- 之后consumer发送请求告诉协调者它想加入group当中。
- 协调者收到请求后会发送请求会选取一个consumer让其作为leader进行计算分区如何分配
- 之后consumer会发送请求告诉协调者它的分配方案
- 之后协调者会发送请求告诉所有消费者对应的消费方案
这期间consumer会定时发送心跳,告诉协调者自己还存活,如果那个挂了协调者就会重新开始进行rebalance流程
小结
kafka提高吞吐量和稳定性的措施
- producer:批量发送,数据压缩
- broker:顺序写,消息索引,零拷贝
- consumer:rebalance
kafka缺点
- kafka当运维出现节点变动时,数据的同步时间成本高
-
负载均衡问题:所有数据都写入对应分区的leader上,当第一个分片的数据量明显大于其他分片数据量时,当机器io达到瓶颈,就需要把第一个broker上的分区3迁移到负载小的broker上,这又会有io复制,io复制又会引起io升高,所以需要权衡io来设计复杂的复杂均衡策略
- 总结
- 运维成本高
- 对于负载不均衡的场景,解决方案复杂
- 没有自己的缓存,完全依赖Page Cache
- Controller和Coordinator和Broker在同一进程中,大量lO会造成其性能下降