Kafka的消息格式

Commit Log

Kafka储存消息的文件被它叫做log,在Kafka文档中是这样描述的:

Each partition is an ordered, immutable sequence of messages that is continually appended to—a commit log

这反应出来的Kafka的行为是:消息被不断地append到文件末尾,而且消息是不可变的。

这种行为源于Kafka想要实现的功能:高吞吐量,多副本,消息持久化。这种简单的log形式的文件结构能够更好地实现这些功能,不过也会在其它方面有所欠缺,比如检索消息的能力。

对于Kafka来说,消息的主体部分的格式在网络传输中和磁盘上是一致的,也就是说消息的主体部分可以直接从网络读取的字节buffer中写入到文件(部分情况下),也可以直接从文件中copy到网络,而不需要在程序中再加工,这有利于降低服务器端的开销,以及提高IO速度(比如使用zero-copy的传输)。

Kafka的Producer、Broker和Consumer之间采用的是一套自行设计的基于TCP层的协议。Kafka的这套协议完全是为了Kafka自身的业务需求而定制的,而非要实现一套类似于Protocol Buffer的通用协议。

记录的划分以及消息的格式

Kafka使用了一种经典的格式:在消息前面固定长度的几个字节记录下这条消息的大小(以byte记),所以Kafka的记录格式变成了:

Offset MessageSize Message

消息被以这样格式append到文件里,在读的时候通过MessageSize可以确定一条消息的边界。

但是在Kafka的文档以及源码中,消息(Message)并不包括它的offset。Kafka的log是由一条一条的记录构成的,Kafka并没有给这种记录起个专门的名字,但是需要记住的是这个“记录”并不等于"Message"。Offset MessageSize Message加在一起,构成一条记录。而在Kafka Protocol中,Message具体的格式为:

Message => Crc MagicByte Attributes Key Value
  Crc => int32
  MagicByte => int8
  Attributes => int8
  Key => bytes
  Value => bytes

各个部分的含义是:

名称类型描述
CRCint32表示这条消息(不包括CRC字段本身,不包括记录的offset和MessageSize部分)的校验码。
MagicByteint8表示消息格式的版本,用来做后向兼容,目前值为0。
Attributesint8表示这条消息的元数据,目前最低两位用来表示压缩格式。
Keybytes表示这条消息的Key,可以为null。
Valuebytes表示这条消息的Value。Kafka支持消息嵌套,也就是把一条消息作为Value放到另外一条消息里面。

MessageSet

之所以要强调记录与Message的区别,是为了更好地理解MessageSet的概念。Kafka protocol里对于MessageSet的定义是这样的:

MessageSet => [Offset MessageSize Message]
  Offset => int64
  MessageSize => int32

各部分的含义:

名称类型描述
Offsetint64它用来作为log中的序列号,Producer在生产消息的时候还不知道具体的值是什么,可以随便填个数字进去
MessageSizeint32表示这条Message的大小
Message-表示这条Message的具体内容,其格式见上一小节。

也就是说MessageSet是由多条记录组成的,而不是消息,而这决定了更重要的性质:Kafka的压缩是以MessageSet为单位的,也就决定了Kafka的消息是可以递归包含的。

具体地说,对于Kafka来说,可以对一个MessageSet做为整体压缩,把压缩后得到的字节数组作为一条Message的value。于是,Message既可以表示未压缩的单条消息,也可以表示压缩后的MessageSet。

Message的压缩

Kafka支持下面几种压缩方式,

压缩方式编码
不压缩0
Gzip1
Snappy2
LZ43

Compressed Message的offset

即然可以把压缩后的MessageSet作为Message的value,那么这个Message的offset该如何设置呢?

这个offset的值只有两种可能:1, 被压缩的MessageSet里Message的最大offset; 2, 被压缩的MessageSet里Message的最小offset.

这两种取值没有功能的不同,只有效率的不同。

由于FetchRequest协议中的offset是要求broker提供大于等于这个offset的消息,因此broker会检查log,找到符合条件的,然后传输出去。那么由于FetchRequest中的offset位置的消息可位于一个compressed message中,所以broker需要确定一个compressed Message是否需要被包含在respone中。

  • 如果compressed Message的offset是它包含的MessageSet的最小offset。那么,我们对于这个Message是否应包含在response中,无法给出"是”或"否“的回答。比如FetchRequest中指明的开始读取的offset是14,而一个compressed Message的offset是13,那么这个Message中可能包含offset为14的消息,也可能不包含。
  • 如果compressed Message的offset是它包含的MessageSet的最大offset,那么,可以根据这个offset确定这个Message“不应该”包含在response中。比如FetchRequest中指明的开始读取的offset是14,那么如果一个compressed Message的offset是13,那它就不该被包含在response中。而当我们顺序排除这种不符合条件的Message,就可以找到第一个应该被包含在response中的Message(压缩或者未压缩), 从它开始读取。

在第一种情况下(最小offset),我们尽管可以通过连续的两个Message确定第一个Message的offset范围,但是这样在读取时需要在读取第二个Message的offset之后跳回到第一个Message,  这通常会使得最近一次读(也就读第二个offset)的文件系统的缓存失效。而且逻辑比第二种情况更复杂。在第二种情况下,broker只需要找到第一个其offset大于或等于目标offset的Message,从它可以读取即可,而且也通常能利用到文件系统缓存,因为offset和消息内容有可能在同一个缓存块中。

实际在broker给compressed Message赋予offset时,其逻辑也是赋予其包含的messages中的最大offset

Validate Message

什么需要验证?

首先,网络传输过程中,数据可能会产生错误,即使是写在磁盘上的消息,也可能会由于磁盘的问题产生错误。因此,broker对接收到的消息需要验证其完整性。这里的消息就是前边协议里定义的Message。

对于消息完整性的检测,是使用CRC32校验,但是并不是对消息的所有部分计算CRC,而是对Message的Crc部分以后的部分,不包括记录的offset和MessageSize部分。把offset和MessageSize加到CRC计算中,可以对完整性有更强的估证,但是坏处在于这两个部分在消息由producer到达broker以后,会被broker重写,因此如果把它们计算在crc里边,就需要在broker端重新计算crc32,这样会带来额外的开销。

除了消息的完整性,还需要对消息的合规性进行检验,主要是检验offset是否是单调增长的,以及MessageSize是超过了最大值。

这里检验时使用的MessageSize就不是Message本身的大小了,而是一个记录的大小,包括offset和MessageSize。

何时需要验证?

在broker把收到的producer request里的MessageSet append到Log之前,以及consumer和follower获取消息之后,都需要进行校验。

这种情况分成两种:

1. broker和consumer把收到的消息append到log之前

2. consumser收到消息后


参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值