概念
什么是 Window
在流处理应用中,数据是连续不断的,因此我们不可能等到所有数据都到了才开始处理。当然我们可以每来一个消息就处理一次,但是有时我们需要做一些聚合类的处理,例如:在过去的1分钟内有多少用户点击了我们的网页。在这种情况下,我们必须定义一个窗口,用来收集最近一分钟内的数据,并对这个窗口内的数据进行计算。
Time Window
就如名字所说的,Time Window 是根据时间对数据流进行分组的。 Flink 提出了三种时间的概念,分别是event time(事件时间:事件发生时的时间),ingestion time(摄取时间:事件进入流处理系统的时间),processing time(处理时间:消息被计算处理的时间)
- Tumbling Time Window
场景:统计10s内生成的数据之和,时间为event time
- 数据对象 Javabean
@Data public class Element { public Integer value; public Long timestamp; public Element(){} public Element(Integer value, Long timestamp) { this.value = value; this.timestamp = timestamp; } @Override public String toString() { return "Element{" + "value=" + value + '}'; } }
- 生成数据的工具类
/** * 数据源--每秒生成一个对象Element(int number,long timestamp) */ public class ElementGeneratorSource implements SourceFunction<Element> { final Logger logger = LoggerFactory.getLogger(ElementGeneratorSource.class); volatile boolean isRunning = true; @Override public void run(SourceContext<Element> ctx) throws Exception { int counter = 1; // flink程序启动20秒后 long eventStartTime = System.currentTimeMillis() - 20000; // 使用上面的时间戳创建第一个事件 Element element = new Element(counter++, eventStartTime); while (isRunning) { logger.info("Produced Element with value {} and timestamp {}", element.value, printTime(element.timestamp)); ctx.collect(element); // 创建元素并分配具有随机性的时间戳,以便它们不与当前系统时钟时间相同 element = new Element(counter++, element.timestamp + ThreadLocalRandom.current().nextLong(1000, 6000)); Thread.sleep(1000); } } @Override public void cancel() { isRunning = false; } // 辅助函数以可读格式打印 epoch 时间 String printTime(long longValue) { return LocalDateTime.ofInstant(Instant.ofEpochMilli(longValue), ZoneId.systemDefault()).toString(); } }
- main方法,用时间本身的时间戳分组,每10s一个窗口,计算数据
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); // 设置为 EventTime,否则默认为 ProcessTime env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); DataStreamSource<Element> elementStream = env.addSource(new ElementGeneratorSource()); elementStream // 在定义窗口之前,需要告诉Flink如何获取它接收到的每个元素的时间戳和水印 .assignTimestampsAndWatermarks(WatermarkStrategy.<Element>forBoundedOutOfOrderness(Duration.ofSeconds(3)).withTimestampAssigner((data, time) -> data.getTimestamp())) .windowAll(TumblingEventTimeWindows.of(Time.seconds(10))) .process(new ProcessAllWindowFunction<Element, Object, TimeWindow>() { @Override public void process(Context context, Iterable<Element> elements, Collector<Object> out) throws Exception { TimeWindow window = context.window(); logger.info("窗口:【"+sdf.format(new Date(window.getStart()))+"-- "+sdf.format(new Date(window.getEnd()))+"】"); logger.info( "Computing sum for {}", elements ); int sum = 0; for(Element e : elements) { sum += e.value; } out.collect( sum ); } }) .print(); env.execute();
- 执行结果如下,计算窗口内的数据,每10s一个窗口
-
TumblingCountWindow
按照数量分组,如每5个对象 计算一下他们的和
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<Element> elementStream = env.addSource(new ElementGeneratorSource());
elementStream.countWindowAll(5)
.aggregate(new AggregateFunction<Element, Long, Long>() {
@Override
public Long createAccumulator() {
return 0L;
}
@Override
public Long add(Element value, Long accumulator) {
return value.value + accumulator;
}
@Override
public Long getResult(Long accumulator) {
return accumulator;
}
@Override
public Long merge(Long a, Long b) {
return a + b;
}
}).print();
env.execute();
运行结果如下,达到5个数据后就开始计算