最后
/** 用于输出窗口的结果 */
//IN, OUT, KEY, W extends Window
public static class WindowResultFunction implements WindowFunction<Long, OrderView, String, TimeWindow> {
@Override
public void apply(
String key, // 窗口的主键
TimeWindow window, // 窗口
Iterable aggregateResult, // 聚合函数的结果
Collector collector // 输出类型为 OrderView
) throws Exception {
Long count = aggregateResult.iterator().next();
collector.collect(OrderView.of(key, window.getEnd(), count));
}
}
public static class OrderView {
public String itemId; // 商品ID
public long windowEnd; // 窗口结束时间戳
public long allsum; // 商品的点击量
public static OrderView of(String itemId, long windowEnd, long allsum) {
OrderView result = new OrderView();
result.itemId = itemId;
result.windowEnd = windowEnd;
result.allsum = allsum;
return result;
}
@Override
public String toString() {
return “OrderView{” +
“itemId='” + itemId + ‘\’’ +
“, windowEnd=” + windowEnd +
“, viewCount=” + allsum +
‘}’;
}
}
经过上述的步骤,我们得到了一个经过聚合的时间窗口数据,接下来只需取再按时间分组并取到前五的数据就大功告成了。使用 ProcessFunction
实现一个自定义的 TopN 函数 TopNHot
来计算排名前5的商品,并将排名结果格式化成字符串,便于后续输出。
DataStream topNHots = windowedData
.keyBy(“windowEnd”)
.process(new TopNHot(5));
ProcessFunction
是 Flink 提供的一个 low-level API,用于实现更高级的功能。它主要提供了定时器 timer 的功能(支持EventTime或ProcessingTime)。本案例中我们将利用 timer 来判断何时收齐了某个 window 下所有商品的点击量数据。由于 Watermark 的进度是全局的,在 processElement
方法中,每当收到一条数据(OrderView
),我们就注册一个 windowEnd+1
的定时器(Flink 框架会自动忽略同一时间的重复注册)。windowEnd+1
的定时器被触发时,意味着收到了windowEnd+1
的 Watermark,即收齐了该windowEnd
下的所有商品窗口统计值。我们在 onTimer()
中处理将收集的所有商品及点击量进行排序,选出 TopN,并将排名信息格式化成字符串后进行输出。
这里我们还使用了 ListState<OrderView>
来存储收到的每条 OrderView消息,保证在发生故障时,状态数据的不丢失和一致性。ListState
是 Flink 提供的类似 Java List
接口的 State API,它集成了框架的 checkpoint 机制,自动做到了 exactly-once 的语义保证。
public static class TopNHot extends KeyedProcessFunction<Tuple, OrderView, String> {
private final int topSize;
public TopNHot(int topSize) {