八股文之-kafka

kafka
能屏蔽各种平台以及协议之间的特性,实现应用程序中间的协同
1.异步处理
2.流量削峰
3.弱一致性事务模型中,实现最大能力通知的方式实现最终一致性

异步发送:
并不是每次把消息直接发送到broker上,而是把消息放入一个队列,由后台线程不断从队列中取消息发送,成功会触发callback
积累一定量的消息批量发送:满足任一条件就会发送
batch.size :批量提交的字节大小默认16KB
linger.ms:两次发送间隔时间。

group
consumer group是可扩展的具有容错机制的消费者机制
同一个group中的多个消费者共享一个group ID,共同消费一个topic中的所有分区partition,每个分区只能被同一个group中的一个消费者消费

enable.auto.commit
消费者消费消息之后自动提交,提交之后的消息就不会被再次消费 可通过auto.commit.interval.ms控制自动提交的频率
也可以使用consumer.commitSync()手动提交

auto.offset.reset
对于新的group ID中的消费者 控制消费消息的偏移量
auto.offset.reset=latest 新的消费者会从其他消费者最后消费的offset处开始消费
auto.offset.reset=earlist 新的消费者会从该topic最早的消息开始消费
auto.offset.reset=none 抛异常

max.poll.records
限制每次poll返回的消息数量

topic
存储消息的逻辑概念,是一个消息的集合。物理上来说不同的topic消息是分开存储的
一个topic可以对应多个生产者 多个消费者

partition
每个topic可以对应多个partition,不同partition中的消息是不同的,
每条消息被添加到分区时会分配一个offset,通过这个保证消息在分区内的顺序,不跨分区,只保证一个分区内的消息是有序的


消息的分发策略
消息由key和value组成,producer根据key和partition的机制来判断这条消息发送到哪个partition,默认采用hash取模算法
可以自定义partitioner
然后在发送端指定这个partitioner


消息消费原理:
多个分区对应一个group中的多个consumer怎么分配
一个分区只能由一个group中的一个consumer消费,一个consumer可以消费多个分区

1.如果comsumer比partition多,会浪费consumer,因为在一个partition上不允许并发
2.如果consumer比partition少,一个consumer可以消费多个分区
3.consumer从多个分区读到的消息不保证顺序性
4.增减consumer broker partition会导致rebalance

rebalance:规定一个group下所有consumer如何分配订阅topic中的每个分区,执行分配策略

分区分配策略:
Range(默认)
RoundRobin(轮询)
StickyAssignor(粘性)
通过参数控制:partition.assignment.strategy

Range(默认):
对每个主题(topic),分区按序号排序,对消费者按字母顺序排序
n=分区数/消费者数
m=分区数%消费者数
前m个消费者消费n+1个分区,后面的消费者分配n个分区
弊端:2个主题,各由10个分区,3个消费者,会导致第一个消费者多消费2个分区

RoundRobin(轮询):
把所有分区和所有consumer列出来,按hashcode排序,最后用轮询算法分配,会均匀分布
使用这个策略的条件:
1.每个主题的消费者实例具有相同数量的流
2.每个消费者订阅的主题相同

StickyAssignor(粘性):
分区分配尽可能均匀
尽可能和上次相同
冲突时,尽量满足分配均匀
rebalance时减少了不必要的分区移动


谁执行rebalance和管理consumer的group?
kafka提供一个角色coordinator 
当consumer group的第一个consumer启动时就会和kafka server确定当前group的coordinator:
消费者向kafka中任一broker发送GroupCoordinatorRequest请求,服务端返回一个负载最小的brokerde id ,该broker就是coordinator
之后该group中所有consumer都会和该coordinator通信


JoinGroup的过程:
rebalance之前要确保coordinator已确定,分两步骤:join和sync
1.join
加入到group中,所有成员向coordinator发送joinGroup的请求,当所有成员都发送了joinGroup,coordinator会选择一个consumer担任leader,并把组成员信息和订阅信息发送给消费者
leader选举算法:如果没有 leader,第一个加入消费组的就是leader,如果这个leader退出消费组,随机选择一个leader

joinGroup阶段,每个consumer把自己支持的分区策略发送到coordinator
coordinator收到所有消费者的分配策略,组成一个候选集
每个消费者从候选集中找出自己支持的策略,并投票
最后计算票数,票数最多的就是当前消费组的分配策略


2.sync(Synchronizing group state)
分区分配完成之后每个消费者向group coordinator发送syncGroupRequest请求,只有leader的请求包含分区分配结果。并处理syncGroupResp
就是leader将消费者对应的分区分配方案同步给group中所有consumer


consumer group rebalance总结:
1.每个consumer group 都对应一个Groupcoordinator进行管理
Groupcoordinator在zookeeper上添加watch,当消费者加入或退出 触发rebalance
2.消费者加入consumer group或者groupcoordinator故障转移,消费者不知道哪个是groupcoordinator
就向集群中任一broker发送consumerMetadataRequest,收到请求的broker返回一个response,其中包含当前consumer group的coordinator
3.消费者连接到coordinator 发送heartBeatRequest告诉coordinator这个消费者正常在线,消费者在指定时间没有发送心跳,则rebalance


offset
每个消息被添加到分区时会被分配一个offset,
kafka有一个consumer_offsets_*的topic 把offset信息写入这个topic  默认有50个分区
某个group的分区信息 保存在groupid.hashCode()%分区数量(默认50),号分区里。


分区副本:
为了提高partition的可靠性提供了副本概念,实现冗余备份
分区有多个副本,其中有一个leader副本,所有读写请求都由leader处理,follower从leader同步消息日志
一般情况同一个分区的多个副本被分在集群的不同broker上

查看各分区的leader:get /brokers/topics/xxxTopic/partitions/1/state

副本的leader选举:
ISR副本:leader副本和所有同leader保持同步的follower副本。 
每个副本都包含:
LEO 日志末端位移(log end offset) 
HW 水位值

判断保持同步的标识:
1.副本节点与zk保持着连接
2.副本的最后一条消息的offset和leader的最后一条offset同步上的延迟时间 不超过阈值(repic.lag.time.max.ms)
如果follower在这个时间内一直没有追上leader,就会被剔除isr

follower把leader的LEO之前的日志都同步完,就认为追赶上了leader,更新lastCatchUpTime,kafka有副本过期检查定时任务
,定期检查当前时间和lastCatchUpTime差值 ,并与repic.lag.time.max.ms 比较,如果大于,就踢出ISR

应对所有replica不工作的情况:


ISR中至少有一个follower时 可以保证已经commit的数据不丢失
如果某个partition的所有replica都宕机,则无法保证数据不丢失

1.等待ISR中任一replica活过来,并作为leader
  满足一致性,不可用的时间会相对较长
  
2.选择第一个活过来的replica 不一定时ISR中的,作为leader
  满足可用性,不保证已经包含所有已commit的消息
  
  


副本数据同步原理:


producer发送消息到某partition时
先通过zk找到该partition的leader节点
leader先将消息写入本地log 每个follower从leader pull数据。数据顺序和leader一致
follower收到消息并写入log后 向leader发送ACK
一旦leader收到ISR中所有replica的ACK    ,就认为该消息已commit,leader会增加HW,向producer发送ACK    

随着follower不断同步leader的数据,follower的LEO    会逐渐追赶上leader,追赶上的标准是Follower的LEO是否大于等于leader的 HW,追赶上之后被踢出ISR的replica会重新加入到ISR

初始状态:
leader和follower的HW 和LEO都是0,leader副本保存remote LEO,表示所有follower LEO.
producer没发送消息,follower不断向leader发送fetch请求,没有数据会被寄存,超时(replica.fetch.wait.max.ms)后强制完成请求
如果在这个时间内有消息发送过来,则唤醒fetch请求

消息同步处理分两种情况
一.leader先处理完producer请求后,follower发送一个fetch请求过来
    leader收到producer的请求后:
    1.把消息追加到log文件,同时更新leader的LEO
    2.尝试更新leader的HW,由于这时follower没有发送fetch过来,leader的remote LEO是0,leader比较自己的LEO和remote LEO,发现最小值是0,与HW相同,不更新HW
    
    follower发起fetch
    leader处理逻辑:
    1.读取log 更新remote LEO=0(这个根据follower的fetch请求中带来的offset确定)
    2.尝试更新HW,自己的LEO和remote LEO,发现最小值是0,与HW相同,不更新HW
    3.把消息内容和当前分区的HW(0)发给follower
    
    follower处理response逻辑:
    1.将消息写入本地log 更新follower的LEO
    2.更新本地的HW,比较自己的LEO和follower返回的HW,取最小值,还是0
    
    follower再次fetch
    leader处理逻辑:
    1.读取log,更新remote LEO=1(follower本次fetch携带的offset=1)
    2.更新HW,这时自己的LEO和remote LEO 都是1,更新HW=1
    3.把数据和当前分区的HW返回follower 如果没有数据,返回为空
    
    follower处理response逻辑:
    1.如果有数据就写LOG,更新自己的LEO
    2.更新本地的HW,比较自己的LEO和follower返回的HW(1),取最小值 1

    
二.follower阻塞在leader指定时间内,leader收到producer发送消息的请求
    
    1.把消息追加到log文件,同时更新leader的LEO
    2.leader唤醒阻塞的fetch请求:
    3.尝试更新leader的HW
    
    因为HW是在下一轮fetch请求才更新,特定背景下会出现数据丢失
    

丢失数据的背景:
1.min.insync.replicas=1(ISR中最小副本数)
2.acks参数设为-1(表示需要所有副本确认)


此时消息被写入leader端,则任务已提交,因为延迟一轮的更新HW,使得leader发生变更时,
新的leader的HW可能是过期的(因为在做日志阶段会调整LEO为之前的HW),则成功提交的消息丢失了

acks参数:producer发送消息到broker的确认值
0:不需要确认,延迟最小,风险最大
1:只需要kafka集群中leader的确认
-1:需要ISR中所有replica确认,速度最慢,安全性最高,因为ISR可能缩小到至于1个replica 所以不能完全避免数据丢失


解决数据丢失:


引入leader epoch:实际是一对值(epoch,offset)
epoch表示leader版本号,leader发生变更就+1,offset表示当前leader写入的第一条offset
这个数据持久化存储在对应分区的磁盘上:/tmp/kafka-log/leader-epoch-checkpoint

leader 会有一个缓存 定期更新到checkpoint

leader写log时,尝试更新缓存,如果leader首次写入消息,在缓存中增加一个条目,否则不更新,每次副本重新成为leader会查询这个缓存,获取对应leader版本的offset

follower宕机恢复后
1.leader没有挂
  follower不会截断自己的日志,而是发一个offsetForLeaderEpochRequest到leader, leader返回当前的LEO
  如果follower和leader的epoch相同,leader的LEO肯定大于等于follower的LEO,不会截断
  如果follower和leader的epoch不同,leader查找follower传来的epoch+1去本地存储中找到startoffset返回,就是新leader的LEO,也不会截断数据
  
2.leader发生重新选举
  原本的follower变为leader,epoch+1,原follower中的LEO得到保留
  


Leader副本选举:
kafka controller会监听zk的/brokers/ids 发现broker挂了。
1.优先从ISR选出第一个作为leader
2.如果ISR为空 检查需要unclean.leader.election.enable
为true:表示允许使用非ISR的副本做leader,ISR中只包含该leader,并将leader和isr信息写入zk路劲上。意味着数据可能丢失,
为false:抛出NoReplicaOnlineException


消息的存储:


消息存放在{topic_name}_{partition_name} 
一个topic的多个partition存储在 /tmp/kafka-log/topic_partition

kafka通过分段,将Log 分为多个LogSegment,一个LogSegment对应一个日志文件(.log)和索引文件(.index)
log.segment.bytes=10370 分段大小 默认1G
以LogSegment为单位 把partition消息细分,命名规则:
partition全局的第一个segment从0开始,后续每个segment文件名为上一个segment中最后一条消息的offset递增
加入上一个segment最后一条offset为5376
下一个segment名为:0000...5376.log和0000...5376.index

每个日志文件对应2个索引
.index,.timeIndex

index中存储索引以及物理偏移量(索引指log中第几条消息,偏移量position指bytebuffer指针位置)
log存储消息内容

根据offset如何在partition中查找消息?
1.二分法找到offset所在的.index文件
2.根据offset找到索引文件中符合范围的索引(稀疏索引提高查找效率)
3.得到position后去log文件中从该position开始找 offset对应的消息

例如:找offset= 2250的消息,
先找到所在index:0000000000.index
找到[2240,11899]这条索引,去log中从118999的偏移位置开始一条条查找,直到offset = 2250.


日志清除:
log.retention.bytes
log.retention.hours 默认7天,两个参数,满足一个就执行删除


日志压缩:
定期将相同key的消息合并,保留最新的消息


Kafka如何保证高性能:


1.采用顺序写
2.零拷贝

 


 将页缓存数据直接传输在socket
 通过FileChannel.transferTo() API 调用Linux系统的sendfile系统调用
 
 
3.页缓存减少磁盘IO
提供同步刷盘和间断性强制刷盘
log.flush.interval.messages
log.flush.interval.ms


Kafka如何保证高可靠:
1.分区副本(一般3个副本够了)
2.acks参数
3.enable.auto.commit 默认为true,自动提交offset,批量执行的,有时间窗口,对可靠性要求高的改为手动提交,宁愿重复消费(消费端幂等)

kafka的事务性消息:

1.2pc
2.Transaction Coordinator
3.事务日志:topic_transaction_state  存储事务状态的topic:_transaction-state
4.生产者事务ID:transaction.id

事务操作流程:
1.生产者调用initTransaction API 向Coordinator注册事务ID
2.Coordinator记录事务日志
3.生产者把消息写入目标分区
4.分区和Coordinator交互,事务完成后,消息状态设置为已提交。

需要事务消息的场景:
1.需要发送多条消息
2.发送消息到多个topic或多个partition
3.消费以后发出消息

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值