一、简介
Kafka 是一个高吞吐量、分布式的发布—订阅消息系统。据Kafka 官方网站介绍,当前的Kafka 已经定位为一个分布式流式处理平台(a distributed streaming platform),它最初由LinkedIn公司开发,后来成为Apache 项目的一部分。Kafka 核心模块使用Scala 语言开发,支持多语言(如Java、C/C++、Python、Go、Erlang、Node.js 等)客户端,它以可水平扩展和具有高吞吐量等
特性而被广泛使用。Kafka使用基于TCP的二进制协议。该协议定义了所有API的请求及响应消息。所有消息都是通过长度来分隔,并且由后面描述的基本类型组成。
二、通讯协议
1、基本数据类型
定长基本类型
int8, int16, int32, int64 : 不同精度(以bit数区分)的带符号整数,以大端(Big Endiam)方式存储.
变长基本类型
bytes, string : 这些类型由一个表示长度的带符号整数N以及后续N字节的内容组成。 string 使用int16表示长度,bytes使用int32表示长度。NULLABLE_STRING和 NULLABLE_BYTES表示可以为空,如果为空长度为-1(null)。
varint, varlong: varint 表示介于-2 31和2 31 -1 之间的整数。编码遵循Google Protocol Buffers的可变长度Zig-zag编码 。
varlong 表示介于-2 63和2 63 -1 之间的整数。编码遵循Google Protocol Buffers的可变长度Zig-zag编码 。
数组
这个类型用来处理重复的结构体数据。他们总是由一个代表元素个数int32整数N 表示数组的个数,以及后续的N个重复结构体组成,这些结构体自身是有其他的基本数据类型组成。比如一个foo的结构体数组表示为[foo],注意前面会有四个字节的数组个数。
2、通用的请求和响应格式
RequestOrResponse => Size (RequestMessage | ResponseMessage)
Size => int32
域(FIELD) | 描述 |
---|---|
MessageSize | MessageSize 域给出了后续请求或响应消息的字节(bytes)长度。客户端可以先读取4字节的长度N,然后读取并解析后续的N字节请求内容。 |
2、1 请求(Requests)
所有的请求具有以下格式:
RequestMessage => ApiKey ApiVersion CorrelationId ClientId RequestMessage
ApiKey => int16
ApiVersion => int16
CorrelationId => int32
ClientId => NULLABLE_STRING
RequestMessage => MetadataRequest | ProduceRequest | FetchRequest | OffsetRequest |
OffsetCommitRequest | OffsetFetchRequest
域(FIELD) | 描述 |
---|---|
ApiKey | 这是一个表示所调用的API的数字id(即它表示是一个元数据请求?生产请求?获取请求等). |
ApiVersion | 这是该API的一个数字版本号。我们为每个API定义一个版本号,该版本号允许服务器根据版本号正确地解释请求内容。响应消息也始终对应于所述请求的版本的格式。 |
CorrelationId | 这是一个用户提供的整数。它将会被服务器原封不动地回传给客户端。用于匹配客户机和服务器之间的请求和响应。 |
ClientId | 这是为客户端应用程序的自定义的标识。用户可以使用他们喜欢的任何标识符,他们会被用在记录错误时,监测统计信息等场景。例如,你可能不仅想要监视每秒的总体请求,还要根据客户端应用程序进行监视,那它就可以被用上(其中每一个都将驻留在多个服务器上)。这个ID作为特定的客户端对所有的请求的逻辑分组。 |
2、2响应(Responses)
Response => CorrelationId ResponseMessage
CorrelationId => int32
ResponseMessage => MetadataResponse | ProduceResponse | FetchResponse | OffsetResponse | OffsetCommitResponse | OffsetFetchResponse
域(FIELD) | 描述 |
---|---|
CorrelationId | 服务器传回给客户端它所提供用作关联请求和响应消息的整数。 |
所有响应都是与请求成对匹配(例如,我们将发送回一个元数据请求,会得到一个元数据响应)。
2、3消息(Record)
消息(即记录)是分批写入的,批次(RecodeBatch)即一个或多个记录,一个批次里包含一个或多个消息记录。
在kafka0.11版本之前,消息传输是用消息集(message sets)格式中。
Record Batch
baseOffset: int64
batchLength: int32
partitionLeaderEpoch: int32
magic: int8 (current magic value is 2)
crc: int32
attributes: int16
bit 0~2:
0: no compression
1: gzip
2: snappy
3: lz4
bit 3: timestampType
bit 4: isTransactional (0 means not transactional)
bit 5: isControlBatch (0 means not a control batch)
bit 6~15: unused
lastOffsetDelta: int32
firstTimestamp: int64
maxTimestamp: int64
producerId: int64
producerEpoch: int16
baseSequence: int32
records: [Record]
Record
length: varint
attributes: int8
bit 0~7: unused
timestampDelta: varint
offsetDelta: varint
keyLength: varint
key: byte[]
valueLen: varint
value: byte[]
Headers => [Header]
Record Header
headerKeyLength: varint
headerKey: String
headerValueLength: varint
Value: byte[]
域(FIELD) | 描述 |
---|---|
baseOffset | 这是在Kafka中作为日志序列号使用的偏移量。当生产者发送非压缩消息,这时候它可以填写任意值。当生产者发送压缩消息,为了避免服务端重新压缩,每个压缩消息的内部消息的偏移量应该从0开始,然后依次增加(kafka压缩消息详见后面的描述)。 |
Crc | Crc是的剩余消息字节的CRC32值。broker和消费者可用来检查信息的完整性。 |
Magic | 这是一个用于允许消息二进制格式的向后兼容演化的版本id。当前值是0。 |
Attributes | 这个字节保存有关信息的元数据属性。最低的3位包含用于消息的压缩编解码器。第四位表示时间戳类型,0代表CreateTime,1代表LogAppendTime。生产者必须把这个位设成0。所有其他位必须被设置为0。 |
Key | Key是一个可选项,它主要用来进行指派分区。Key可以为null。 |
Value | Value是消息的实际内容,类型是字节数组。Kafka支持本身递归包含,因此本身也可能是一个消息集。消息可以为null |
2、4压缩(Compression)
Kafka支持压缩多条消息以提高效率,当然,这比压缩一条原始消息要来得复杂。因为单个消息可能没有足够的冗余信息以达到良好的压缩比,压缩的多条信息必须以特殊方式批量发送(当然,如果真的需要的话,你可以自己压缩批处理的一个消息)。要被发送的消息被包装(未压缩)在一个MessageSet结构中,然后将其压缩并存储在一个单一的“消息”中,一起保存的还有相应的压缩编解码集。接收系统通过解压缩得到实际的消息集。外层MessageSet应该只包含一个压缩的“消息”。
Kafka目前支持一下两种压缩算法:
压缩算法(COMPRESSION) | 编码器编号(CODEC) |
---|---|
None | 0 |
GZIP | 1 |
Snappy | 2 |
3、接口
本节将给出每个API的用法、二进制格式,以及它们的字段的含义的细节。以kafka_2.11-1.0.0版本为例抓包测试。
3、1 生产接口(Produce API)
生产者API用于将消息集发送到服务器。为了提高效率,它允许在单个请求中发送多个不同topic的不同分区的消息。
生产者API使用通用的消息集格式,但由于发送时还没有被分配偏移量,因此可以任意填写该值。
Produce Request
Produce Request (Version: 0、1、2) => acks timeout [topic_data]
acks => INT16
timeout => INT32
topic_data => topic [data]
topic => STRING
data => partition record_set
partition => INT32
record_set => RECORDS
Produce Request (Version: 3、4、5、6) => transactional_id acks timeout [topic_data]
transactional_id => NULLABLE_STRING
acks => INT16
timeout => INT32
topic_data => topic [data]
topic => STRING
data => partition record_set
partition => INT32
record_set => RECORDS
域(FIELD) | 描述 |
---|---|
transactional_id | 事务id,如果生成器不是事务性的,则返回null |
acks | 这个值表示服务端收到多少确认后才发送反馈消息给客户端。如果设置为0,那么服务端将不发送response(这是唯一的服务端不发送response的情况)。如果这个值为1,那么服务器将等到数据写入到本地日之后发送response。如果这个值是-1,那么服务端将阻塞,知道这个消息被所有的同步副本写入后再发送response。 |
timeout | 这个值提供了以毫秒为单位的超时时间,服务器可以在这个时间内可以等待接收所需的Ack确认的数目。超时并非一个确切的限制,有以下原因:(1)不包括网络延迟,(2)计时器开始在这一请求的处理开始,所以如果有很多请求,由于服务器负载而导致的排队等待时间将不被包括在内,(3)如果本地写入时间超过超时,我们将不会终止本地写操作,这样这个超时时间就不会得到遵守。要使硬超时时间,客户端应该使用套接字超时。 |
topicName | 该数据将会发布到的topic名称 |
partition | 该数据将会发布到的分区 |
record_set | 上面描述的标准格式的消息集合 |
通过抓包分析,在partition 之后有四字节的长度,正好是record_set 的长度。版本是V5。
Responses:
Produce Response (Version: 0) => [responses]
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code base_offset
partition => INT32
error_code => INT16
base_offset => INT64
Produce Response (Version: 1) => [responses] throttle_time_ms
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code base_offset
partition => INT32
error_code => INT16
base_offset => INT64
throttle_time_ms => INT32
Produce Response (Version: 3、4) => [responses] throttle_time_ms
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code base_offset log_append_time
partition => INT32
error_code => INT16
base_offset => INT64
log_append_time => INT64
throttle_time_ms => INT32
Produce Response (Version: 5) => [responses] throttle_time_ms
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code base_offset log_append_time log_start_offset
partition => INT32
error_code => INT16
base_offset => INT64
log_append_time => INT64
log_start_offset => INT64
throttle_time_ms => INT32
域 | 描述 |
---|---|
topic | 此响应对应的主题。 |
partition | 此响应对应的分区。 |
error_code | 如果有,此分区对应的错误信息。错误以分区为单位提供,因为可能存在给定的分区不可用或者被其他的主机维护(非Leader),但是其他的分区的请求操作成功的情况 |
base_offset | 追加到该分区的消息集中的分配给第一个消息的偏移量。 |
log_append_time | 附加消息后代理返回的时间戳。如果CreateTime用于主题,则时间戳将为-1。如果LogAppendTime用于主题,则时间戳将是附加消息时的代理本地时间。 |
log_start_offset | 创建此生成响应时日志的起始偏移量 |
ThrottleTime | 由于限额冲突而导致的时间延迟长度,以毫秒为单位。(如果没有违反限额条件,此值为0) |
3、2
获取消息接口(Fetch API)
获取消息接口用于获取一些topic分区的一个或多个的日志块。逻辑上根据指定topic,分区和消息起始偏移量开始获取一批消息。在一般情况下,返回消息的偏移量将大于或等于开始偏移量。然而,如果是压缩消息,有可能返回的消息的偏移量比起始偏移量小。这类的消息的数量通常较少,并且调用者必须负责过滤掉这些消息。
获取数据指令请求遵循一个长轮询模型,如果没有足够数量的消息可用,它们可以阻塞一段时间。
作为优化,服务器被允许在消息集的末尾返回一个消息的一部分。客户端应处理这种情况。
有一点要注意的是,获取消息API需要指定消费的分区。现在的问题是如何让消费者知道消费哪个分区?特别地,作为一组消费者,如何使得每个消费者获取分区的一个子集,并且平衡这些分区。我们使用zookeeper动态地为Scala和Java客户端完成这个任务。这种方法的缺点是,它需要一个相当胖的客户端并且需要客户端与zookeeper连接。我们尚未创建一个Kafka接口(API),允许该功能被移动到在服务器端并被更方便地访问。一个简单的消费者的客户端可以通过配置指定访问的分区,但这样将不能在某些消费者失效后做到分区的动态重新分配。我们希望能在下一个主要版本解决这一空白。
Fetch Requests:
Fetch Request (Version: 0、1、2) => replica_id max_wait_time min_bytes [topics]
replica_id => INT32
max_wait_time => INT32
min_bytes => INT32
topics => topic [partitions]
topic => STRING
partitions => partition fetch_offset max_bytes
partition => INT32
fetch_offset => INT64
max_bytes => INT32
Fetch Request (Version: 3) => replica_id max_wait_time min_bytes max_bytes [topics]
replica_id => INT32
max_wait_time => INT32
min_bytes => INT32
max_bytes => INT32
topics => topic [partitions]
topic => STRING
partitions => partition fetch_offset max_bytes
partition => INT32
fetch_offset => INT64
max_bytes => INT32
Fetch Request (Version: 4、5、6) => replica_id max_wait_time min_bytes max_bytes isolation_level [topics]
replica_id => INT32
max_wait_time => INT32
min_bytes => INT32
max_bytes => INT32
isolation_level => INT8
topics => topic [partitions]
topic => STRING
partitions => partition fetch_offset max_bytes
partition => INT32
fetch_offset => INT64
max_bytes => INT32
Fetch Request (Version: 7) => replica_id max_wait_time min_bytes max_bytes isolation_level session_id epoch [topics] [forgotten_topics_data]
replica_id => INT32
max_wait_time => INT32
min_bytes => INT32
max_bytes => INT32
isolation_level => INT8
session_id => INT32
epoch => INT32
topics => topic [partitions]
topic => STRING
partitions => partition fetch_offset log_start_offset max_bytes
partition => INT32
fetch_offset => INT64
log_start_offset => INT64
max_bytes => INT32
forgotten_topics_data => topic [partitions]
topic => STRING
partitions => INT32
Fetch Request (Version: 8) => replica_id max_wait_time min_bytes max_bytes isolation_level session_id epoch [topics] [forgotten_topics_data]
replica_id => INT32
max_wait_time => INT32
min_bytes => INT32
max_bytes => INT32
isolation_level => INT8
session_id => INT32
epoch => INT32
topics => topic [partitions]
topic => STRING
partitions => partition fetch_offset log_start_offset max_bytes
partition => INT32
fetch_offset => INT64
log_start_offset => INT64
max_bytes => INT32
forgotten_topics_data => topic [partitions]
topic => STRING
partitions => INT32
域 | 描述 |
---|---|
replica_id | 副本ID的是发起这个请求的副本节点ID。普通消费者客户端应该始终将其指定为-1,因为他们没有节点ID。其他broker设置他们自己的节点ID。基于调试目的,以非代理身份模拟副本broker发出获取数据指令请求时,这个值填-2。 |
max_wait_time | 如果没有足够的数据可发送时,最大阻塞等待时间,以毫秒为单位。 |
min_bytes | 返回响应消息的最小字节数目,必须设置。如果客户端将此值设为0,服务器将会立即返回,但如果没有新的数据,服务端会返回一个空消息集。如果它被设置为1,则服务器将在至少一个分区收到一个字节的数据的情况下立即返回,或者等到超时时间达到。通过设置较高的值,结合超时设置,消费者可以在牺牲一点实时性能的情况下通过一次读取较大的字节的数据块从而提高的吞吐量(例如,设置MaxWaitTime至100毫秒,设置MinBytes为64K,将允许服务器累积数据达到64K前等待长达100ms再响应)。 |
TopicName | topic名称 |
Partition | 获取数据的Partition id |
fetch_offset | 获取数据的起始偏移量 |
max_bytes | 此分区返回消息集所能包含的最大字节数。这有助于限制响应消息的大小。 |
Fetch Response:
Fetch Response (Version: 0) => [responses]
responses => topic [partition_responses]
topic => STRING
partition_responses => partition_header record_set
partition_header => partition error_code high_watermark
partition => INT32
error_code => INT16
high_watermark => INT64
record_set => RECORDS
Fetch Response (Version: 1、2、3) => throttle_time_ms [responses]
throttle_time_ms => INT32
responses => topic [partition_responses]
topic => STRING
partition_responses => partition_header record_set
partition_header => partition error_code high_watermark
partition => INT32
error_code => INT16
high_watermark => INT64
record_set => RECORDS
Fetch Response (Version: 4) => throttle_time_ms [responses]
throttle_time_ms => INT32
responses => topic [partition_responses]
topic => STRING
partition_responses => partition_header record_set
partition_header => partition error_code high_watermark last_stable_offset [aborted_transactions]
partition => INT32
error_code => INT16
high_watermark => INT64
last_stable_offset => INT64
aborted_transactions => producer_id first_offset
producer_id => INT64
first_offset => INT64
record_set => RECORDS
Fetch Response (Version: 5、6、7、8) => throttle_time_ms [responses]
throttle_time_ms => INT32
responses => topic [partition_responses]
topic => STRING
partition_responses => partition_header record_set
partition_header => partition error_code high_watermark last_stable_offset log_start_offset [aborted_transactions]
partition => INT32
error_code => INT16
high_watermark => INT64
last_stable_offset => INT64
log_start_offset => INT64
aborted_transactions => producer_id first_offset
producer_id => INT64
first_offset => INT64
record_set => RECORDS
域 | 描述 |
---|---|
throttle_time_ms | 由于限额冲突而导致的时间延迟长度,以毫秒为单位。(如果没有违反限额条件,此值为0) |
topic | 返回消息所对应的Topic名称。 |
partition | 返回消息所对应的分区id。 |
highwaterMark | 此分区日志中最末尾的偏移量。此信息可被客户端用来确定后面还有多少条消息。 |
last_stable_offset | 分区的最后一个稳定偏移量(或LSO)。这是最后一个偏移量,以便确定此偏移之前的所有事务记录的状态(已中止或已提交) |
log_start_offset | 最早可用的偏移量。 |
producer_id | 与中止的事务关联的生产者ID |
first_offset | 中止事务中的第一个偏移量 |
ListOffsetsRequest:
ListOffsets Request (Version: 0) => replica_id [topics]
replica_id => INT32
topics => topic [partitions]
topic => STRING
partitions => partition timestamp max_num_offsets
partition => INT32
timestamp => INT64
max_num_offsets => INT32
ListOffsets Request (Version: 1) => replica_id [topics]
replica_id => INT32
topics => topic [partitions]
topic => STRING
partitions => partition timestamp
partition => INT32
timestamp => INT64
ListOffsets Request (Version: 2、3) => replica_id isolation_level [topics]
replica_id => INT32
isolation_level => INT8
topics => topic [partitions]
topic => STRING
partitions => partition timestamp
partition => INT32
timestamp => INT64
ListOffsets Request (Version: 4) => replica_id isolation_level [topics]
replica_id => INT32
isolation_level => INT8
topics => topic [partitions]
topic => STRING
partitions => partition current_leader_epoch timestamp
partition => INT32
current_leader_epoch => INT32
timestamp => INT64
Responses:
ListOffsets Response (Version: 0) => [responses]
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code [offsets']
partition => INT32
error_code => INT16
offsets' => INT64
ListOffsets Response (Version: 1) => [responses]
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code timestamp offset
partition => INT32
error_code => INT16
timestamp => INT64
offset => INT64
ListOffsets Response (Version: 2、3) => throttle_time_ms [responses]
throttle_time_ms => INT32
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code timestamp offset
partition => INT32
error_code => INT16
timestamp => INT64
offset => INT64
ListOffsets Response (Version: 4) => throttle_time_ms [responses]
throttle_time_ms => INT32
responses => topic [partition_responses]
topic => STRING
partition_responses => partition error_code timestamp offset leader_epoch
partition => INT32
error_code => INT16
timestamp => INT64
offset => INT64
leader_epoch => INT32
Metadata Requests
Metadata Request (Version: 0、1、2、3) => [topics]
topics => STRING
Metadata Request (Version: 4、5、6、7) => [topics] allow_auto_topic_creation
topics => STRING
allow_auto_topic_creation => BOOLEAN
Response:
Metadata Response (Version: 0) => [brokers] [topic_metadata]
brokers => node_id host port
node_id => INT32
host => STRING
port => INT32
topic_metadata => error_code topic [partition_metadata]
error_code => INT16
topic => STRING
partition_metadata => error_code partition leader [replicas] [isr]
error_code => INT16
partition => INT32
leader => INT32
replicas => INT32
isr => INT32
Metadata Response (Version: 1) => [brokers] controller_id [topic_metadata]
brokers => node_id host port rack
node_id => INT32
host => STRING
port => INT32
rack => NULLABLE_STRING
controller_id => INT32
topic_metadata => error_code topic is_internal [partition_metadata]
error_code => INT16
topic => STRING
is_internal => BOOLEAN
partition_metadata => error_code partition leader [replicas] [isr]
error_code => INT16
partition => INT32
leader => INT32
replicas => INT32
isr => INT32
Metadata Response (Version: 2) => [brokers] cluster_id controller_id [topic_metadata]
brokers => node_id host port rack
node_id => INT32
host => STRING
port => INT32
rack => NULLABLE_STRING
cluster_id => NULLABLE_STRING
controller_id => INT32
topic_metadata => error_code topic is_internal [partition_metadata]
error_code => INT16
topic => STRING
is_internal => BOOLEAN
partition_metadata => error_code partition leader [replicas] [isr]
error_code => INT16
partition => INT32
leader => INT32
replicas => INT32
isr => INT32
Metadata Response (Version: 3、4) => throttle_time_ms [brokers] cluster_id controller_id [topic_metadata]
throttle_time_ms => INT32
brokers => node_id host port rack
node_id => INT32
host => STRING
port => INT32
rack => NULLABLE_STRING
cluster_id => NULLABLE_STRING
controller_id => INT32
topic_metadata => error_code topic is_internal [partition_metadata]
error_code => INT16
topic => STRING
is_internal => BOOLEAN
partition_metadata => error_code partition leader [replicas] [isr]
error_code => INT16
partition => INT32
leader => INT32
replicas => INT32
isr => INT32
Metadata Response (Version: 5、6、7) => throttle_time_ms [brokers] cluster_id controller_id [topic_metadata]
throttle_time_ms => INT32
brokers => node_id host port rack
node_id => INT32
host => STRING
port => INT32
rack => NULLABLE_STRING
cluster_id => NULLABLE_STRING
controller_id => INT32
topic_metadata => error_code topic is_internal [partition_metadata]
error_code => INT16
topic => STRING
is_internal => BOOLEAN
partition_metadata => error_code partition leader [replicas] [isr] [offline_replicas]
error_code => INT16
partition => INT32
leader => INT32
replicas => INT32
isr => INT32
offline_replicas => INT32
LeaderAndlsr Request:
LeaderAndIsr Request (Version: 0) => controller_id controller_epoch [partition_states] [live_leaders]
controller_id => INT32
controller_epoch => INT32
partition_states => topic partition controller_epoch leader leader_epoch [isr] zk_version [replicas]
topic => STRING
partition => INT32
controller_epoch => INT32
leader => INT32
leader_epoch => INT32
isr => INT32
zk_version => INT32
replicas => INT32
live_leaders => id host port
id => INT32
host => STRING
port => INT32
LeaderAndIsr Request (Version: 1) => controller_id controller_epoch [partition_states] [live_leaders]
controller_id => INT32
controller_epoch => INT32
partition_states => topic partition controller_epoch leader leader_epoch [isr] zk_version [replicas] is_new
topic => STRING
partition => INT32
controller_epoch => INT32
leader => INT32
leader_epoch => INT32
isr => INT32
zk_version => INT32
replicas => INT32
is_new => BOOLEAN
live_leaders => id host port
id => INT32
host => STRING
port => INT32
response:
LeaderAndIsr Response (Version: 0) => error_code [partitions]
error_code => INT16
partitions => topic partition error_code
topic => STRING
partition => INT32
error_code => INT16
因工作需要只研究了这几种API,更多API信息参考http://kafka.apache.org/protocol.html#The_Messages_Metadata。