Flink實戰—基於時間窗口定時輸出sink

Flink時間窗口運用

上一篇介紹了Flink定時讀取外部數據Flink 定時加載外部文件數據並廣播

這一篇將介紹Flink定時輸出到外部存儲介質,有兩種辦法實現,一種是同上一篇一樣,在RichXXXFunction中實現SinkFunction的方法,在其中open()方法中引入java的定時任務。

本文介紹另一種實現,基於Flink window窗口機制,將結果定時sink到外部文件。

需求:

經過flink清洗後的數據,要求每天sink一次數據到某文件中(該文件內容是json格式,需要進行追加,不屬於flink範疇不細講)。

實現:

1、數據清洗:

 DataStream<JSONObject> userSink = env.addSource(ssConsumer).map(new MapFunction<String, JSONObject>() {
//此處清洗數據,取實時數據中的某些字段
            public JSONObject map(String value) {
                JSONObject jsonObject = new JSONObject();
                JSONObject out = new JSONObject();
                try {
                    jsonObject = JSON.parseObject(value);
                    out.put(String.valueOf(jsonObject.get("subject_id")), jsonObject.get("subject_name"));
                } catch (Exception e) {
                    JSONObject json = new JSONObject();
                    jsonObject = json;
                    logger.error("This value parse has error:", e);
                }
                return out;
            }
        }).filter(new FilterFunction<JSONObject>() {
            @Override
//此處過濾非Json格式數據
            public boolean filter(JSONObject value) throws Exception {
                if ("error".equals(value.getOrDefault("error", ""))) {
                    return false;
                } else {
                    return true;
                }
            }
        });

2、自定義sink,流數據輸出到txt文件:

/**
 * @Author luran
 * @create 2020/4/13 18:36
 * @Desc
 */


/**
 * 繼承RichSinkFunction<String>類,其中List<JSONObject>爲source端傳到sink的數據類型,這個視Source端數據類型而定。
 */
public class MyRishSinkFileWriter extends RichSinkFunction<List<JSONObject>> implements SinkFunction<List<JSONObject>> {
    private static String path;

    /**
     * open方法在sink第一次啓動時調用,一般用於sink的初始化操作
     */
    @Override
    public void open(Configuration parameters) {
        try {
            super.open(parameters);
            path = PropertyReaderUtil.getStrValue("user.txt.path");
            File file = new File(path);
            if (!file.exists()) {
                file.createNewFile();
            }
        } catch (Exception e) {
            log.error("獲取user.txt路徑失敗:", e);
        }

    }

    /**
     * invoke方法是sink數據處理邏輯的方法,source端傳來的數據都在invoke方法中進行處理
     * 其中invoke方法中第一個參數類型與RichSinkFunctionList<JSONObject>中的泛型對應。第二個參數
     * 爲一些上下文信息
     */
    @Override
    public void invoke(List<JSONObject> v, Context context) {
        try {
            JSONObject json = null;
            String s = JsonFileReaderUtil.readJsonData(path);
            if (s.length() == 0) {
                json = new JSONObject();
            } else {
                json = JSONObject.parseObject(s);
            }
            for (JSONObject d : v) {
                json.putAll(d);
            }
            String value = json.toJSONString();
            File file = new File(path);

            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(value);
            fileWriter.flush();
            fileWriter.close();
        } catch (IOException e) {
            log.error("寫入user文件異常:", e);
        }
    }

    /**
     * close方法在sink結束時調用,一般用於資源的回收操作
     */
    @Override
    public void close() throws Exception {
        super.close();
    }
}

3、時間窗口的啓用:

//由於用了時間窗口,輸出肯定是List<>     
 DataStream<List<JSONObject>> dataBaseStream = userSink
                .windowAll(TumblingProcessingTimeWindows.of(Time.days(1)))
                .process(new ProcessAllWindowFunction<JSONObject, List<JSONObject>, TimeWindow>() {
                    @Override
                    public void process(Context context, Iterable<JSONObject> iterable, Collector<List<JSONObject>> collector) throws Exception {
                        logger.info("進入時間窗口:"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                        List<JSONObject> arrayList = new ArrayList<JSONObject>();
                        iterable.forEach(single -> {
                            arrayList.add(single);
                        });
                        if (arrayList.size() > 0) {
                            collector.collect(arrayList);
                        }
                    }
                });
        dataBaseStream.print("userStream");
        dataBaseStream.addSink(new MyRishSinkFileWriter());

測試:

方便測試,先將時間改爲每30秒執行,Time.seconds(30),循環發送kafka數據:

第1個時間窗口到達:Iterable中集合了這30秒接收的所有實時數據,統一處理

第2個時間窗口達到:

總結:

Flink是實時處理,window機制可以認爲是flink的批處理實現,因爲需要等待水位線對齊觸發timer。一般還會基於時間窗口做一些統計,如Flink按統計Kafka中每小時的數量並輸出到MySQL

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