Flink常用 API 详解

Flink分层API

Flink 根据抽象程度分层,提供了三种不同的 API。每一种 API 在简洁性和表达力上有着不同的侧重,并且针对不同的应用场景。

  • ProcessFunction 是 Flink 所提供最底层接口。ProcessFunction 可以处理一或两条输入数据流中的单个事件或者归入一个特定窗口内的多个事件。它提供了对于时间和状态的细粒度控制。开发者可以在其中任意地修改状态,也能够注册定时器用以在未来的某一时刻触发回调函数。因此,你可以利用 ProcessFunction 实现许多有状态的事件驱动应用所需要的基于单个事件的复杂业务逻辑。
  • DataStream API 为许多通用的流处理操作提供了处理原语。这些操作包括窗口、逐条记录的转换操作,在处理事件时进行外部数据库查询等。DataStream API 支持 Java 和Scala 语言,预先定义了例如 map()、reduce()、aggregate() 等函数。你可以通过扩展实现预定义接口或使用 Java、Scala 的 lambda 表达式实现自定义的函数
  • SQL & Table API:Flink 支持两种关系型的 API,Table API 和 SQL。这两个 API 都是批处理和流处理统一的 API,这意味着在无边界的实时数据流和有边界的历史记录数据流上,关系型 API 会以相同的语义执行查询,并产生相同的结果。Table API 和 SQL借助了 Apache Calcite 来进行查询的解析,校验以及优化。它们可以与 DataStream 和DataSet API 无缝集成,并支持用户自定义的标量函数,聚合函数以及表值函数。
  • 扩展库
    • 复杂事件处理(CEP):模式检测是事件流处理中的一个非常常见的用例。Flink 的 CEP库提供了 API,使用户能够以例如正则表达式或状态机的方式指定事件模式。CEP 库与Flink 的 DataStream API 集成,以便在 DataStream 上评估模式。CEP 库的应用包括网络入侵检测,业务流程监控和欺诈检测。
    • DataSet API:DataSet API 是 Flink 用于批处理应用程序的核心 API。DataSet API 所提供的基础算子包括 map、reduce、(outer) join、co-group、iterate 等。所有算子都有相应的算法和数据结构支持,对内存中的序列化数据进行操作。如果数据大小超过预留内存,则过量数据将存储到磁盘。Flink 的 DataSet API 的数据处理算法借鉴了传统数据库算法的实现,例如混合散列连接(hybrid hash-join)和外部归并排序(external merge-sort)。
    • Gelly:Gelly 是一个可扩展的图形处理和分析库。Gelly 是在 DataSet API 之上实现的,并与 DataSet API 集成。因此,它能够受益于其可扩展且健壮的操作符。Gelly 提供了内置算法,如 label propagation、triangle enumeration 和 page rank 算法,也提供了一个简化自定义图算法实现的 Graph API。

Flink的编程模型

DataStream 的编程模型包括四个部分:Environment,DataSource,Transformation,Sink。

Flink的开发步骤

Flink使用 DataSet 和 DataStream 代表数据集。DateSet 用于批处理,代表数据是有限的;而 DataStream 用于流数据,代表数据是无界的。数据集中的数据是不可以变的,也就是说不能对其中的元素增加或删除。我们通过数据源创建 DataSet 或者 DataStream ,通过 map,filter 等转换(transform)操作对数据集进行操作产生新的数据集。

编写 Flink 程序一般经过一下几个步骤:

  • step1:准备环境-env
  • step2:准备数据-source
  • step3:处理数据-transformation
  • step4:输出结果-sink
  • step5:触发执行-execute

创建执行环境

Flink 提供了以下三种方式:

getExecutionEnvironment() //推荐使用
createLocalEnvironment()
createRemoteEnvironment(String host, int port, String... jarFiles)

我们以第一个为例创建 execution 环境,代码如下:

批处理:

ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
DataSet<String> text = env.readTextFile("file:///D:\\words.txt");
text.print();

流处理:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<String> text = env.readTextFile("file:///D:\\words.txt");
text.print();
env.execute();

words.txt:

a
b
c
d
e
a
b

上面代码创建了 execution 环境,同时利用 env 创建了输入源。在数据集上调用 print 方法可以将数据输出到控制台,当然也可以调用 writeAsText 等方法将数据输出到其他介质。上面流处理最后一行代码调用了 execute 方法,在流处理中需要显式调用该方法触发程序的执行

上述代码有两种方式运行,一种是直接在 IDE 中执行,就像运行一个普通的 Java 程序,Flink 将启动一个本地的环境执行程序。另一种方式是将程序打包,提交到 Flink 集群运行。上面例子基本包含了一个 Flink 程序的基本骨架,但是并没有对数据集进行更多的 transform 操作。

ExecutionEnvironment是与DataSet应用程序接口(已被软弃用,即将被弃用)一起使用的接口。StreamExecutionEnvironment是与DataStream API一起使用的接口。

后面我们主要讲解DataStream API

StreamExecutionEnvironment 的运行模式

由枚举RuntimeExecutionMode定义,主要有三个值:

  • STREAMING:默认的,有界数据和无界数据
  • BATCH:有界数据
  • AUTOMATIC:自动设置STREAMING或BATCH

当你处理的数据是有界的就应该使用BATCH执行模式,因为它更加高效。当你的数据是无界的, 则必须使用STREAMING 执行模式,因为只有这种模式才能处理持续的数据流。

批处理与流处理的区别:

  • 批处理处理数据,是一批一批对数据处理,可以理解成先对数据积压,然后达到一定量再一块处理。
  • 流处理,有数据就处理,不需要积压数据
  • 批处理无需保留数据状态,处理完就输出。
  • 流处理需要保留数据状态,因为也有可能还有该数据。
  • 批处理完成,程序就停止。
  • 流处理,需要一直等待,即使后面不会有数据产生,程序依然保存运行状态。

有界与无界的理解:

有界流与无界流的区别在于读取的数据是否有尽头,若读取的数据类似于文件(知道开始的位置,结束的位置),无界流就是知道开始但不知道什么时候结束,如网络,Kafka,需要不同的监听着,等待处理数据。

StreamExecutionEnvironment API

StreamExecutionEnvironment内部的方法非常多,我们从整体功能上进行简单划分,主要包括:创建执行环境、设置执行环境、自定义配置、数据源输入、执行计算、计算设置以及各类查询操作等。

创建执行环境

static LocalStreamEnvironment createLocalEnvironment()
static LocalStreamEnvironment createLocalEnvironment(Configuration configuration)
static LocalStreamEnvironment createLocalEnvironment(int parallelism)
static LocalStreamEnvironment createLocalEnvironment(int parallelism, Configuration configuration)
static StreamExecutionEnvironment createLocalEnvironmentWithWebUI(Configuration conf)
static StreamExecutionEnvironment createRemoteEnvironment(String host, int port, Configuration clientConfig, String… jarFiles)
static StreamExecutionEnvironment createRemoteEnvironment(String host, int port, int parallelism, String… jarFiles)
static StreamExecutionEnvironment createRemoteEnvironment(String host, int port, String… jarFiles)

执行环境设置操作

StreamExecutionEnvironment	disableOperatorChaining()
StreamExecutionEnvironment	enableCheckpointing()	已过期 
StreamExecutionEnvironment	enableCheckpointing(long interval)
StreamExecutionEnvironment	enableCheckpointing(long interval, CheckpointingMode mode)
StreamExecutionEnvironment	enableCheckpointing(long interval, CheckpointingMode mode, boolean force)	已过期 
protected static void	resetContextEnvironment() 
StreamExecutionEnvironment	setBufferTimeout(long timeoutMillis)
static void	setDefaultLocalParallelism(int parallelism)
StreamExecutionEnvironment	setDefaultSavepointDirectory(Path savepointDirectory)
StreamExecutionEnvironment	setDefaultSavepointDirectory(String savepointDirectory)
StreamExecutionEnvironment	setDefaultSavepointDirectory(URI savepointDirectory)
StreamExecutionEnvironment	setMaxParallelism(int maxParallelism)
void	setNumberOfExecutionRetries(int numberOfExecutionRetries)	已过期 
StreamExecutionEnvironment	setParallelism(int parallelism)
void	setRestartStrategy(RestartStrategies.RestartStrategyConfiguration restartStrategyConfiguration)
StreamExecutionEnvironment	setRuntimeMode(RuntimeExecutionMode executionMode)
StreamExecutionEnvironment	setStateBackend(StateBackend backend)
void	setStreamTimeCharacteristic(TimeCharacteristic characteristic)	已过期 

配置

void configure(ReadableConfig configuration)
void configure(ReadableConfig configuration, ClassLoader classLoader)
protected static void initializeContextEnvironment(StreamExecutionEnvironmentFactory ctx)

数据输入

DataStreamSource addSource(SourceFunction function)
DataStreamSource addSource(SourceFunction function, String sourceName)
DataStreamSource addSource(SourceFunction function, String sourceName, TypeInformation typeInfo)
DataStreamSource addSource(SourceFunction function, TypeInformation typeInfo)
    
DataStreamSource createInput(InputFormat inputFormat)
DataStreamSource createInput(InputFormat inputFormat, TypeInformation typeInfo)
    
DataStreamSource fromCollection(Collection data)
DataStreamSource fromCollection(Collection data, TypeInformation typeInfo)
DataStreamSource fromCollection(Iterator data, Class type)
DataStreamSource fromCollection(Iterator data, TypeInformation typeInfo)
DataStreamSource fromElements(Class type, OUT… data)
DataStreamSource fromElements(OUT… data)
DataStreamSource fromParallelCollection(SplittableIterator iterator, Class type)
DataStreamSource fromParallelCollection(SplittableIterator iterator, TypeInformation typeInfo)
DataStreamSource fromSequence(long from, long to)
DataStreamSource fromSource(Source source, WatermarkStrategy timestampsAndWatermarks, String sourceName)
DataStreamSource fromSource(Source source, WatermarkStrategy timestampsAndWatermarks, String sourceName, TypeInformation typeInfo)
DataStreamSource generateSequence(long from, long to)
    
DataStreamSource readFile(FileInputFormat inputFormat, String filePath)
DataStreamSource readFile(FileInputFormat inputFormat, String filePath, FileProcessingMode watchType, long interval)
DataStreamSource readFile(FileInputFormat inputFormat, String filePath, FileProcessingMode watchType, long interval, FilePathFilter filter)
DataStreamSource readFile(FileInputFormat inputFormat, String filePath, FileProcessingMode watchType, long interval, TypeInformation typeInformation)
DataStream readFileStream(String filePath, long intervalMillis, FileMonitoringFunction.WatchType watchType)
DataStreamSource readTextFile(String filePath)
DataStreamSource readTextFile(String filePath, String charsetName)
    
DataStreamSource socketTextStream(String hostname, int port)
DataStreamSource socketTextStream(String hostname, int port, char delimiter)
DataStreamSource socketTextStream(String hostname, int port, char delimiter, long maxRetry) Deprecated.
DataStreamSource socketTextStream(String hostname, int port, String delimiter)
DataStreamSource socketTextStream(String hostname, int port, String delimiter, long maxRetry)    

执行计算 

JobExecutionResult execute()
JobExecutionResult execute(StreamGraph streamGraph)
JobExecutionResult execute(String jobName)
JobClient executeAsync()
JobClient executeAsync(StreamGraph streamGraph)
JobClient executeAsync(String jobName)

执行设置

void registerCachedFile(String filePath, String name)
void registerCachedFile(String filePath, String name, boolean executable)
void registerJobListener(JobListener jobListener)
StreamExecutionEnvironment registerSlotSharingGroup(SlotSharingGroup slotSharingGroup)
void registerType(Class type)
void registerTypeWithKryoSerializer(Class type, Class serializerClass)
void registerTypeWithKryoSerializer(Class type, T serializer)
void addOperator(Transformation transformation)
F clean(F f)
void clearJobListeners()
StreamGraph generateStreamGraph(List> transformations)

查询相关

long getBufferTimeout()
List> getCachedFiles()
CheckpointConfig getCheckpointConfig()
CheckpointingMode getCheckpointingMode()
long getCheckpointInterval()
ExecutionConfig getConfig()
ReadableConfig getConfiguration()
static int getDefaultLocalParallelism()
Path getDefaultSavepointDirectory()
static StreamExecutionEnvironment getExecutionEnvironment()
static StreamExecutionEnvironment getExecutionEnvironment(Configuration configuration)
String getExecutionPlan()
List getJobListeners()
int getMaxParallelism()
int getNumberOfExecutionRetries()
int getParallelism()
StateBackend getStateBackend()
StreamGraph getStreamGraph()
StreamGraph getStreamGraph(boolean clearTransformations)
TimeCharacteristic getStreamTimeCharacteristic()
protected ClassLoader getUserClassloader()
boolean isChainingEnabled()
boolean isForceCheckpointing()
boolean isForceUnalignedCheckpoints()
boolean isUnalignedCheckpointsEnabled()

其他操作

void addDefaultKryoSerializer(Class type, Class> serializerClass)
void addDefaultKryoSerializer(Class type, T serializer)

DataStream数据源DataSource

Flink 使用 StreamExecutionEnvironment.getExecutionEnvironment创建流处理的执行环境

Flink 使用 StreamExecutionEnvironment.addSource(source) 来为你的程序添加数据来源。

Flink 已经提供了若干实现好了的 source functions,当然你也可以通过实现SourceFunction来自定义非并行的source,或者实现ParallelSourceFunction 接口或者扩展RichParallelSourceFunction来自定义并行的 source。

Flink在流处理上的source和在批处理上的source基本一致。大致有这几类:

  1. 基于本地集合的 source---有界流
  2. 基于文件的 source----有界流
  3. 基于网络套接字的 source---无界流
  4. 自定义的source --- 较难,用得比较多

自定义的 source 常见的有 Apache kafka、Amazon Kinesis Streams、RabbitMQ、Twitter Streaming API、Apache NiFi 等,当然你也可以定义自己的 source。

前面三类原理一样,底层都是Java代码实现的

集合source

/**
 * 集合source
 */
public class StreamingDemoFromCollectionSource {

    public static void main(String[] args) throws Exception {
        //1. 创建env
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //2. 数据源source
        //2.1 用element创建DataStream(fromElements)
        DataStreamSource<String> elemSource = env.fromElements("spark", "flink");

        //2.2 用Tuple创建DataStream(fromElements)
        DataStreamSource<Tuple2<Integer, String>> tupleSource = env.fromElements(Tuple2.of(1, "spark"), Tuple2.of(2, "flink"));

        //2.3 用List创建DataStream
        DataStreamSource<String> listSource = env.fromCollection(Arrays.asList("spark", "flink"));
        //如果不设置并行度的话,这里会跟机器的核数一致
        int parallelism = listSource.getParallelism();
        System.out.println("listSource并行度:" + parallelism);

        //2.4 fromSequence, 1~20的数字
        DataStreamSource<Long> sequenceSource = env.fromSequence(1, 20);


        //3. transformation
        //4. sink
        elemSource.print("elemSource");
        tupleSource.print("tupleSource");
        listSource.print("listSource");
        sequenceSource.print("sequenceSource");

        //5. execute
        //env.execute();
        env.execute(StreamingDemoFromCollectionSource.class.getName()); //jobName
    }
}

文件source

/**
 * 文件source
 */
public class Demo2FileSource {

    public static void main(String[] args) throws Exception {
        //1. 创建flink环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //2. source
        //读取文件创建DS----有界流
        DataStreamSource<String> fileSource = env.readTextFile("D:\\student.txt");

        //3.transformation
        //4. sink
        fileSource.print();
        //5. execute
        env.execute();

    }
}

student.txt:

1000,张三,文科六班
1001,李四,文科三班
1002,王五,理科一班

Socket source

使用nc

1)在linux服务器上使用yum安装,执行命令

yum -y install nc

2)启动nc,监听8888端口

nc -lp 8888

3)连接nc

/**
 * socket source 接收socket
 */
public class SocketSource {

    public static void main(String[] args) throws Exception {
        // flink 流执行环境
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //设置模式 STREAMING
        env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
        //数据源,socket,连接上nc服务端 ip、port,数据的分隔符是","
        env.socketTextStream("172.16.10.159", 8888, ",")
                //扁平化
                .flatMap(new FlatMapFunction<String, String>() {
                    @Override
                    public void flatMap(String value, Collector<String> out) throws Exception {
                        Arrays.stream(value.split(",")).forEach(v -> out.collect(v));
                    }
                })
                //映射
                .map(new MapFunction<String, Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> map(String value) throws Exception {
                        return Tuple2.of(value, 1);
                    }
                })
                //分组
                .keyBy((KeySelector<Tuple2<String, Integer>, String>) value -> value.f0)
                //求和
                .sum(1)
                //打印结果
                .print();
        //开始执行
        env.execute("flink streaming from socket");
    }
}

4)在nc命令行上输入参数:

[root@flink-node01 ~]# nc -lp 8888
java,python,c++,
java,python,c#,
java,python,redis,

5)客户端输出结果

3> (c++,1)
2> (java,1)
3> (python,1)
1> (java,1)
3> (python,2)
5> (c#,1)
1> (redis,1)
3> (python,3)
1> (java,2)

使用ServerSocket

1)启动 ServerSocket ,等待客户端连接上后,每 2s 发送一次消息

public class SocketServer {
    public static void main(String[] args) throws IOException {

        try {
            ServerSocket ss = new ServerSocket(8888);
            System.out.println("启动 server ....");
            Socket s = ss.accept();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            String response = "java,python,c++,";

            //每 2s 发送一次消息
            while(true){
                Thread.sleep(2000);
                bw.write(response);
                bw.flush();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2)连接ServerSocket

public class SocketSource {

    public static void main(String[] args) throws Exception {
        // flink 流执行环境
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //设置模式 STREAMING
        env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
        //数据源,连接 ServerSocket
        env.socketTextStream("127.0.0.1", 8888, ",")
                //扁平化
                .flatMap(new FlatMapFunction<String, String>() {
                    @Override
                    public void flatMap(String value, Collector<String> out) throws Exception {
                        Arrays.stream(value.split(",")).forEach(v -> out.collect(v));
                    }
                })
                //映射
                .map(new MapFunction<String, Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> map(String value) throws Exception {
                        return Tuple2.of(value, 1);
                    }
                })
                //分组
                .keyBy((KeySelector<Tuple2<String, Integer>, String>) value -> value.f0)
                //求和
                .sum(1)
                //打印结果
                .print();
        //开始执行
        env.execute("flink streaming from socket");
    }
}

输出结果:

2> (java,1)
3> (python,1)
3> (c++,1)
2> (java,2)
3> (c++,2)
3> (python,2)
2> (java,3)
3> (c++,3)
3> (python,3)

自定义的source

读取自定义的source创建DS :env.addSource()

参数需要传入一个SourceFunction,但SourceFunction是一个接口,所以需要传入这个接口的子类,因此需要创建一个子类(一般实现SourceFunction或者它的子类RichSourceFunction)来实现这个接口。

子类中需要重写这个接口的两个方法run()、cancle()。

自定义一个Source----无界流

public class Demo4SourceFunction {

    public static void main(String[] args) throws Exception {
        //1. 创建flink环境
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2. source
        //读取自定义的source创建DS(参数传入SourceFunction的子类对象)
        DataStreamSource<Integer> definedSource = env.addSource(new MySource());

        //3.transformation
        //4. sink
        definedSource.print();
        //5. execute
        env.execute();
    }

    //创建一个类实现接口
    static class MySource implements SourceFunction<Integer> {
        /**
         * run方法:用于生成数据的方法,只执行一次
         * sourceContext:用于将数据发送到下游的对象
         */
        @Override
        public void run(SourceContext<Integer> sourceContext) throws Exception {
            for (int i = 1; i < 10; i++) {
                sourceContext.collect(i);
            }
        }

        /**
         * 任务被取消的时候执行,一般用于回收资源
         */
        @Override
        public void cancel() {

        }
    }
}

自定义一个Source----无界流

在子类实现的run()方法中加入一个循环即可。

public class Demo5SourceFunction {

    public static void main(String[] args) throws Exception {
        //1. 创建flink环境
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2. source
        //读取自定义的source创建DS(参数传入SourceFunction的子类对象)
        DataStreamSource<Integer> definedSource = env.addSource(new Demo4SourceFunction.MySource());

        //3.transformation
        //4. sink
        definedSource.print();
        //5. execute
        env.execute();
    }

    //创建一个类实现接口
    static class MySource implements SourceFunction<Integer> {
        /**
         * run方法:用于生成数据的方法,只执行一次
         * sourceContext:用于将数据发送到下游的对象
         */
        @Override
        public void run(SourceContext<Integer> sourceContext) throws Exception {
            int i = 0;
            while (true) {
                i += 1;
                sourceContext.collect(i);//将数据发送到下游
                Thread.sleep(100);//设置一个100ms的循环时间,让其打印的慢一点
            }
        }

        /**
         * 任务被取消的时候执行,一般用于回收资源
         */
        @Override
        public void cancel() {

        }
    }
}

Kafka作为数据源

添加依赖:

<!--flink kafka-->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-kafka</artifactId>
    <version>1.15.0</version>
</dependency>
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-base</artifactId>
    <version>1.15.0</version>
</dependency>

<!--fastjson-->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.4</version>
</dependency>

生产者KafkaProducer

/**
 * KafkaProducer
 */
public class KafkaProducer {

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

        //1.env
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //2.Source
        DataStreamSource<Student> studentDS = env.fromElements(new Student(10, "tonyma", 18));

        //3.Transformation
        //注意:目前来说我们使用Kafka使用的序列化和反序列化都是直接使用最简单的字符串,所以先将Student转为字符串
        //可以直接调用Student的toString,也可以转为JSON
        SingleOutputStreamOperator<String> jsonDS = studentDS.map(new MapFunction<Student, String>() {
            @Override
            public String map(Student value) throws Exception {
                //String str = value.toString();
                String jsonStr = JSON.toJSONString(value);
                return jsonStr;
            }
        });

        //4.Sink
        jsonDS.print();
        //根据参数创建KafkaSink
        KafkaSink<String> sink = KafkaSink.<String>builder()
                .setBootstrapServers("localhost:9092")
                .setRecordSerializer(KafkaRecordSerializationSchema.builder()
                        .setTopic("flink_kafka") //topic name
                        .setValueSerializationSchema(new SimpleStringSchema())
                        .build()
                )
                .setDeliverGuarantee(DeliveryGuarantee.AT_LEAST_ONCE)
                .build();

        jsonDS.sinkTo(sink);

        //5.execute
        env.execute();

        // /export/server/kafka/bin/kafka-console-consumer.sh --bootstrap-server node1:9092 --topic flink_kafka
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Student {
        private Integer id;
        private String name;
        private Integer age;
    }
}

消费者KafkaConsumer

/**
 * KafkaConsumer
 */
public class KafkaConsumer {

    public static void main(String[] args) throws Exception {
        //1.env
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //2.Source
        Properties props = new Properties();
        props.setProperty("enable.auto.commit", "true");
        props.setProperty("auto.commit.interval.ms", "2000");
        //kafkaSource就是KafkaConsumer
        KafkaSource<String> kafkaSource = KafkaSource.<String>builder()
                .setBootstrapServers("localhost:9092")
                .setTopics("flink_kafka")
                .setGroupId("my-group")
                .setProperties(props)
                .setStartingOffsets(OffsetsInitializer.earliest())
                .setValueOnlyDeserializer(new SimpleStringSchema())
                .build();

        DataStreamSource<String> kafkaDS = env.fromSource(kafkaSource, WatermarkStrategy.noWatermarks(), "Kafka Source");

        //3.Transformation
        //3.1切割并记为1
        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOneDS = kafkaDS.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
                Student student = JSON.parseObject(s, Student.class);
                collector.collect(Tuple2.of(student.getName(), 1));
            }
        });
        //3.2分组
        KeyedStream<Tuple2<String, Integer>, String> groupedDS = wordAndOneDS.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
            @Override
            public String getKey(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
                return stringIntegerTuple2.f0;
            }
        });
        //3.3聚合
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = groupedDS.sum(1);

        //4.Sink
        result.print();

        //5.execute
        env.execute();
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Student {
        private Integer id;
        private String name;
        private Integer age;
    }
}

MySQL作为数据源

添加依赖:

<!-- mysql 相关 -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-connector-jdbc</artifactId>
    <version>1.15.0</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>

<!-- lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    <scope>provided</scope>
</dependency>

示例代码:

public class Demo5MysqlSource {


    public static void main(String[] args) throws Exception {
        //1.env
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2.source
        DataStreamSource<Student> dbSource = env.addSource(new MysqlSource());

        //3.transformation
        //4. sink
        dbSource.print();
        //5. execute
        env.execute();

    }


    static class MysqlSource implements SourceFunction<Student> {

        @Override
        public void run(SourceContext<Student> sourceContext) throws Exception {
            //使用JDBC读取Mysql
            Class.forName("com.mysql.jdbc.Driver");
            //建立连接
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&charcterEncoding=UTF-8&useSSL=false", "root", "root");
            //查询数据
            PreparedStatement pst = conn.prepareStatement("select * from t_student");
            //执行查询
            ResultSet rs = pst.executeQuery();
            //循环解析数据
            while (rs.next()) {
                Integer id = rs.getInt("id");
                String name = rs.getString("name");
                Integer age = rs.getInt("age");
                //将数据发送到下游
                sourceContext.collect(new Student(id, name, age));
            }
            //关闭连接
            conn.close();
        }

        @Override
        public void cancel() {

        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    static class Student {
        private Integer id;
        private String name;
        private Integer age;
    }
}

自定义的source的其他接口

在自定义的Source除了有SourceFunction接口,还有RichSourceFunction接口、ParallelSourceFunction接口、RichParallelSourceFunction接口

SourceFunction是自定义source中最基础的,是一个单线程的source
RichSourceFunction比SourceFunction多了open()和close()方法,也是一个单线程的source

ParallelSourceFunction接口,多并行的source
RichParallelSourceFunction接口,多并行的source

DataStream的Transformation

Map

  • 调用用户定义的MapFunction对DataStream数据进行处理,形成新的DataStream
  • 其中数据格式可能会发生变化,常用作对数据集内数据的清洗和转换。

示例代码1:

public class Demo1Operator {

    public static void main(String[] args) throws Exception {
        //1.环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2.source
        DataStreamSource<String> dataSource = env.fromElements("a", "b", "a", "d", "d");
        //3.数据处理/转换
        //MapFunction中的第一个参数是输入类型,即DataStreamSource的类型
        //第二个参数就是输出类型,即匿名函数要返回的数据类型
        dataSource.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String s) throws Exception {
                return Tuple2.of(s, 1); //第一个字段是字符串, 第二个字段记为1
                //(a,1)
                //(a,1)
                //(b,1)
                //(d,1)
                //(d,1)
            }
        }).print(); //4.sink

        //5.程序执行
        env.execute();
    }
}

示例代码2:

public class Demo2Operator {

    public static void main(String[] args) throws Exception {
        //1.环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2.source
        DataStreamSource<Tuple4<String, String, Double, Integer>> dataSource = env.fromElements(Tuple4.of("zhangsan", "苹果", 4.5d, 2),
                Tuple4.of("lisi", "机械键盘", 800d, 1),
                Tuple4.of("zhangsan", "橘子", 2.5d, 2));
        //3.数据处理/转换 --- MapFunction中的第二个参数就是匿名函数要返回的数据类型
        SingleOutputStreamOperator<Tuple3<String, String, Double>> result = dataSource.map(new MapFunction<Tuple4<String, String, Double, Integer>, Tuple3<String, String, Double>>() {
            @Override
            public Tuple3<String, String, Double> map(Tuple4<String, String, Double, Integer> tuple4) throws Exception {
                return Tuple3.of(tuple4.f0, tuple4.f1, tuple4.f2 * tuple4.f3);
            }
        });
        //4.sink
        result.print();
        //5.程序执行
        env.execute();
    }
}

富函数RichMapFunction

public class Demo3Operator {

    public static void main(String[] args) throws Exception {
        //1.环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //2.source
        DataStreamSource<Event> dataSource = env.fromElements(
                new Event("Mary", "./home", 2000),
                new Event("Bob", "./cart", 4000),
                new Event("Alice", "./prod?id=100", 3000)
        );
        //3.数据处理 4.sink
        dataSource.map(new MyRichMapper()).print();
        //5.程序执行
        env.execute();
    }


    //实现一个自定义的富函数
    static class MyRichMapper extends RichMapFunction<Event, Integer> {

        @Override
        public void open(Configuration parameters) throws Exception {
            super.open(parameters);
            System.out.println("open生命周期被调用:" + getRuntimeContext().getIndexOfThisSubtask() + "号任务启动");
        }

        @Override
        public Integer map(Event value) throws Exception {
            return value.getUrl().length(); //返回值
        }

        @Override
        public void close() throws Exception {
            super.close();
            System.out.println("close生命周期被调用:" + getRuntimeContext().getIndexOfThisSubtask() + "号任务关闭");
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class Event {
        private String user;
        private String url;
        private Integer timestamp;
    }
}

输出结果:

open生命周期被调用:0号任务启动
6
6
13
close生命周期被调用:0号任务关闭

FlatMap

  • 处理输入一个元素产生一个或者多个元素的计算场景。
public class Demo4Operator {

    public static void main(String[] args) throws Exception {
        //1.环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2.source
        DataStreamSource<String> dataSource = env.fromElements("aa bb cc", "bb dd", "cc ff aa");
        //3.数据处理 4.sink
        //dataSource.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
        //    @Override
        //    public void flatMap(String value, Collector<Tuple2<String, Integer>> collector) throws Exception {
        //        String[] words = value.split(" ");
        //        for (String word : words) {
        //            collector.collect(Tuple2.of(word, 1));
        //        }
        //    }
        //}).print();
        //等价于 new Splitter()作为flatmap的入参
        dataSource.flatMap(new Splitter()).print();

        //5.程序执行
        env.execute();
    }

    //自定义FlatMapFunction函数
    static class Splitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String sentence, Collector<Tuple2<String, Integer>> out) throws Exception {
            for (String word : sentence.split(" ")) {
                out.collect(Tuple2.of(word, 1));
            }
        }
    }
}

Filter的使用

  • 处理输入一个元素产生一个或者多个元素的计算场景
public class Demo5Operator {

    public static void main(String[] args) throws Exception {
        //1.环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //2.source
        DataStreamSource<String> dataSource = env.fromElements("aa bb cc", "bb dd", "cc ff aa");
        //3.数据处理 4.sink
        dataSource.filter(new FilterFunction<String>() {
            @Override
            public boolean filter(String s) throws Exception {
                //aa开头的数据进行采集
                return s.startsWith("aa");
            }
        }).print();

        //5.程序执行
        env.execute();
    }
}

分组(keyBy)+滚动聚合算子(Rolling Aggregation)

1)分组算子如下:

DataStream -> KeyedStream:逻辑上将一个流分成不相交的分区,每个分区包含具有相同key的元素,在内部以hash的形式实现的。

KeyedStream才有聚合算子(其并行度依赖于前置的DataStream),不能单独设置并行度),普通的DataStream是没有的,但是KeyedStream依旧继承于DataStream。

2)滚动算子如下:

这些算子可以针对KeyedStream的每一个支流做聚合。

sum(),min(),max(),minBy(),maxBy()

public class Demo5Operator {

    public static void main(String[] args) throws Exception {
        //1.环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        //2.source
        DataStreamSource<String> dataSource = env.fromElements("aa bb cc", "bb dd", "cc ff aa");
        //3.数据处理
        //3.1 分割数据, 每个单词记1
        //SingleOutputStreamOperator<Tuple2<String, Integer>> osOperator = dataSource.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
        //    @Override
        //    public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
        //        String[] words = s.split(" ");
        //        for (String word : words) {
        //            collector.collect(Tuple2.of(word, 1));
        //        }
        //    }
        //});
        //3.2 keyBy分组获取KeyedStream,聚合API在KeyedStream才有 ,只有当输入类型是Tuple时keyBy分组这里才能写数字
        //KeyedStream<Tuple2<String, Integer>, String> keyGroupStream = osOperator.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
        //    @Override
        //    public String getKey(Tuple2<String, Integer> tuple2) throws Exception {
        //        return tuple2.f0; //根据第一个字段f0分组
        //    }
        //});
        //3.3 根据前面的分组,将同个分组下的position=1的都进行相加,这里即将相同字符串进行相加
        //SingleOutputStreamOperator<Tuple2<String, Integer>> sumOperator = keyGroupStream.sum(1);

        //第三步的处理可以简化为
        SingleOutputStreamOperator<Tuple2<String, Integer>> sumOperator = dataSource.flatMap((FlatMapFunction<String, Tuple2<String, Integer>>) (s, collector) -> {
            String[] words = s.split(" ");
            for (String word : words) {
                collector.collect(Tuple2.of(word, 1));
            }
        }).returns(Types.TUPLE(Types.STRING, Types.INT)) //特别注意:必须返回值,否则会报错
                .keyBy((KeySelector<Tuple2<String, Integer>, String>) tuple2 -> {
                    return tuple2.f0; //根据第一个字段f0分组
                }).sum(1);

        //4. sink
        sumOperator.print();

        //5.程序执行
        env.execute();
    }
}

Reduce

KeyedStream->DataStream:一个分组数据流的聚合操作。合并当前的元素和上次聚合的结果,产生一个新的值。返回的流中包含每一次聚合的结果,而不是只返回最后一次聚合的最终结果。

 

posted @ 2022-05-29 08:54  残城碎梦  阅读(1653)  评论(0编辑  收藏  举报