Flink学习笔记(二)——进入Flink

1,001 阅读5分钟

记录

 上一篇主要学习和记录了什么是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后台程序接受到数据并进行实时统计

  1. 首先定义好接受数据的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;
        }
    }
  1. 接下来是主题程序,创建设置运行环境及配置,监听客户端。
//获取运行环境
StreamExecutionEnvironment streamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
//设置使用eventTime,默认使用processTime,这里使用数据中的时间戳
streamExecutionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//设置并行度
streamExecutionEnvironment.setParallelism(1);
//设置socket端口号并读取输入数据,默认的分隔符是\n
DataStream<String> dataStream = streamExecutionEnvironment.socketTextStream("localhost", 1234);
  1. 这一步是接受原始数据再将其转换为我们预设的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));
      }
})
  1. 给数据加上水印(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);
}
  1. 设置统计的频率与条件与输出结果
//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

未完待续...