4. Kafka 消息结构:消息的 Key、Value、Timestamp、Headers、Offset 详解

Kafka 消息(Record)是数据在集群中存储和传输的最小单元。理解消息的内部结构,是进行契约校验、版本兼容性分析以及故障排查的基础。一条完整的 Kafka 消息由五个核心字段组成:Key、Value、Timestamp、Headers 和 Offset。其中 Offset 属于元数据,由 Broker 分配,其余字段由生产者填充。

Kafka 消息结构全景图 Kafka 消息结构全景图 一条完整的 Kafka 消息(Record) Key 分区路由 / 压缩 Value 业务数据主体 Timestamp 时间戳 / 日志保留 Headers 元数据 / 契约版本 Offset(Broker 分配) 分区内唯一递增序号 💡 Offset 由 Broker 在消息追加到日志时分配,不属于生产者发送的数据部分

4.1 消息 Key:分区路由与消息压缩

Key 是消息的可选字段,用于决定消息被发送到哪个分区。Kafka 生产者通过 分区器(Partitioner) 对 Key 进行哈希计算,将相同 Key 的消息路由到同一个分区,从而保证同一 Key 的消息有序。

  • 分区路由:默认分区器使用 murmur2 哈希算法,对 Key 的字节数组取模分区数。
  • 空 Key:如果 Key 为 null,Kafka 2.4+ 默认使用粘性分区策略(Sticky Partitioner),批量发送到同一分区以提升吞吐。
  • 压缩:Key 与 Value 一起参与消息压缩(如 gzip、snappy、lz4、zstd),但 Key 通常较小,压缩收益有限。
  • 契约校验:Key 的数据类型和序列化方式必须在契约中声明,例如 Avro 或 Protobuf Schema。
实战建议:在契约测试中,Key 的 Schema 变更(如字段类型从 int 改为 string)会导致分区路由结果不一致,引发消息乱序。建议将 Key 的 Schema 标记为 稳定(Stable),避免频繁变更。

4.2 消息 Value:业务数据主体

Value 是消息的核心负载,承载业务数据。Value 的序列化格式直接决定了契约校验的复杂度。

序列化格式 特点 契约校验方式
Avro 带 Schema,支持向前/向后兼容 Schema Registry + 兼容性策略
Protobuf 强类型,二进制紧凑 .proto 文件 + 字段编号校验
JSON 可读性好,无强制 Schema JSON Schema 或 OpenAPI 契约
String / Bytes 无结构约束 仅校验长度或正则模式

在契约测试中,Value 的 Schema 版本号通常通过 Headers 传递,消费者根据版本号选择对应的反序列化器。

4.3 消息 Timestamp:时间戳与日志保留策略

Timestamp 记录消息的生产时间或 Broker 接收时间,由生产者配置 message.timestamp.type 决定。

  • CreateTime(默认):生产者客户端生成的时间戳,表示消息创建时间。
  • LogAppendTime:Broker 追加消息到日志文件时生成的时间戳,表示消息存储时间。

Timestamp 直接影响 日志保留策略:基于时间的删除(retention.ms)和基于时间的压缩(delete.retention.ms)都依赖此字段。契约测试中需验证时间戳格式是否为 Unix 毫秒级,以及是否在合理时间范围内。

常见陷阱:生产者和 Broker 的时钟不同步会导致 Timestamp 偏差,可能引发消息被提前删除或延迟删除。建议在契约中约定时间戳的精度(毫秒)和允许的误差范围(如 ±5 秒)。

4.4 消息 Headers:元数据与契约版本传递

Headers 是 Kafka 0.11+ 引入的键值对集合,用于携带消息的元数据。每个 Header 由 Key(String) 和 Value(byte[]) 组成,不参与分区路由。

在契约测试中,Headers 是传递 契约版本号 和 序列化格式标识 的理想载体:

// 生产者设置 Headers 示例
ProducerRecord<String, MyAvroRecord> record = new ProducerRecord<>("orders", key, value);
record.headers().add("schema-version", "2.1.0".getBytes(StandardCharsets.UTF_8));
record.headers().add("content-type", "avro".getBytes(StandardCharsets.UTF_8));
producer.send(record);

消费者端通过读取 Headers 动态选择反序列化器,实现多版本兼容:

// 消费者读取 Headers 示例
for (Header header : record.headers()) {
    if ("schema-version".equals(header.key())) {
        String version = new String(header.value(), StandardCharsets.UTF_8);
        // 根据 version 选择对应的 Schema
    }
}
Headers 在契约版本传递中的角色 Headers 在契约版本传递中的角色 生产者 设置 Headers schema-version: 2.1.0 Kafka 消息 Headers + Value 版本号随消息存储 消费者 读取 Headers 选择 Schema v2.1.0 💡 Headers 是契约版本传递的“信使” • 生产者将 Value 的 Schema 版本号写入 Headers • 消费者根据 Headers 中的版本号动态选择反序列化器,实现多版本兼容 • 契约测试中需校验 Headers 的 Key 命名规范和 Value 编码格式

4.5 消息 Offset:分区内唯一递增序号

Offset 是 Kafka 为每条消息在分区内分配的 唯一且递增的 64 位整数。Offset 由 Broker 在消息追加到日志文件时生成,消费者通过 Offset 记录消费进度(Consumer Offset)。

  • 唯一性:在同一个分区内,Offset 唯一标识一条消息;不同分区之间 Offset 独立。
  • 顺序性:Offset 严格递增,新消息的 Offset 总是大于旧消息。
  • 不可变:消息一旦写入,Offset 不会改变;即使消息被删除(日志清理),Offset 也不会复用。
  • 契约测试:Offset 属于 Broker 元数据,不参与序列化契约,但消费者需校验 Offset 的单调递增性,用于检测消息丢失或重复。
核心要点:在契约测试中,Key、Value、Timestamp、Headers 是生产者与消费者之间的 数据契约,而 Offset 是 Broker 与消费者之间的 进度契约。两者共同保障消息的可靠传递与版本兼容。

4.6 消息结构在契约测试中的校验清单

字段 校验项 契约测试断言示例
Key 序列化格式、Schema 版本、是否允许 null key.schema == "avro" && key.version == "1.0"
Value Schema 兼容性、字段类型、必填字段 value.field("orderId").type == "string"
Timestamp 时间戳类型、精度、范围 timestamp >= 1700000000000L
Headers Key 命名规范、Value 编码、必选 Header header("schema-version") != null
Offset 单调递增、无跳跃、无重复 offset == previousOffset + 1