前言
正文
生产者发送流程
生产端主要由两个线程协调运行,分别是main线程和sender线程
- 拦截器:拦截器的作用是实现消息的定制化。
- 序列化:利用指定工具对key,value进行序列化。
- 分区器:通过这个判断将消息发往哪个partition。
- 指定partition:直接发往对于的partition。
- 未指定,自定义了分区器:根据分区器的策略发往对应的partition。
- 未指定partition,未定义分区器,但是key不为空:使用默认分区器,根据key的hash值与topic可用的partition数取余得到对应的partion值,发往对应的partition。
- 未指定partition,未定义分区器,key为空:第一次随机获取一个int值(后序再这个值进行递增),将这个值与topic可用的partition数取余得到对应的partion值,即round-robin算法。 4.消息累加器:选择分区后并不会直接发送消息,而是存入消息累加器(ConcurrentMap)中,一个partition一个batch,当batch满了之后会唤醒sender线程,发送消息。
数据可靠性保证ACK
服务端响应策略
- 需要
半数以上的follower节点同步成功,这样的话客户端的等待时间就会少一点,延迟低。(这也是为啥说部署的节点一定要是奇数个,否则可能发生脑裂现象) - 需要全部follower节点同步成功,客户端的等待时间较长。
kafka采用的是第二种+ISR机制(不完全同步)。
ISR(精兵悍将)
kafka的leader维护了一个动态的set集合,将那些优质的从节点放入其中,每次同步保证这些节点成功就算成功。
剔除机制:当一定时间没有向leader同步数据就会被移除集合。由参数replica.lag.time.max.ms决定,默认值为30秒。
Ack应答机制
- acks=0:producer不等待broker的ack,发送即成功。最低延迟,但是broker故障有消息丢失的风险。
- acks=1(默认):producer等待broker的ack,partion的leader落盘成功后即返回ack。但是如果follower同步成功后leader故障,那么将会丢失数据。
- acks=-1(all):producer等待broker的ack,partion的leader和follower全部落盘成功后返回ack。如果在数据落盘成功,broker返回ack之前leader发生故障,没有发送ack给producer,producer会重发,导致消息重发。此时把reties设置为0,才不会重复。
总结:三种效率依次递减(producer的吞吐性降低),但是数据的健壮性依次提升。类比Mysql的binlog主从复制———同步,异步,半同步。
Kafka的broker存储的原理
文件存储的结构
配置文件:config/ever.properties
logs.dir配置:(默认) /temp/kafka-logs
partition分区
为了实现横向扩展,把不同的数据存放在不同的broker上,同时降低单台服务器的访问压力,把一个topic中的数据分隔成多个partition。
一个partittion中的数据是有序的,但全局不一定有序。
topic名字后面的数字标号即代表分区。
replica副本
为了提高分区的可靠性。
创建topic的时候,通过指定replication-factor确定topic的副本数。
副本因子必须小于等于节点数,否则会报错。
这样可以保证一个分区的副本保存在不通的节点上,不然也失去的副本机制的意义。
副本分为leader和follower两个角色,leader对外提供读写,follower就只是异步拉去leader的数据。
问:为什么不想Mysql一样实现读写分离,写在leader,读在follower节点?
答:设计思想不一样。读写都在leader节点上,就不存在读写不一致的问题,这个叫做单调读一致性。
副本分布规则
第一个分区(编号为0的分区)的第一个副本放置位置是随机从broker中选择的其他分区的第一个副本放置位置相对于第一个副本的位置向后移 作用:提高容灾能力,基本上每个分区的第一个副本都是leader,而第一副本的错开,保证的影响不会很大。
segment
为了防止log不断追加导致文件过大,导致检索消息效率变低,一个partition又被划分成多个segment(Mysql也有segment概念,叶子节点就是数据段,非叶子节点就是索引段)。
在磁盘上,每个segment由一个log文件和两个index文件组成。
.log日志文件(日志就是存储的数据):在一个segment文件里面,日志是追加写入的。满足一定条件就会切分日志文件,产生一个新的segment。
- 第一种是根据文件的大小:当一个segment写满之后(默认是1G,由参数
log.segment.bytes控制),会创建一个新的segment,用最新的offset作为名称。 - 根据消息的最大时间戳,和当前系统时间戳的差值。默认168小时(一周),
log.roll.hours=168。就是说距离服务器上一次写入数据的时间是一周前就会创建一个新的文件写入。 - offset索引文件(.index)或者timestamp索引文件(.timeindex)达到一定的大小(默认是10m),也要拆分,不然这一套东西对应不上。
索引
一个segment中会存放很多数据,索引是可以快速检索消息的机制。
offset索引
稀疏索引,每隔4kb(默认)就会产生一条索引
log.index.interval.bytes
timestamp索引
默认是消息创建时间戳
log.message.timestamp.type=CreateTime
问:kafka是如何基于索引快速检索消息的?
答:找到segment,根据offset找到对应的position,基于position找到对应的.log查找offset,和消息的offset进行比较,直到找到消息。
总结
总体架构
Kafka消息清理机制
默认开启
有两种方式:1.直接delete,2.压缩
直接删除
1.根据时间删除,默认开启,超过168小时(一周)的会被删除 2.根据文件大小删除,默认关闭,默认1G
压缩策略
将相同key的消息合并为最后一个value
高可用架构
leader选举
利用ZK实现选举,ZK的节点的唯一性、watch机制、临时节点。
步骤:
- 利用ZK的节点的唯一性选出一个Controller(控制器) ,Controller挂了重新选。
- COntroller选出ISR集合中的副本,作为候选人。(ISR可能为空,如果需要防止这种情况发生允许isr之外的也有资格,但是会造成数据丢失,不建议)。
- 默认是选ISR中的第一个replica作为leader,其他人都是陪跑。
主从同步
LEO(Log End Offset):下一条等待写入的消息的offset。
HW(High Watermark):ISR中最小的LEO.
consumer最多只能消费到HW之前的位置,也就是说未被同步完全的消息是不能被消费的。如果同步成功就被消费,那consumer group的offset会偏大,如果leader崩溃会导致消息丢失。
从节点怎么和主节点保持同步?
- follower节点会向leader节点发送一个fetch请求,leader节点向follower节点发送数据.
- follower接收存储数据,更新自己LEO.
- Leader更新HW(ISR中最小的LEO).
follower故障
- 会被踢出ISR
- 恢复后将当前高于HW部分截掉,然后重新开始同步。
- 追上Leader30秒后,加入ISR
leader故障
- 重新选leader,优先选replica1
- 其余副本将高于HW部分删除,重新同步
这种机制只能保证副本直接的数据一致性,并不能保证数据不丢失或者不重复。
Kafka消费者原理
对于一个partition,consumer group一般都是根据上一次消费的位置(offset)继续去消费。
如果是新增的group,没有offset,那就从最新消息开始消费。
offset的存储
offset存入broker的一特殊topic中。
offset的更新
默认是每隔5秒钟,自动提交更新一次
也可以手动提交更新
Kafka消费者消费策略
一个消费组对应一个topic的所有partition
如果组内消费者的数量大于分区数量,则多出来的无法消费。
消费者的数量小于分区数量,如果分配?
- range:每个消费者分一部分
- round robin : 每人一个分区,循环给。
- stiky:粘滞,自行baidu 这里也可以手动指定。
原则:
- 分区尽可能均匀。
- 结果与上次分的相同。
rebalance 分区重分配
- 消费者数量发生变化
- 分区数量发生变化
如何提高消费者消费速度?
- 增加分区数量,尽量保证消费者的数量与分区数量1:1。
- 代码方面提高消费方面的处理速度。
- 提高消费者一次消息拉取的数量;开启消息压缩(保证broker和producer使用的使用一种算法)。
- 如果不考虑消息一致性,重写分区器,使消息分发到分区尽量均衡。
Kafka为什么这么快
- 顺序读写+PageCache:kafka是先写入内存中,操作系统刷盘写入文件(这个过程可能会导致消息丢失);message是追加写入文件末尾的,不是随机写入的。
- 索引:offset和timestamp索引
- 批量读写和文件压缩:消息累加器减少网络IO
- 零拷贝:DMA,内存直接访问。
Kafka消息不丢失配置
- producer使用带回调的send方法,可以根据消息丢失做针对性的处理。
- acks=all:确保所有follower都落盘成功。
- reties设置比较大的值:多次重试。
- unclean.leader.election.enable=false : 默认关闭,不允许非isr集合里的follower参与选举。
- 增加副本数,>=3
- 设置min.insync.replics > 1:broker端参数,控制isr中最少的副本数,控制消息写入多少副本才算已提交。推荐replication.factor=min.insync.replics+1
- 确保消息消费完成再提交。consumer的enable.auto.commit最好设置成false,并自己控制offset的提交更新。