Kafka資料每5分鐘同步到Hive

哥不是小蘿莉發表於2020-12-26

1.概述

最近有同學留言諮詢Kafka資料落地到Hive的一些問題,今天筆者將為大家來介紹一種除Flink流批一體以外的方式(流批一體下次再單獨寫一篇給大家分享)。

2.內容

首先,我們簡單來描述一下資料場景,比如有這樣一個資料場景,有一批實時流資料實時寫入Kafka,然後需要對Topic中的資料進行每隔5分鐘進行落地到Hive,進行每5分鐘分割槽儲存。流程圖如下所示:

 

 

 2.1 環境依賴

整個流程,需要依賴的元件有Kafka、Flink、Hadoop。由於Flink提交需要依賴Hadoop的計算資源和儲存資源,所以Hadoop的YARN和HDFS均需要啟動。各個元件版本如下:

元件 版本
Kafka 2.4.0
Flink 1.10.0
Hadoop 2.10.0

2.2 每分鐘落地HDFS實現

Flink消費Kafka叢集中的資料,需要依賴Flink包,依賴如下:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-filesystem_2.12</artifactId>
    <version>${flink.connector.version}</version>
 </dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka-0.11_2.12</artifactId>
    <version>${flink.kafka.version}</version>
 </dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-streaming-java_2.12</artifactId>
    <version>${flink.streaming.version}</version>
 </dependency>

編寫消費Topic的Flink程式碼,這裡不對Topic中的資料做邏輯處理,在後面統一交給MapReduce來做資料預處理,直接消費並儲存到HDFS上。程式碼如下:

public class Kafka2Hdfs {

    private static Logger LOG = LoggerFactory.getLogger(Kafka2Hdfs.class);

    public static void main(String[] args) {
        if (args.length != 3) {
            LOG.error("kafka(server01:9092), hdfs(hdfs://cluster01/data/), flink(parallelism=2) must be exist.");
            return;
        }
        String bootStrapServer = args[0];
        String hdfsPath = args[1];
        int parallelism = Integer.parseInt(args[2]);

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.enableCheckpointing(5000);
        env.setParallelism(parallelism);
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        DataStream<String> transction = env.addSource(new FlinkKafkaConsumer010<>("test_bll_data", new SimpleStringSchema(), configByKafkaServer(bootStrapServer)));

        // Storage into hdfs
        BucketingSink<String> sink = new BucketingSink<>(hdfsPath);

        sink.setBucketer(new JDateTimeBucketer<String>("HH-mm"));// 自定義儲存到HDFS上的檔名,用小時和分鐘來命名,方便後面算策略

        sink.setBatchSize(1024 * 1024 * 4); // this is 5MB
        sink.setBatchRolloverInterval(1000 * 30); // 30s producer a file into hdfs
        transction.addSink(sink);

        env.execute("Kafka2Hdfs");
    }

    private static Object configByKafkaServer(String bootStrapServer) {
        Properties props = new Properties();
        props.setProperty("bootstrap.servers", bootStrapServer);
        props.setProperty("group.id", "test_bll_group");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        return props;
    }

}

2.3 注意事項

  • 這裡我們把時間視窗設定小一些,每30s做一次檢查,如果該批次的時間視窗沒有資料過來,就生成一個檔案落地到HDFS上;
  • 另外,我們重寫了DateTimeBucketer為JDateTimeBucketer,邏輯並不複雜,在原有的方法上加一個年-月-日/時-分的檔案生成路徑,例如在HDFS上的生成路徑:xxxx/2020-12-26/00-00

2.4 資料預處理

這裡,我們需要對落地到HDFS上的檔案進行預處理,處理的邏輯是這樣的。比如,現在是2020-12-26 14:00,那麼我們需要將當天的13:55,13:56,13:57,13:58,13:59這最近5分鐘的資料處理到一起,並載入到Hive的最近5分鐘的一個分割槽裡面去。那麼,我們需要生成這樣一個邏輯策略集合,用HH-mm作為key,與之最近的5個檔案作為value,進行資料預處理合並。

實現程式碼如下:

public class DateRange {

    public static void main(String[] args) {
        for (int i = 0; i < 24; i++) {
            for (int j = 0; j < 60; j++) {
                if (j % 5 == 0) {
                    if (j < 10) {
                        if (i < 10) {
                            if (i == 0 && j == 0) {
                                System.out.println("0" + i + "-0" + j + "=>23-59,23-58,23-57,23-56,23-55");
                            } else {
                                if (j == 0) {
                                    String tmp = "";
                                    for (int k = 1; k <= 5; k++) {
                                        tmp += "0" + (i - 1) + "-" + (60 - k) + ",";
                                    }
                                    System.out.println("0" + i + "-0" + j + "=>" + tmp.substring(0, tmp.length() - 1));
                                } else {
                                    String tmp = "";
                                    for (int k = 1; k <= 5; k++) {
                                        if (j - k < 10) {
                                            tmp += "0" + i + "-0" + (j - k) + ",";
                                        } else {
                                            tmp += "0" + i + "-" + (j - k) + ",";
                                        }
                                    }
                                    System.out.println("0" + i + "-0" + j + "=>" + tmp.substring(0, tmp.length() - 1));
                                }
                            }
                        } else {
                            if (j == 0) {
                                String tmp = "";
                                for (int k = 1; k <= 5; k++) {
                                    if (i - 1 < 10) {
                                        tmp += "0" + (i - 1) + "-" + (60 - k) + ",";
                                    } else {
                                        tmp += (i - 1) + "-" + (60 - k) + ",";
                                    }
                                }
                                System.out.println(i + "-0" + j + "=>" + tmp.substring(0, tmp.length() - 1));
                            } else {
                                String tmp = "";
                                for (int k = 1; k <= 5; k++) {
                                    if (j - k < 10) {
                                        tmp += i + "-0" + (j - k) + ",";
                                    } else {
                                        tmp += i + "-" + (j - k) + ",";
                                    }
                                }
                                System.out.println(i + "-0" + j + "=>" + tmp.substring(0, tmp.length() - 1));
                            }
                        }
                    } else {
                        if (i < 10) {
                            String tmp = "";
                            for (int k = 1; k <= 5; k++) {
                                if (j - k < 10) {
                                    tmp += "0" + i + "-0" + (j - k) + ",";
                                } else {
                                    tmp += "0" + i + "-" + (j - k) + ",";
                                }
                            }
                            System.out.println("0" + i + "-" + j + "=>" + tmp.substring(0, tmp.length() - 1));
                        } else {
                            String tmp = "";
                            for (int k = 1; k <= 5; k++) {
                                if (j - 1 < 10) {
                                    tmp += i + "-0" + (j - k) + ",";
                                } else {
                                    tmp += i + "-" + (j - k) + ",";
                                }
                            }
                            System.out.println(i + "-" + j + "=>" + tmp.substring(0, tmp.length() - 1));
                        }
                    }
                }
            }
        }
    }

}

預覽結果如下:

 

 需要注意的是,如果發生了第二天00:00,那麼我們需要用到前一天的00-00=>23-59,23-58,23-57,23-56,23-55這5個檔案中的資料來做預處理。

2.5 資料載入

準備好資料後,我們可以使用Hive的load命令直接載入HDFS上預處理的檔案,把資料載入到對應的表中,實現命令如下:

load data inpath '/cluster01/hive/hfile/data/min/2020-12-26/14-05/' overwrite into table jketable partition(day='2020-12-26-14-05')

這裡,我們在執行命令時,可能檔案不存在會導致載入出錯。那我們在載入HDFS路徑之前,先判斷一下路徑是否存在。

實現指令碼如下所示:

hdfs dfs -ls /cluster01/hive/hfile/data/min/2020-12-26/14-05/ | wc -l > /tmp/hdfs_check_files.txt

hdfs_check_files=`cat /tmp/hdfs_check_files.txt`

# 判斷HDFS上檔案是否存在
if [ $hdfs_check_files -eq 0 ] then echo "Match file is null.Stop hive load script." else echo "Match file is exist.Start hive load script." hive -e "load data inpath '/cluster01/hive/hfile/data/min/2020-12-26/14-05/' overwrite into table jketable partition(day='2020-12-26-14-05')" fi

3.總結

整個流程為,先使用Flink消費儲存在Kafka中的資料,按照每分鐘進行儲存,然後將具體需要聚合的時間段進行策略生成,比如每5分鐘、10分鐘、15分鐘等等,可以在DateRange類中修改對應的策略邏輯。最後,再將預處理好的資料使用hive命令進行載入。整個過程,流程較多。如果我們使用Flink的流批一體特性,可以通過Flink直接建表,然後使用Flink消費Kafka中的資料後,直接分割槽落地到Hive表,這個就留到下次再給大家分享吧。

4.結束語

這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大資料探勘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點選購買連結購買博主的書進行學習,在此感謝大家的支援。關注下面公眾號,根據提示,可免費獲取書籍的教學視訊。

相關文章