目录
水位线
水位线的主要内容就是一个时间戳,表示数据流中的时间
一个水位线t 表示在当前数据流中,事件时间已经达到了时间戳t,这代表t之前的所有数据都到齐了,之后出现的数据流中不会出现 t0
Datastream.assignTimestampsAndWatermarks(
WatermarkStrategy
.<Event>forBoundedOutOfOrderness(Duration.ZERO) // 可容许的延迟时间 这里是0秒
.withTimestampAssigner((SerializableTimestampAssigner<T>) (x, l) -> x.time) // 这里定义要抽取的时间 作为数据流中的事件
);
窗口
我们知道在实时处理领域中数据是源源不断 没有边界的,我们把这称之为无界流的数据
而将flink中无界流的数据进行切分,切分为一个个有限数据的数据块,这就是所谓的窗口(windows)
窗口分配器
顾名思义,窗口分配器就是将数据划分到它应有的数据块(窗口)中,构建窗口函数前 我们都要先构建窗口分配器
滚动事件时间窗口
长度为5s的滚动窗口,每隔5s 对当前 5s 内的数据进行一次统计
// 调用window算子 传入窗口分配器的静态方法of 创建窗口分配器
Datastream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
滑动事件时间窗口
长度为10s 滑动步长为5s的滑动窗口,5s统计一次,每次统计当前10s内的数据
Datastream.window(SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(5)));
会话事件时间窗口
一个会话超过10s内没有数据,就对他们进行合并
Datastream.window(EventTimeSessionWindows.withGap(Time.seconds(10)));
窗口函数
对窗口中的有界数据进行计算的操作 叫做窗口函数
如ReduceFunction、AggregateFunction、ProcessWindowFunction 等
代码实例
废话不多说 下面我们通过代码进行练习:
主程序
统计5s内各用户对网站的访问情况
package com.flink.wc.myflink.window;
import com.flink.wc.ClickSource;
import com.flink.wc.Event;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows;
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import java.time.Duration;
public class WindowReduceExample {
public static void main(String[] args) {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<Event> stream = env.addSource(new ClickSource());
stream.print("stream");
// 定义水位线
// for Bounded Out Of Orderness 对于跳出秩序的边界
SingleOutputStreamOperator<Event> stream02 = stream.assignTimestampsAndWatermarks(
WatermarkStrategy
.<Event>forBoundedOutOfOrderness(Duration.ZERO) // 可容许的延迟时间 这里是0秒
.withTimestampAssigner((SerializableTimestampAssigner<Event>) (event, l) -> event.time) // 定义要抽取的时间 这里是event对象里面time属性
);
SingleOutputStreamOperator<Tuple2<String, Long>> stream03 =
stream02.map(x -> Tuple2.of(x.user, 1L)).returns(Types.TUPLE(Types.STRING, Types.LONG));
KeyedStream<Tuple2<String, Long>, String> stream04 =
stream03.keyBy(x -> x.f0);
// 窗口分配器
// TumblingEventTimeWindows 方法是 滚动Tumbling 事件时间EventTime 窗口Windows
WindowedStream<Tuple2<String, Long>, String, TimeWindow> windowedStream01 =
stream04.window(TumblingEventTimeWindows.of(Time.seconds(5)));
// SlidingEventTimeWindows 滑动 事件时间 窗口 长度为10s 滑动步长为5s————5s统计一次,每次统计10s内的数据
WindowedStream<Tuple2<String, Long>, String, TimeWindow> windowedStream02 =
stream04.window(SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(5)));
// EventTimeSessionWindows 会话 事件时间 窗口 一个会话超过10s内没有数据,就对他们进行合并 这里的一个会话是指一个user属性???10s内没出现相同的user属性,就把他们合并然后进行聚合
WindowedStream<Tuple2<String, Long>, String, TimeWindow> windowedStream03 =
stream04.window(EventTimeSessionWindows.withGap(Time.seconds(10)));
// 窗口函数
// 修改 windowedStream01 为 windowedStream02、 windowedStream03 观察不同窗口分配器的运行效果
// 这个reduce(x, y)中 x是累加后的数据 y是下一个数据
SingleOutputStreamOperator<Tuple2<String, Long>> resultStream =
windowedStream01.reduce((x, y) -> Tuple2.of(x.f0, x.f1 + y.f1)).returns(Types.TUPLE(Types.STRING, Types.LONG));
resultStream.print("stream06");
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Event类 用户访问网站的信息
user使用者、url用户访问的网址、time 用户的访问时间
作为数据流中的数据类型
package com.flink.wc;
public class Event {
public String user;
public String url;
public long time;
public Event() {
}
public Event(String user, String url, long time) {
this.user = user;
this.url = url;
this.time = time;
}
@Override
public String toString() {
return "Event{" +
"user='" + user + '\'' +
", url='" + url + '\'' +
", time=" + time +
'}';
}
}
自定义源算子
自动生成用户访问信息
package com.flink.wc;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import java.util.Calendar;
import java.util.Random;
public class ClickSource implements SourceFunction<Event> {
private Boolean running = true;
@Override
public void run(SourceContext<Event> ctx) throws Exception {
Random random = new Random();
String[] users = {"Mary","Alice","Bob","Cary"};
String[] urls = {"./home", "./cart","./fav", "./prod?id=1","./prod?id=2"};
while (running) {
ctx.collect(new Event(
users[random.nextInt(users.length)],
urls[random.nextInt(urls.length)],
Calendar.getInstance().getTimeInMillis() //getTimeInMillis 方法返回当前时间 作为 event对象的time属性
));
// 在这个循环中 每隔一秒 就collect(发送)一个数据
Thread.sleep(1000);
}
}
@Override
public void cancel() {
running = false;
}
}