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 。