kafka每个主题可以有多个分区,每个分区在它所在的broker上创建一个文件夹。
每个分区又分为多个段,每个段有两个文件,log文件里顺序存消息,index文件里存消息的索引。
段的命名直接以当前段的第一条消息的offset为名。
注意是偏移量,不是序号! 第几条消息 = 偏移量 + 1。类似数组长度和下标。
所以offset从0开始(可以开新队列新groupid消费第一条消息打印offset得到验证)
例如:
每个log文件配备一个索引文件 *.index
文件格式为: (offset , 内存偏移地址)
文件名是offset命名格式,所以可以判断。
6,9807
,说明消息藏在这里!Kafka作为消息中间件,数据需要按照一定的规则删除,否则数据量太大会把集群存储空间占满。
删除数据方式:
Kafka删除数据的最小单位:segment(段)
,也就是直接干掉文件!一删就是一个log和index文件
1、通过km新建一个test主题,加2个分区新建时,注意下面的选项:
segment.bytes = 1000
,即:每个log文件到达1000byte时,开始创建新文件删除策略:
retention.bytes = 2000
,即:超出2000byte的旧日志被删除retention.ms = 60000
,即:超出1分钟后的旧日志被删除以上任意一条满足,就会删除。
3、查看2个分区的日志文件清单,注意当前还没有任何消息写进来
4、往里灌数据。启动项目通过swagger发送消息。注意边发送边查看上一步的文件列表信息!
继续逐条发送,返回再来看文件,大小为1000,到达边界!
继续发送消息!1号分区的log文件开始分裂。说明第8条消息已经进入了第二个log
持续发送,另一个分区也开始分离
5、持续发送消息,分区越来越多。过一段时间后再来查看,清理任务将会执行,超出的日志被删除!(默认调度间隔5min)。通过log.retention.check.interval.ms
参数指定
Kafka 在执行消息的写入和读取这么快,其中的一个原因是零拷贝(Zero-copy)技术。
关于零拷贝的内容请看我的这篇博客。
1)先回顾两个值:
LEO指向了当前已经写入的最大偏移量
HW指向了可以被消费的最高偏移量
LEO >= HW
2)再看下几个值的存储位置:
注意!分区是有leader和follower的,最新写的消息会进入leader,follower从leader不停的同步。
无论leader还是follower,都有自己的HW和LEO,存储在各自分区所在的磁盘上。
leader多一个Remote LEO
,它表示针对各个follower的LEO,leader又额外记了一份!
3)为什么这么做呢?
leader会拿这些remote值里最小的来更新自己的hw,具体过程我们详细往下看
我们来看这几个值是如何更新的:
1)leader.LEO
这个很简单,每次producer有新消息发过来,就会增加
2)其他值
另外的4个值初始化都是 0
他们的更新由follower的fetch(同步消息线程)得到的数据来决定!
如果把fetch看做是leader上提供的方法,由follower远程请求调用,那么它的伪代码大概是这个样子:
//java伪代码!
//follower端的操作,不停的请求从leader获取最新数据
class Follower{
private List<Message> messages;
private HW hw;
private LEO leo;
@Schedule("不停的向leader发起同步请求")
void execute(){
//向leader发起fetch请求,将自己的leo传过去
//leader返回leo之后最新的消息,以及leader的hw
LeaderReturn lr = leader.fetch(this.leo) ;
//存消息
this.messages.addAll(lr.newMsg);
//增加follower的leo值
this.leo = this.leo + lr.newMsg.length;
//比较自己的leo和leader的hw,取两者小的,作为follower的hw
this.hw = min(this.leo , lr.leaderHW);
}
}
//leader返回的报文
class LeaderReturn{
//新增的消息
List<Messages> newMsg;
//leader的hw
HW leaderHW;
}
//leader在接到follower的fetch请求时,做的逻辑
class Leader{
private List<Message> messages;
private LEO leo;
private HW hw;
//Leader比follower多了个Remote!
//注意!如果有多个副本,那么RemoteLEO也有多个,每个副本对应一个
private RemoteLEO remoteLEO;
//接到follower的fetch请求时,leader做的事情
LeaderReturn fetch(LEO followerLEO){
//根据follower传过来的leo,来更新leader的remote
this.remoteLEO = followerLEO ;
//然后取ISR(所有可用副本)的最小leo作为leader的hw
this.hw = min(this.leo , this.remoteLEO) ;
//从leader的消息列表里,查找大于follower的leo的所有新消息
List<Message> newMsg = queryMsg(followerLEO) ;
//将最新的消息(大于follower leo的那些),以及leader的hw返回给follower
LeaderReturn lr = new LeaderReturn(newMsg , this.hw)
return lr;
}
}
1)产生的背景
0.11版本之前的kafka,完全借助hw作为消息的基准,不管leo。
发生故障后的规则:
follower故障再次恢复后,从磁盘读取hw的值并从hw开始剔除后面的消息,并同步leader消息。
leader故障后,新当选的leader的hw作为新的分区hw,其余节点按照此hw进行剔除数据,并重新同步。
上述根据hw进行数据恢复会出现数据丢失和不一致的情况,下面分开来看
假设:
我们有两个副本:leader(A),follower(B)
2)改进思路
0.11之后,kafka改进了hw做主的规则,这就是leader epoch
。
leader epoch给leader节点带了一个版本号,类似于乐观锁的设计。
它的思想是,一旦发生机器故障,重启之后,不再机械的将leo退回hw,
而是借助epoch的版本信息,去请求当前leader,让它去算一算leo应该是什么。
3)实现原理