Flink流处理随笔(上)

Flink 基本处理流程(上)

目前对于我对flink的基本的一个流程的了解来看的话,对于Flink其实的流处理我们其实完整的步骤只需要大概四步。

1.对数据的读取

2.对数据的筛选

3.对数据的处理,也就是这个数据的处理

4.对数据的分类

其实在总结一下其实就两大块,数据读取,数据处理,只是在数据处理这一块我们又可以细分几大块

我们可以继续回到那个最经典的程序(当然这个并不是流处理模式,不过都是一样的)

package com.java;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.AggregateOperator;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.FlatMapOperator;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;

public class HelloOffline {
    
    
    public static void main(String[] args) throws Exception{
    
    

        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();//创建环境
        //读取数据逐行读取
        DataSource<String> data = env.readTextFile("F:\\FlinkDome1\\src\\main\\resources\\Hello.txt");
        //数据处理
        FlatMapOperator<String, Tuple2<String, Integer>> dataout = data.flatMap(new MyFunction());
        //数据聚类处理(分组,求和)
        AggregateOperator<Tuple2<String, Integer>> sum = dataout.groupBy(0).sum(1);
        //输出
        sum.print();

    }

    public static class MyFunction implements FlatMapFunction<String,Tuple2<String,Integer>> {
    
    

        @Override
        //如何对数据进行处理,这里是把数据返回成一个数组,输入String,输出Tuple2<String,Integer>
        public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
    
    
            String[] worlds = s.split(" ");
            for(String world:worlds){
    
    
                collector.collect(new Tuple2<>(world,1));//输出处理好的数据
            }
        }
    }
}

那么我们接下来就来我们按照流程来说一说这几大块的流程怎么玩。

也就是说对这几个模块部分经行细分。

数据读取

首先明确一点我们是基于流处理环境来进行的,也就是**StreamExecutionEnvironment**

直接读取文件

这个没什么好说的。

请添加图片描述

这里面的API看参数就知道怎么搞了。

从列表当中读取文件

请添加图片描述

这里主要使用fromCollection

现在做个小小的演示。

现在我们简单的搞一下,我们现在统计一下,学生的一些信息。

1.创建学生类

package com.java;

public class Student {
     
     
    public Student(){
     
     

    }

    public Student(String name, Integer age, double height) {
     
     
        this.name = name;
        this.age = age;
        this.height = height;
    }
    private String name;
    private Integer age;
    private double height;

    public String getName() {
     
     
        return name;
    }
    public void setName(String name) {
     
     
        this.name = name;
    }
    public Integer getAge() {
     
     
        return age;
    }
    public void setAge(Integer age) {
     
     
        this.age = age;
    }
    public double getHeight() {
     
     
        return height;
    }
    public void setHeight(double height) {
     
     
        this.height = height;
    }
    @Override
    public String toString() {
     
     
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}
//这里那啥重写一下那个toString方法,不然输出的是地址

2.我们现在这样做,我们先把数据写死,只是单纯的读取一下内容。

package com.java;

import org.apache.flink.api.java.tuple.Tuple;
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.environment.StreamExecutionEnvironment;
import java.util.Arrays;
public class CollectionClass
{
     
     
    public static void main(String[] args) throws Exception{
     
     
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Student> studentdata = env.fromCollection(Arrays.asList(
                new Student("小明", 20, 195),
                new Student("小李", 21, 192),
                new Student("小王", 22, 175),
                new Student("小夏", 20, 195)
                ));
        //我们来计算一下年龄相同的小伙子的总部身高。
        SingleOutputStreamOperator<Student> sum = studentdata.keyBy("age").sum("height");
        sum.print();
        env.execute();
    }
}

结果

请添加图片描述

补充

这里先补充一下那个keyby()方法和sum()方法,我这里是直接把student里面的属性名给他了,然后他自己通过一套机制,毕竟传过来的时候其实那个学生类的地址已经传过来,做个反序列化什么的是可以拿到值的。

那么也还可以换个写法,只是不方便。

SingleOutputStreamOperator<Student> sum = studentdata.keyBy(Student::getAge).sum("height");
//sum不能这样

当然除此之外还有个那个fromElements,这个看看就好
请添加图片描述
结果:

请添加图片描述

从socket读取网络数据

这个早就在先前的博客当中演示过了,这里就不做过多的二次描述了。

DataStreamSource<String> stringDataStreamSource = env.socketTextStream("localhost",7777);

从Kafka读取数据

这个惭愧,目前手头上没有东西演示不了,暂时也接触不到,但是搞这个还是简单的,也一样就两个步骤

1.添加Kafka依赖。

<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.11_2.12</artifactId>
<version>1.10.1</version>

这里说一下目前我当前项目的配置(按道理应该是没问题的,先前踩的坑不少了)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>org.example</groupId>
   <artifactId>FlinkDome1</artifactId>
   <version>1.0-SNAPSHOT</version>

   <properties>
       <maven.compiler.source>8</maven.compiler.source>
       <maven.compiler.target>8</maven.compiler.target>
   </properties>

   <dependencies>
       <dependency>
           <groupId>org.apache.flink</groupId>
           <artifactId>flink-streaming-java_2.12</artifactId>
           <version>1.10.1</version>
       </dependency>

       <dependency>
           <groupId>org.apache.flink</groupId>
           <artifactId>flink-java</artifactId>
           <version>1.10.1</version>
       </dependency>

       <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
           <version>1.7.21</version>
           <scope>test</scope>
       </dependency>

       <dependency>
           <groupId>log4j</groupId>
           <artifactId>log4j</artifactId>
           <version>1.2.17</version>
       </dependency>

       <dependency>
           <groupId> org.apache.cassandra</groupId>
           <artifactId>cassandra-all</artifactId>
           <version>0.8.1</version>

           <exclusions>
               <exclusion>
                   <groupId>org.slf4j</groupId>
                   <artifactId>slf4j-log4j12</artifactId>
               </exclusion>
               <exclusion>
                   <groupId>log4j</groupId>
                   <artifactId>log4j</artifactId>
               </exclusion>
           </exclusions>

       </dependency>

   </dependencies>

</project>

这个还是很重要的,我先前把平台迁移回windows就是出现这个问题了。然后注意Java版本,先前的是14的真的坑,强行换回8。

2.使用addSource()这个方法去实现一个kafka的接口,还是很简单的,就是换个数据源罢了,操作还是一样的。那么关于addSource()这个就是我们读取数据的重中之重了。

addSource自定义数据源

这个我们对刚才的的那个从列表读取数据做个处理,我们把相关的学生信息写到一个文件里面,然后我们返回Student()类

package com.java;

import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.io.BufferedReader;
import java.io.FileReader;

public class MyAddSource {
    
    
    public static void main(String[] args) throws  Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Student> studentDataStreamSource = env.addSource(new MySource());
        studentDataStreamSource.print();

        env.execute();

    }

    public static class MySource implements SourceFunction<Student> {
    
    
        @Override
        public void run(SourceContext<Student> sourceContext) throws Exception {
    
    
        //读取数据
            BufferedReader reader = new BufferedReader(new FileReader("F:\\FlinkDome1\\src\\main\\resources\\Student.txt"));
            String line = reader.readLine();
            while (line!=null){
    
    
                String[] args = line.split(" ");
                sourceContext.collect(new Student(args[0],new Integer(args[1]),new Double(args[2])));
                line = reader.readLine();
            }
            reader.close();
            return;
        }

        @Override
        public void cancel() {
    
    
        //停止的标准,我们这里自己会停下就没必要写了
        }
    }
}

请添加图片描述

结果

请添加图片描述

数据处理

map与flatmap的区别

这是个大头了,也是比较重要的。

这块我们先来说说单纯的数据来一个数据处理一个数据的方式。这里主要有两个一个是map 还有一个是flatmap()

至于怎么用首先flatmap就不再做介绍了,前面的例子太多了。那么我们先简单说说map()这个玩意,这玩意有什么特点,这里就做个对比,因为功能几乎一样,只是有些许差别。

先来看看map

请添加图片描述

再来看看flatmap()

```java`

public static class MyFunction implements FlatMapFunction<String,Tuple2<String,Integer>> {

    @Override
    public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
        String[] worlds = s.split(" ");
        for(String world:worlds){
            collector.collect(new Tuple2<>(world,1));
        }
    }
}

直接看到返回类型你就发现不一样

1.前者返回object换句话说,你基本上输入什么类型就输出一个object.

2.后者,我的输出是完全自定义的,在这里我定义了一个元组,此外这里有个叫采集器的玩意,也就是说,我的数据

在flatmap()里面其实可以做个’停留‘,而前者直接返回。

所以发现了么,前者返回数据return 那么返回了就结束了当前数据处理,

但是后者是使用采集器,换句话说,在当前我通过采集器发送出一个数据并不意味着我当前的这个数据处理完了!

也就是前者,一个数据对应一个数据(或者数据类型)的输出,但是后者可不是,我可以输出多个!

那么对应map还是举个例子:

package com.java;

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.io.BufferedReader;
import java.io.FileReader;

public class MyAddSource {
    
    
    public static void main(String[] args) throws  Exception {
    
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Student> studentDataStreamSource = env.addSource(new MySource());

        SingleOutputStreamOperator<Object> map = studentDataStreamSource.map(new MapFunction<Student, Object>() {
    
    
            @Override
            public Object map(Student student) throws Exception {
    
    
                return new Tuple2<Integer, Double>(student.getAge(), student.getHeight());
            }
        });
        map.print();
        env.execute();

    }

    public static class MySource implements SourceFunction<Student> {
    
    
        @Override
        public void run(SourceContext<Student> sourceContext) throws Exception {
    
    
        //读取数据,返回身高,年龄的元组
            BufferedReader reader = new BufferedReader(new FileReader("F:\\FlinkDome1\\src\\main\\resources\\Student.txt"));
            String line = reader.readLine();
            while (line!=null){
    
    
                String[] args = line.split(" ");
                sourceContext.collect(new Student(args[0],new Integer(args[1]),new Double(args[2])));
                line = reader.readLine();
            }
            reader.close();
            return;
        }

        @Override
        public void cancel() {
    
    
        //停止的标准,我们这里自己会停下就没必要写了
        }
    }
}

过滤

这个简单

请添加图片描述

我们过滤一下身高不到180的

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Student> studentDataStreamSource = env.addSource(new MySource());

        SingleOutputStreamOperator<Student> filter = studentDataStreamSource.filter(new FilterFunction<Student>() {
    
    
            @Override
            public boolean filter(Student student) throws Exception {
    
    
                boolean flag = true;
                if (student.getHeight() < 180) {
    
    
                    flag = false;
                }
                return flag;
            }
        });
        filter.print();

请添加图片描述

分组处理(滚动聚合)

这玩意听起来很流批,但是其实前面就演示了,一个是最先前的单词分组统计的,还有一个是刚刚的那个学生统计的。

这里主要说明两个方法,一个是groupby()还有一个是keyby()。这两个玩意,首先是前者原来的块处理的,还有一个是现在的流处理的,andthen nothing。

那么这里还是以Keyby()这个函数为主,这玩意首先也具备与groupby一样的功能。例如直接从元组里面分组,然后处理,通过下标索引。或者直接对某对象属性进行分组,也就是先前的学生的例子。

SingleOutputStreamOperator<Student> sum = studentdata.keyBy(Student::getAge).sum("height");
or
SingleOutputStreamOperator<Student> sum = studentdata.keyBy("age").sum("height");

这里额外说明一下的是,分组不等于分流,只是看起来像而已,最后还是在一个流输出的。这个你看那个后面连并行都单独设置不了。

reduce使用

这个操作怎么说呢,我的语言表达有限,以一个需求为例子,那就是我们分组以后,我们要对分组后的数据做处理,例如前面使用sum对分组后的数据进行统计。但是先前有个问题,那就是这玩意只是统计了一个变量,那就是身高,现在我也想要把年龄的加起来,和身高的一起。那么这个时候就好玩了,我们需要使用到这个玩意。作用就是自定义对分组后(聚合)后的数据处理。

直接看代码。

package com.java;

import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.java.tuple.Tuple;
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.environment.StreamExecutionEnvironment;

import java.util.Arrays;

public class CollectionClass
{
    
    
    public static void main(String[] args) throws Exception{
    
    

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//        env.setParallelism(3);
        DataStreamSource<Student> studentdata = env.fromCollection(Arrays.asList(
                new Student("小明", 20, 195),
                new Student("小李", 21, 192),
                new Student("小王", 22, 175),
                new Student("小夏", 20, 195)
                ));
        //我们来计算一下年龄相同的小伙子的总部身高和年龄的。
        SingleOutputStreamOperator<Student> age = studentdata.keyBy("age").reduce(new ReduceFunction<Student>() {
    
    
            @Override
            public Student reduce(Student student, Student t1) throws Exception {
    
    
                //student上一个数据,t1下一个数据。刚好一个流么,两个两个先处理,然后汇总。
                return new Student(student.getName(),(student.getAge()+t1.getAge()),(student.getHeight()+t1.getHeight()));
            }
        });
        SingleOutputStreamOperator<Student> sum = age;
        sum.print();
        env.execute("");
    }
}

这些代码都是在我的设备正常运行的,如果有问题即时留言。坑确实不少,
有时候昨天能happy的代码,第二天就不行了,然后改配置…

猜你喜欢

转载自blog.csdn.net/FUTEROX/article/details/120541733