1.Kafka的拓扑结构
一个典型的 Kafka 集群中包含若干 Producer,若干 broker(Kafka 支持水平扩展, 一般 broker 数量越多,分区越多,集群吞吐率越高),若干 Consumer Group,以及一个 Zookeeper 集群。Kafka 通过 Zookeeper 管理集群配置,选举 leader。Producer 使用 push 模式将消息发布到 broker,Consumer 使用 pull 模式从 broker 订阅并消费消息。
Producer 发布消息
producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 partition
中,属于顺序写磁盘。
producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。
- 指定了 partition,则直接使用;
- 未指定 partition 但指定 key,通过对 key 的 value 进行 hash 选出一个 partition
- partition 和 key 都未指定,使用轮询选出一个 partition 。
- 注意,每个分区里面是有序的,但是整个分区来说,数据的顺序就不一定了。
写数据流程
- producer 先从 zookeeper 的 “/brokers/…/state” 节点找到该 partition 的 leader
- producer 将消息发送给该 leader
- leader 将消息写入本地 log
- followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK
- leader 收到所有 ISR(in-sync replicas) 中的 replica 的 ACK 后向 producer 发送 ACK
- 简单来说,producer将数据传入leader,leader将数据写入自己以及副本,通过ACK机制得到响应,确认已经写完之后,向producer发送响应。直到数据都复制并且传输完。
Broker存储消息
(1)
Kafkalog的存储解析
Partition 中的每条 Message 由 offset 来表示它在这个 partition 中的偏移量,这个 offset 不是该 Message 在 partition 数据文件中的实际存储位置,而是逻辑上一个值,它唯一确定了
partition 中的一条 Message。因此,可以认为 offset 是 partition 中 Message 的 id。partition
中的每条 Message 包含了以下三个属性:
offset
MessageSize
data
其中 offset 为 long 型,MessageSize 为 int32,表示 data 有多大,data 为 message 的具体内容。
(2)数据文件的分段
Kafka 解决查询效率的手段之一是将数据文件分段,比如有 100 条 Message,它们的 offset 是从 0 到 99。假设将数据文件分成 5 段,第一段为 0-19,第二段为 20-39,以此类推,每段放在一个单独的数据文件里面,数据文件以该段中最小的 offset 命名。这样在查找指定 offset 的 Message 的时候,用二分查找就可以定位到该 Message 在哪个段中。
(3)为数据文件建立索引
数据文件分段使得可以在一个较小的数据文件中查找对应 offset 的 Message 了,但是这依然需要顺序扫描才能找到对应 offset 的 Message。为了进一步提高查找的效率,Kafka 为每个分段后的数据文件建立了索引文件,文件名与数据文件的名字是一样的,只是文件扩展名为.index。
索引文件中包含若干个索引条目,每个条目表示数据文件中一条 Message 的索引。索引包含两个部分,分别为相对 offset 和 position。
相对 offset:因为数据文件分段以后,每个数据文件的起始 offset 不为 0,相对 offset 表示这条 Message 相对于其所属数据文件中最小的 offset 的大小。举例,分段后的一个数据文件的 offset 是从 20 开始,那么 offset 为 25 的 Message 在 index 文件中的相对 offset 就是 25-20 = 5。存储相对 offset 可以减小索引文件占用的空间。
position,表示该条 Message 在数据文件中的绝对位置。只要打开文件并移动文件指针到这个 position 就可以读取对应的 Message 了。
index 文件中并没有为数据文件中的每条 Message 建立索引,而是采用了稀疏存储的方式, 每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引 文件保留在内存中。但缺点是没有建立索引的 Message 也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。
一句话,Kafka 的 Message 存储采用了分区(partition),分段(LogSegment)和稀疏索引这几个手段来达到了高效性。