记录
上一篇主要学习和记录了什么是Flink以及关于Flink的一些基础组件与架构,这一篇咋们主要是通过实际代码来更清楚的认识和了解Flink
准备
Flink的是由java编写的,所以当然不能缺少java的运行环境
- 需要jdk1.8及以上的环境
- maven的运行环境
简单的程序
用idea新建一个maven项目,取名随便,创建好自己的包,在包下面新建一个java文件,并同时在maven中引入Flink的依赖,这样就可以直接开始编程了,这里引入的是最近版本哦。
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-clients -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.12</artifactId>
<version>1.11.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-streaming-java -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.12</artifactId>
<version>1.11.1</version>
</dependency>
接下来就是编写main函数,这里准备写一个统计ip访问次数的简单逻辑,利用netCat工具发送数据模拟客户端,Flink后台程序接受到数据并进行实时统计
- 首先定义好接受数据的model类
//原始数据格式为:19:30:31 192.0.0.2
public class IpAndCount {
Date date;
String ip;
Long count;
public IpAndCount() {
}
public IpAndCount(Date date, String ip, Long count) {
this.date = date;
this.ip = ip;
this.count = count;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
}
- 接下来是主题程序,创建设置运行环境及配置,监听客户端。
//获取运行环境
StreamExecutionEnvironment streamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
//设置使用eventTime,默认使用processTime,这里使用数据中的时间戳
streamExecutionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//设置并行度
streamExecutionEnvironment.setParallelism(1);
//设置socket端口号并读取输入数据,默认的分隔符是\n
DataStream<String> dataStream = streamExecutionEnvironment.socketTextStream("localhost", 1234);
- 这一步是接受原始数据再将其转换为我们预设的model
//其中value就是从客户端接受到原始数据19:30:31 192.0.0.2
dataStream.flatMap(new FlatMapFunction<String, IpAndCount>() {
@Override
public void flatMap(String value, Collector<IpAndCount> collector) throws Exception {
String[] wordArray = value.split("\\s");
//将数据重新组装放入集合中 默认次数为1
collector.collect(new IpAndCount(new SimpleDateFormat("HH:mm:ss").parse(wordArray[0]), wordArray[1], 1L));
}
})
- 给数据加上水印(Watermarks)
//这里就是将原始数据中的date字段作为元素的水印并设置固定延迟时间为3秒
dataStream.assignTimestampsAndWatermarks(
WatermarkStrategy.<IpAndCount>forBoundedOutOfOrderness(Duration.ofSeconds(3)).withTimestampAssigner(new SerializableTimestampAssigner<IpAndCount>() {
@Override
public long extractTimestamp(IpAndCount ipAndCount, long l) {
return ipAndCount.getDate().getTime();
}
}))
PS:在flink 1.11之前的版本中,提供了两种生成水印(Watermark)的策略,分别是AssignerWithPunctuatedWatermarks和AssignerWithPeriodicWatermarks,这两个接口都继承自TimestampAssigner接口。如果想使用不同的水印生成方式,则需要实现不同的接口。
在flink 1.11 中对flink的水印生成接口进行了重构,当我们构建了一个DataStream之后,使用assignTimestampsAndWatermarks方法来构造水印,新的接口需要传入一个WatermarkStrategy对象。具体的水印生成策略都由WatermarkStrategy这个对象决定
//构造水印方法
DataStream#assignTimestampsAndWatermarks(WatermarkStrategy<T>);
/**
* Instantiates a WatermarkGenerator that generates watermarks according to this strategy.
*/
@Override
WatermarkGenerator<T> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context);
//水印生成策略对象WatermarkGenerator 实现类
@Public
public interface WatermarkGenerator<T> {
/**
* Called for every event, allows the watermark generator to examine and remember the
* event timestamps, or to emit a watermark based on the event itself.
* 每个元素都会调用这个方法,如果我们想依赖每个元素生成一个水印,然后发射到下游(可选,就是看是否用output来收集水印),我们可以实现这个方法.
*/
void onEvent(T event, long eventTimestamp, WatermarkOutput output);
/**
* Called periodically, and might emit a new watermark, or not.
*
* <p>The interval in which this method is called and Watermarks are generated
* depends on {@link ExecutionConfig#getAutoWatermarkInterval()}.
* onPeriodicEmit : 如果数据量比较大的时候,我们每条数据都生成一个水印的话,会影响性能,所以这里还有一个周期性生成水印的方法。这个水印的生成周期可以这样设置:env.getConfig().setAutoWatermarkInterval(5000L);
*/
void onPeriodicEmit(WatermarkOutput output);
}
- 设置统计的频率与条件与输出结果
//KeySelector -> 以数据中的ip字段统计key
dataStream.keyBy(new KeySelector<IpAndCount,String>() {
@Override
public String getKey(IpAndCount o) throws Exception {
return o.getIp();
}
})
//每5秒统计一次
.timeWindow(Time.seconds(5))
//将结果输出
.reduce(new MyReduceFunction(), new MyProcessWindows())
.print();
//将所有数据做聚合处理,这里是将所有相同ip的count求和
//增量聚合: 窗口不维护原始数据,只维护中间结果,每次基于中间结果和增量数据进行聚合。
static class MyReduceFunction implements ReduceFunction<IpAndCount> {
@Override
public IpAndCount reduce(IpAndCount ipAndCount, IpAndCount t1) throws Exception {
ipAndCount.setCount(ipAndCount.getCount() + t1.getCount());
return ipAndCount;
}
}
//ProcessWindowFunction一次性迭代整个窗口里的所有元素,这里是输出所有结果至控制台
//全量聚合: 窗口需要维护全部原始数据,窗口触发进行全量聚合。如:ProcessWindowFunction。
static class MyProcessWindows extends ProcessWindowFunction<IpAndCount, String, String, TimeWindow> {
@Override
public void process(String s, Context context, Iterable<IpAndCount> elements, Collector<String> out) throws Exception {
IpAndCount next = elements.iterator().next();
String buffer = "窗口范围是:" + new SimpleDateFormat("HH:mm:ss").format(context.window().getStart()) + "----" + new SimpleDateFormat("HH:mm:ss").format(context.window().getEnd()) + "\n" +
"时间:" + new SimpleDateFormat("HH:mm:ss").format(next.getDate()) + "\t" +
"ip:" + next.getIp() + "\t" +
"次数" + next.getCount() + "\t";
out.collect(buffer);
}
}
PS:Flink窗口
Flink支持基于时间窗口操作,也支持基于数据的窗口操作:
- 按分割标准划分:timeWindow、countWindow。
- 按窗口行为划分:Tumbling Window, Sliding Window、自定义窗口。
Flink常用窗口类型—时间和计数窗口:
- TimeWindow:时间窗口,按固定的时间划分的窗口。
- CountWindow:事件窗口,窗口是以数据驱动的,比如每经过100个元素,就把这100个元素归结到一个事件窗口当中。
Flink常用窗口类型—滚动窗口:
Tumbing Windows:滚动窗口,窗口之间时间点不重叠。它是按照固定的时间,或固定的事件个数划分的,分别可以叫做滚动时间窗口和滚动事件窗口。
Flink常用窗口类型—滑动窗口:
Sliding Windows:滑动窗口,窗口之间时间点存在重叠。对于某些应用,它们需要的时间是不间断的,需要平滑的进行窗口聚合。例如,可以每30s记算一次最近1分钟用户所购买的商品数量的总数,这个就是时间滑动窗口;或者每10个客户点击购买,然后就计算一下最近100个客户购买的商品的总和,这个就是事件滑动窗口。
Flink常用窗口类型—会话窗口:
Session Windows:会话窗口,经过一段设置时间无数据认为窗口完成。
最终运行结果
#数据源
orange$ nc -lk 1234
14:37:11 192.168.1.3
14:37:11 192.168.1.2
14:37:18 192.168.1.1
14:37:19 192.168.1.2
#结果
窗口范围是:14:37:10----14:37:15
时间:14:37:11 ip:192.168.1.3 次数1
窗口范围是:14:37:10----14:37:15
时间:14:37:11 ip:192.168.1.2 次数1