kafka的日志格式
ps:该图引用下面kafka日志版本的演变的博客
- crc32:消息校验码
- magic:消息版本号,0=v0,1=v1,2=v2,目前是2
- attributes:占用2字节,低3位指压缩格式,0=none,1=gzip,2=snappy,3=lz4;第4位指时间戳,第5位值是否为事务消息,第6位指是否为control消息,用于支持事务,0=否,1=是,其余保留
- key length:息的key的长度,若为-1,则表示没有设置key,即key=null
- key:可选,如果没有key则无此字段
- value length:实际消息体的长度,若为-1,则表示消息为空(墓碑消息)
- value:消息体,可以为空,即墓碑消息
- 中图length:消息总长度
- timestamp delta:时间戳增量,这里保存与RecordBatch的起始时间戳的差值
- offset delta:位移增量,保存与RecordBatch起始位移的差值
- headers:用来支持应用级别的扩展,而无需像v0和v1版本一样将一些应用级别的属性值嵌入在消息体里面
- first offset:表示当前RecordBatch的起始位移
- 左图length:计算partition leader epoch到headers之间的长度
- partition leader epoch:用来确保数据可靠性
- last offset delta:RecordBatch中最后一个Record的offset与第一个Record的offset的差值,主要被broker用来确认RecordBatch中Records的组装正确性
- first timestamp:RecordBatch中第一条Record的时间戳
- max timestamp:RecordBatch中最大的时间戳,一般情况下是指最后一个Record的时间戳, 和last offset delta的作用一样,用来确保消息组装的正确性
- producer id:用来支持幂等性
- producer epoch:和producer id一样,用来支持幂等性
- first sequence:和producer id与producer epoch一样,用来支持幂等性
- records count:RecordBatch中Record的个数
日志查找
1.查找offset=350的消息,先二分查找确定消息在哪个segment中
2.到对应segment的.index文件中找到小于350的最大偏移量,即 345,其对应的物理位置是328
3.根据物理位置328,直接定位到.log文件的328文件位置
4.顺序读取每条消息的偏移量,但不读取消息内容
5.找到offset=350的消息,得到物理位置为448
6.开始真正读取offset=350的消息内容并返回
日志压缩(log campaction)
在默认的删除规则之外提供的另一种数据清除策略,对于有相同key的的不同value值,只保留最后一个版本 kafka中
用于保存消费者消费位移的主题”__consumer_offsets”使用的就是log compaction策略
每个日志目录下,有名为”cleaner-offset-checkpoint”的文件,根据该文件可以将日志文件分成2部分
clean部分:偏移量是断续的,经过压缩的部分
dirty部分:偏移量是连续的,未清理过的部分
activeSegment部分:活跃的热点数据,不参与log campaction,默认情况下firstUncleanableOffset等于activeSegment的baseOffset
log campaction使用时应注意每个消息的key值不为null,这种方式当客户端进行消费时总能赶上dirty部分的情况,它就能读取到日志的所有消息,反之,就不可能读到全部的消息
如何确选择合适的log文件进行清理:
选择污浊率最高的,可通过配置log.cleaner.min.cleanable.ratio参数(默认0.5)
dirtyRatio = dirtyBytes / (cleanBytes + dirtyBytes)
如何对log文件中消息的key进行筛选:
1.创建一个名为”SkimpyOffsetMap”的哈希表来构建key与offset的映射表
2.遍历第一遍log文件,把每个key的hashCode和最后出现的offset都保存在SkimpyOffsetMap中
3.遍历第二遍log文件,检查每个消息是否符合保留条件,如果符合就保留下来,否则就会被清理掉
保留条件:假设一条消息的offset为O1,这条消息的key在SkimpyOffsetMap中所对应的offset为O2,如果O1>=O2即为满足保留条件
hash冲突处理:采用线性探测法来处理哈希冲突
缺点:遇到两个不同的key但哈希值相同的情况,那么其中一个key所对应的消息就会丢失
日志压缩步骤举例:
1.第一次日志压缩,清理点为0,日志头部的范围从0到活动分段的基准偏移量13
2.第一次压缩后,清理点更新为13,第二次日志压缩时,日志头部范围从13到活动日志分段的基准偏移量20,日志尾部范围从0到清理点的位置13
3.第二次压缩后,清理点更新为20,第三次日志压缩时,日志头部范围从20到活动日志分段的基准偏移量28,日志尾部范围从2到清理点的位置20
日志压缩合并:
1.第一次日志压缩,清理点等于0,没有尾部日志,日志头部从6:00带7:40,所有日志分段文件都是1GB,不考虑删除的消息
2.第一次日志压缩后,清理点改为日志头部末尾即7:40,每个新日志分段的大小都小于1GB
3.第二次日志压缩时,清理点为7:40,日志头部从8:00到8:10,日志尾部从6:00到7:40,压缩操作会将多个小文件分成1组,每一组不超过1GB
kafka日志版本的演变
kafka生产者幂等的实现思路
1.为每个producer设置唯一的PID
2.引入了序列号字段sequence number,标识某个producer发送的每批消息,sequence number是从0开始严格单调递增的
3.broker端会为每个PID(即每个producer)保存该producer发送过来的消息batch的某些元信息(如PID信息、消息batch的起始seq number及结束seq number等)
4.每当该PID发送新的消息batch时,Kafka broker就会对比这些信息,如果发生冲突(比如起始seq number和结束seq number与当前缓存的相同),那么broker就会拒绝这次写入请求;倘若没有冲突,那么broker端就会更新这部分缓存然后再开始写入消息
总结:为每个producer设置唯一的PID并引入seq number以及broker端seq number缓存更新机制来去重
这部分完~