Kafka Timestamp

Timestamp來龍去脈

在這裏插入圖片描述

Message Body

出於對日誌保存、日誌切分和Kafka Streaming的改進和優化,Kafka從0.10.0.0版本起,在消息內新增加了個timestamp字段;時間戳的類型有兩種:可以設定爲producer創建消息的時間(CreateTime),也可以設定爲該消息寫入Broker的時間(LogAppendTime)。默認爲CreateTime,可通過參數message.timestamp.type 實現Topic級別的類型更改,Broker級別的時間戳類型參數爲log.message.timestamp.type。

有關Kafka Message新增時間戳的相關細節,可詳見Kafka官方Doc KIP-32 - Add timestamps to Kafka message

Log Segment

在Kafka 0.10.1.0以前(不包含0.10.1.0),對於一個Topic而言,其Log Segment是由一個.log文件和一個.index文件組合而成,分別用來存儲具體的消息數據和對應的偏移量;如下圖所示:
在這裏插入圖片描述
從Kafka 0.10.1.0開始,對於日誌文件,新增一個.timeindex文件,即每個Segment分別由.log、.index和.timeindex這三個文件組成。
在這裏插入圖片描述
有關Log Segment 新增.timeindex相關細節,可詳見Kafka官方Doc KIP-33 - Add a time based log index

根據時間戳查找消息

在0.10.1.0以前,Kafka提供了通過指定Offset來消費消息(若不清楚如何實現,可參考Kafka實踐之Consumer)。講道理,在那時候這個功能其實是比較雞肋的,因爲通常每天Kafka的消息量都會比較大,假如需要獲取到前一天凌晨三點到五天之間產生的所有消息,維護人員壓根不知道這個時間段內Offset的範圍。當然這個問題從0.10.1.0以後將不復存在,接下來就來討論如何實現按照時間戳查找消息。

以Python API爲例,從0.10.1.0以後新增加了一個offsets_for_times方法(Java API對應的方法爲offsetsForTimes),可以通過給定時間戳獲取每一個Partition上大於等於該時間戳的最早的Offset值。這句話讀起來可能比較拗口,舉個例子:統計2018-12-30 17:00:00 到2018-12-30 20:00:00期間,Topic(topic_demo)的消息偏移量範圍

import time
from kafka import KafkaConsumer, TopicPartition


class GetOffsetWithTimestamp:
    def __init__(self, broker_list, topic):
        self.topic = topic
        self.consumer = KafkaConsumer(bootstrap_servers=broker_list)

    def get_offset_time_window(self, begin_time, end_time):
        partitions_structs = []

        for partition_id in self.consumer.partitions_for_topic(self.topic):
            partitions_structs.append(TopicPartition(self.topic, partition_id))

        begin_search = {}
        for partition in partitions_structs:
            begin_search[partition] = begin_time if isinstance(begin_time, int) else self.__str_to_timestamp(begin_time)
        begin_offset = self.consumer.offsets_for_times(begin_search)

        end_search = {}
        for partition in partitions_structs:
            end_search[partition] = end_time if isinstance(end_time, int) else self.__str_to_timestamp(end_time)
        end_offset = self.consumer.offsets_for_times(end_search)

        for topic_partition, offset_and_timestamp in begin_offset.items():
            b_offset = 'null' if offset_and_timestamp is None else offset_and_timestamp[0]
            e_offset = 'null' if end_offset[topic_partition] is None else end_offset[topic_partition][0]
            print('Between {0} and {1}, {2} offset range = [{3}, {4}]'.format(begin_time, end_time, topic_partition,
                                                                              b_offset, e_offset))

    @staticmethod
    def __str_to_timestamp(str_time, format_type='%Y-%m-%d %H:%M:%S'):
        time_array = time.strptime(str_time, format_type)
        return int(time.mktime(time_array)) * 1000


if __name__ == '__main__':
    broker_list = 'localhost:9092'
    topic = 'topic_demo'

    action = GetOffsetWithTimestamp(broker_list, topic)
    action.get_offset_time_window('2018-12-30 17:00:00', '2018-12-30 20:00:00')

對於offsets_for_times的返回結果是一個{TopicPartition: OffsetAndTimestamp},對於不同的TopicPartition(topic, partition),其對應的OffsetAndTimestamp(offset, timestamp)通常都會是不一樣的,如果找不到滿足給定時間的最早消息,則返回None。

若想實現消費指定時間範圍內所產生的消息,源碼詳見GitHub

Kafka.tools.GetOffsetShell

查看消息偏移量是平時使用和維護過程中一個比較常見的操作;Kafka提供了一個內置的工具腳本來滿足這方面的需求,代碼實現邏輯詳見GitHub,在這裏來聊一下Kafka提供的GetOffsetShell工具:可以通過下載Kafka相應版本的源碼,在core\src\main\scala\kafka\tools下查看該腳本的源碼。該命令的使用方法如下

bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list <address:port> --topic <string> --time <param>

broker-list、topic和time是三個必須參數,與此同時還可以指定partitions;如果不顯示的指定partition,則默認查看所有分區對應的offset信息;在這裏主要討論一下參數time:

time可以有三種值,分別爲-1,-2和一個時間戳;-1代表latest,-2代表earliest,這兩個是比較好理解的,可以分別執行一遍命令就能實現查看指定Topic的有效Offset的最大範圍。

需要注意:當time的值爲一個時間戳時,這裏返回的結果並不是上文中提及到的那種含義:即就是並不是返回各Partition內大於等於當前timestamp的最早那條消息的offset值,而是返回當前時間戳的消息所在的Segment中最早的那條消息的Offset值,說白了就是該時間戳的消息在那個Segment上, 就返回該Segment的文件名(把文件名前無效的零都去掉)
在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章