Kafka是一种高吞吐量的分布式发布订阅消息系统(消息引擎系统),它可以处理消费者在网站中的所有动作流数据。
消息队列应用场景
缓存/削峰 :处理突然激增的大量数据,先放入消息队列,再按照速度去处理,
解耦 :在不同模块,不同服务间传递数据和消息
异步通信:发送消息让别的模块去异步处理一些功能
kafka高效读写原因
- kafka是分布式集群,采用分区方式,并行操作
- 读取数据采用稀疏索引,可以快速定位消费数据
- 顺序读写磁盘 (已追加的方式,写入 segment的末尾)
- 页缓存和零拷贝
Kafka结构
kafka 架构总体上分为四部分 : 生产者 、消费者、kafka集群、Zookeeper(3.x换为Kraft 模式)
kafka各部分
broker
Kafka 集群包含一个或多个服务器,服务器节点称为broker。
broker存储topic的数据。如果某topic有N个partition,集群有N个broker,那么每个broker存储该topic的一个partition。
Topic
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。
Partition
topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个segment文件存储。partition中的数据是有序的,不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序。
Leader
每个partition有多个副本,其中有且仅有一个作为Leader, leader负责处理读写操作
follower
follower只是负责副本数据的同步
Producer
生产者即数据的发布者,该角色将消息发布到Kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。
Consumer
消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。
Consumer Group
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。
生产者 producer
生产者消息发送流程
kafka生产者发送消息分为main线程、sender线程两部分
Main线程 —> 拦截器 —> 序列化器 —> 分区器 —>消息收集器 (RecordAccumulator,默认32M,可以理解为缓冲区)
Sender线程 拉取数据 —>已节点方式发送(ack机制,未收到ack最多缓存5个节点)—> kafka集群
ProducerBatch :每批次的数据大小 ,大小为 batch.size (16k),
Linger. Ms: 数据未到达batch.size 大小 会等待 linger. Ms 时间,再发送,默认:0ms,也就是不等待
到达 batch.size 或 Linger. Ms 后 ,会调用sender线程 向kafka集群 发送消息
异步发送消息
指的是mian线程 到 消息收集器的发送过程,不会等待消息是否发送到kafka集群
同步发送 :每次 消息收集器的消息都发送到kafka集群后,mian线程才会继续发送消息
生产者分区
paritioner(分区器)
分区的好处:
1.合理分配存储资源:每个partation 再一个broker上,可以吧数据存在多个broker上,实现负载均衡的效果
2.提高并行度:生产者可以以partation为单位发送,消费者可以以partation为单位消费
生产者分区分配策略:
1.给定了分区号,直接将数据发送到指定的分区里面去。
2.没有给定分区号,通过key的hashcode,和 partation分区数,进行取模。
3.既没有给定分区号,也没有给定key值,直接轮循进行分区。
Kafka采用Sticky Partition(黏性分区器),会随机选择一个分区,并尽可能一直使用该分区,待该batch已满或者已完成,Kafka再随机一个分区进行使用。
生产者如何提高吞吐量
1.修改linger. Ms 等待时间(默认是0) 修改为 :5-100ms()
修改batch.size,批次大小 (默认16k) 可修改为:32k
修改的太大也不好,时间太久,批次太大会有延迟
2.开启压缩数据, compress.type 有四个选项: none(不压缩),gzip,snappy,lz4
3.修改缓冲区大小 :RecordAccumulator(默认32m)改为:64m
Kafka应答机制 (可靠性的保证)
Kafka的ack机制,指的是producer的消息发送确认机制。平衡吞吐量和可靠性。
完全可靠性保证 : ack = -1 + 分区副本数量 大于2 + ISR队列中数量 大于2
参数 :request.required.acks (0, 1,-1)
0 :表示生产者将数据发送出去不等待任何返回,效率最高,可靠性最低
1:(默认级别):表示数据发送到Kafka 后,经过leader确认收到消息,才算发送成功,如果leader宕机了,就会丢失数据。
-1(all): 表示生产者需要等待ISR中的所有follower同步完数据后才算发送完成,这样数据不会丢失,因此可靠性最高,性能最低。
问题: -1 级别时 :某个follower故障了,消息怎么才能被 确认呢?
答:Leader 维护了ISR队列(和leader保持同步的follower队列),如果某个follower长时间未和leader通信,则踢出队列
replica.lag.time.max.mss(默认30秒)
数据重复:ack = -1 时 :leader 和follower 同步数据完成,未 发送 ack 时 ,leader 挂了, 会新选出leader ,则会发送两次数据
幂等性原理 (解决数据重复)
判断数据重复 : pid + partation + sepNumber(自增数字)
pid重启会会分配新的,幂等性原理只会解决 单分区内数据重复问题
数据乱序,数据有序
数据有序 :
开启幂等性 支持 单分区数据有序, max.in.flight.requests.per.connection=[1,5]. 保证最多缓存5个请求的数据,保证有序
Broker(节点)
broker存储的信息:
ids
是使用临时节点存储在线的是各个服务节点的信息,当下线后自动删除
seqid:
辅助生成的brokerId,当用户没有配置broker.id时,ZK会自动生成一个全局唯一的id。
kafka副本
leader和follower是针对分区而言的,而controller是针对broker的
副本基本信息
1)Kafka 副本作用:提高数据可靠性。
2)Kafka 默认副本 1个,生产环境一般配置为 2个,保证数据可 靠性;太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率。。
3) Kafka 中副本分为:Leader 和 Follower。leader负责数据读写,follower负责数据同步
AR : Kafka 分区中的所有副本统称为 AR (Assigned Repllicas)。
OSR :表示 Follower 与 Leader 副本同步时,延迟过多的副本。
ISR :表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader发送通信请求或同步数据,则该Follower 将被踢出 ISR。该时间國值由 replica.lag.time.mar. ms 默认 30s。Leader 发生故障之后,就会从ISR 中选举新的 Leader。
AR = ISR + OSR
Leader选举流程
1.broker 启动会在zk上注册,先注册在 Zk Controller 上面 的节点 先为 Controller ,Controller 会检测 broker节点的变化
2.如果leader发送故障,就会从ISR中选举出新的leader
3.选举规则 :首先ISR队列中保证存活,然后是AR队列中选靠前的节点,轮询
4.更新leader 和ISR队列
Unclean leader选举
kafka还提供了一个参数配置:unclean.leader.election.enable,默认是true,参数规定是否允许非ISR的副本成为leader,如果设置为true,当ISR队列是空,此时将选择那些不在ISR队列中的副本选择为新的leader,这写副本的消息可能远远落后于leader,所以可能会造成丢失数据的风险。生产环境中建议关闭这个参数
Leader Follower 故障处理
LEO(log end offset):每个副本的最后一个offset,LEO就是最新的offset+1
HW(high watermark):所有副本中最小的LEO (最高水位线)
消费者只能读到 HW 之前的数据 (所有副本都同步完成的数据才会对消费者可见)
Follower故障处理
1.故障的Follower被踢出 ISR队列 ,leader 和其他正常follower 正常接收数据
2.Follower 恢复后,会读取上次的 HW(故障之前),并将高于 这个HW 的数据接去掉 ,向leader发起同步
3.Follower的LEO 大于当前 partation 的 HW ,便可再次加入 ISR队列。(即数据同步追上leader)
Leader故障处理
1.leader故障,controler 从 ISR队列中选举出新Leader
2.其余follower 将 HW 之后的数据截取掉,并向新leader同步数据
分区副本分配
kafka集群中分区的副本分布是做到尽量的均匀的分配的到各个broker中,以此来保证每台机器的读写吞吐量是均匀
问题 :borker宕机,会导致leader 集中,负载变得不均衡
Leader Partition自动平衡
为了解决上述问题kafka出现了自动平衡的机制。kafka提供了下面几个参数进行控制
auto.leader.rebalance.enable:自动leader parition平衡,默认是true
leader.imbalance.per.broker.percentage:每个broker允许的不平衡的leader的比率,默认是10%,如果超过这个值,控制器将会触发leader的平衡
leader.imbalance.check.interval.seconds:检查leader负载是否平衡的时间间隔,默认是300秒
增加副本因子
生产环境中由于某个主题的重要等级需要提升,考虑增加副本。
1.创建副本Json文件:add-relication-factor.json (版本号,topic,partation等信息)
2.执行副本存储计划
Kafka 文件存储机制
tpoic 是逻辑上的概念,partation是物理上的概念,每个partation对应一个log文件。
该log文件中存储的就是Producer生产的数据。Producer生产的数据会被不断追加到该log文件末端。
为防止log文件过大导致数据定位效率低下,kafka采用了分片和索引机制,将每个partition分为多个**segment(**每个1G大小)
每个segment包括.index文件、.log文件和.timeindex等文件
这些文件位于文件夹下,该文件命名规则为:topic名称+分区号
稀疏索引 .index
当log文件写入4k(这里可以通过log.index.interval.bytes设置)数据,就会写入一条索引信息到index文件中,这样的index索引文件就是一个稀疏索引,它并不会每条日志都建立索引信息。
log日志文件 :是顺序写入,大体上由message+实际offset+position组成,而、
索引文件 :数据结构则是由相对offset(4byte)+position(4byte)组成。
kafka查询一条offset对应实际消息时,可以通过index进行二分查找,通过offset + position 找到消息在 log文件中的位置
时间索引文件:.timeindex
它的作用是可以查询某一个时间段内的消息,它的数据结构是:时间戳(8byte)+ 相对offset(4byte)
查找:先要通过时间范围找到对应的offset,然后再去找对应的index文件找到position信息,最后在log文件中找数据
Kafka文件清除策略
log.cleanup.policy参数标识文件清除策略
delete(删除,按照一定的保留策略直接删除不符合条件的日志分段LogSegment
compact(压缩,日志压缩就是根据key来保留最后一条消息)
Delete 删除
kafka中默认的日志保存时间为7天,可以通过调整如下参数修改保存时间。
log.retention.hours:最低优先级小时,默认7天
log.retention.minutes:分钟
log.retention.ms:最高优先级毫秒
log.retention.check.interval.ms:负责设置检查周期,默认5分钟
file.delete.delay.ms:延迟执行删除时间
log.retention.bytes:当设置为-1时表示运行保留日志最大值(相当于关闭);当设置为1G时,表示日志文件最大值
日志保留策略 :
基于时间策略:
日志删除任务会周期检查当前日志文件中是否有保留时间超过设定的阈值来寻找可删除的日志段文件集合
基于日志大小策略:
日志删除任务会周期性检查当前日志大小是否超过设定的阈值(log.retention.bytes,默认是-1(不限制)表示无穷大),来寻找可删除的日志段文件集合。
基于日志起始偏移量:
该策略判断依据是日志段的下一个日志段的起始偏移量 baseOffset是否小于等于 logStartOffset,如果是,则可以删除此日志分段。
compact 压缩
日志压缩对于有相同key的不同value值,只保留最后一个版本。
如果应用只关心 key对应的最新 value值,则可以开启 Kafka相应的日志清理功能,Kafka会定期将相同 key的消息进行合并,只保留最新的 value值。
页缓存 + 零拷贝
页缓存
pagecache:将数据缓存在内存中
当一个进程要去读取磁盘上的文件内容时,操作系统会先查看要读取的数据页是否缓冲在PageCache 中,如果存在则直接返回要读取的数据,这就减少了对于磁盘 I/O的 操作;但是如果没有查到,操作系统会向磁盘发起读取请求并将读取的数据页存入 PageCache 中,之后再将数据返回给进程,就和使用redis缓存是一个道理
零拷贝
Kafka集群 直接将数据通过内核态 发送至网卡,再发送给消费者,不用拷贝进用户态
(因为kafka不关注数据内容,只是做消息的传递,所以不需要拷贝到用户态 )
Kafka消费者
kafka采用 pull 模式 拉取消息
消费者需要订阅主题(Topic),多个消费者(Consumer)组成一个消费者组(Consumer Group)
一个消费者 可以消费多个分区数据(partation)
消费者组消费流程
1 消费者建立一个 ConsumerNetworkClint 消费者网络客户端
2.从kafka中抓取数据 大于1字节,小于 50M,或者不超过500ms,满足一个就可以抓取
3.将数据放入 conpletedFetches 队列中
4.消费者一次拉取500条数据(max.poll.records)
5.对数据反序列化,然后经过拦截器,最后处理数据
消费者组(Consumer Group)
1.消费者组由多个消费者组成,通过Groupid 来分组,组内消费者id相同(每个消费者都有组id,默认会有)
2.消费者组内每个消费者都负责消费不同的分区,一个分区只能被组内的一个消费者消费
3.不同的消费者组互不影响,消费者组是逻辑上的概念,对外可以看做是一个消费者
4.消费者组内 消费者数量 > 对应的topic 的partation数量,则会有消费者闲置
消费者组初始化
Coordinater(协调器):辅助实现消费者组的初始化和分区的分配
节点选择:gouupid 的 hashCode % 50 (_consumer_offset 分区数量 默认值 50)
1.每个消费者都发送一个 joinGroup 请求
2.消费者组 选出一个leader
3.leader定制消费方案 并且发送给 Coordinater 协调器
4.Coordinater协调器把方案下发给各个 消费者
再平衡(消费者组变动重新分配消息)
1消费者组中的消费者 会和 Coordinater 协调器 保持 心跳包 3秒一次 ,超过45秒未通信,则会移除该消费者,并 触发再平衡
2 消费者长时间未处理消息(5分钟 max_poll_inerval.ms)也会触发再平衡
再平衡策略:range 、 roundRobin 、Sticky 、 CooperActiveSticky
默认采用:range + CooperActiveSticky
Range :对分区和消费者进行编号 ,partation 分区数 除以 消费者数 ,则为每个消费者得到分配的数量,余数 分给编号小的 消费者
缺点 :针对一个tpoic进行分区,容易产生数据倾斜
roundRobin :针对集群中所有Topic来分区,把所有的partation和消费者 列出来,按照hashCode 排序,轮询分配
sticky:粘性分区:尽量按照上一次分区的结果 ,进行重新分配 特点:均匀 且 随机
消费者offset
offset在哪:消费者将offset 维护在 Kafka 内置 topic中 :Topic _consumer_offsets 中 默认不对外展示 exclude.internal.topic = flase
(0.9版本之前,存在ZK中,消耗网络IO性能不好)
存储形式: K V 形式存储 key :groupid + topic + 分区号; val :offset值
每隔一段时间 :kafka会压缩 offset数据 只保留最新数据
offset 自动提交
enable .auto.commit :默认是 true 开启
auto.commit.interval.ms 自动提交间隔,默认 5秒
offset 手动提交
Kafka提供:手动提交offset的APi接口
手动提交 offset 分为 同步提交和异步提交
消费者还可以按照offset位置进行消费,按照时间进来消费,按照分区进行消费
kafka -Kraft 模式(3.x版本)
kafka 干掉 了 zookeeper ,使用 Kraft 模式 替代了 ZK,使用 3台 controller 点 来管理 kafka集群
优点:
不依赖外部框架,而是独立运行,不受到Zk读写能力限制
Controller 不再动态选举,而是通过配置文件规定
Kafka问题(重复数据,重复消费,消息丢失)
重复消费
1 Consumer消费过程中,进程挂掉/异常退出,offset未提交 读到之前的offset
位移(Offset)的提交有两种方式,自动提交和手动提交。
解决 :精确一次性消费
将 消费数据和提交offset 绑定做原子操作 可利用redis 活mysql的事务
将唯一标识存入Redis,要操作数据的时候先判断缓存有没有这个唯一标识。
将版本号(offset)存入到数据里面,然后再要操作数据的时候用这个版本号做乐观锁,当版本号大于原先的才能操作。
2 消费者消费时间过长
Kafka消费端的参数max.poll.interval.ms定义了两次poll的最大间隔,它的默认值是 5 分钟, Consumer 如果在 5 分钟之内无法消费完 poll方法返回的消息,那么Consumer 会主动发起“离开组”的请求。
解决 : 根据情况调整 max.poll.interval.ms 值
提高单条消息的处理速度:异步的、多线程处理等
消息丢失
1 生产者端丢失
Replica ACK :
0 :表示生产者将数据发送出去不等待任何返回,效率最高,可靠性最低
1:(默认级别):表示数据发送到Kafka 后,经过leader确认收到消息,才算发送成功,如果leader宕机了,就会丢失数据。
-1(all): 表示生产者需要等待ISR中的所有follower同步完数据后才算发送完成,这样数据不会丢失,因此可靠性最高,性能最低。
解决方案:
1 通过设置RequiredAcks模式来解决,选用WaitForAll(对应值为-1)可以保证数据推送成功,不过会影响延时。
2 引入重试机制,设置重试次数和重试间隔。
3 当然,最后就是使用Kafka的多副本机制保证Kafka集群本身的可靠性,确保当Leader挂掉之后能进行Follower选举晋升为新的Leader。
2 消费者丢失
消费端的消息丢失主要是因为在消费过程中出现了异常,但是对应消息的 Offset 已经提交,那么消费异常的消息将会丢失。
解决 :手动提交offset