当前位置:   article > 正文

Flink系列二:DataStream API中的Source,Transformation,Sink详解(^_^)_datastream 转换为source

datastream 转换为source

在上面篇文章中已经对flink进行了简单的介绍以及了解了Flink API 层级划分,这一章内容我们主要介绍DataStream API

 

流程图解:

 

3908739ad07d4f6b987e0e15b257e0ac.png

 

一、DataStream API Source

Flink 在流处理和批处理上的 source 大概有 4 类:

(1)基于本地集合的 source

(2)基于文件的 source

(3)基于网络套接字的 source,具体来说就是从远程服务器或本地端口上的套接字连接中接收数据,比如上一篇文章中的入门案例就属于这一种。

(4)自定义的 source。自定义的 source 常见的有 Apache kafka、Amazon Kinesis Streams、RabbitMQ、Twitter Streaming API、Apache NiFi 等,当然你也可以定义自己的 source,灵活度较高,看个人需求。

 

下面就是纯代码演示了,具体细节会在注释中说明

1、本地集合的source

  1. import org.apache.flink.streaming.api.datastream.DataStream;
  2. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  3. import java.util.ArrayList;
  4. public class Demo1ListSource {
  5. public static void main(String[] args) throws Exception{
  6. //创建flink执行环境
  7. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  8. //创建集合
  9. ArrayList<String> arrayList = new ArrayList<>();
  10. arrayList.add("java");
  11. arrayList.add("java");
  12. arrayList.add("java");
  13. arrayList.add("java");
  14. arrayList.add("java");
  15. /*
  16. *基于集合的Source ----- 属于有界流
  17. */
  18. DataStream<String> listDS = env.fromCollection(arrayList);
  19. listDS.print();
  20. //启动Flink作业执行
  21. env.execute();
  22. }
  23. }

结果:

16787a01a3a847538ef32dcecc29f79f.png

在这解释一下结果图中的数字前缀,这个前缀的主要目的是不同并行实例的输出。什么都不设置的话取决于你电脑的内存了,比如我电脑是16G的内存,那么当数据较多时默认分配给该作业分了16个task。

 

2、本地文件的source

注意:同一个File数据源,既能有界读取,也能无界读取

2.1 有界读取

  1. /*
  2. *流批统一:
  3. * 1、同一套算子代码既能作流处理也能做批处理
  4. * 2、同一个File数据源,既能有界读取,也能无界读取
  5. */
  6. import org.apache.flink.api.common.eventtime.WatermarkStrategy;
  7. import org.apache.flink.connector.file.src.FileSource;
  8. import org.apache.flink.connector.file.src.reader.TextLineInputFormat;
  9. import org.apache.flink.core.fs.Path;
  10. import org.apache.flink.streaming.api.datastream.DataStream;
  11. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  12. public class Demo2FileSource1 {
  13. public static void main(String[] args) throws Exception {
  14. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  15. /*
  16. *有界读取
  17. */
  18. //老版本方式:简单但不灵活
  19. DataStream<String> lineDS = env.readTextFile("flink/data/student.csv");
  20. // lineDS.print();
  21. //新版本方式:复杂一点但更灵活,使用这种既能有界读取,也能无界读取
  22. //构建fileSource
  23. FileSource<String> fileSource = FileSource.forRecordStreamFormat(
  24. //指定编码
  25. new TextLineInputFormat("UTF-8")
  26. //指定路径
  27. , new Path("flink/data/student.csv")
  28. ).build();
  29. //使用fileSource
  30. DataStream<String> fileDS = env.fromSource(fileSource, WatermarkStrategy.noWatermarks(), "fileSource");
  31. fileDS.print();
  32. env.execute();
  33. }
  34. }

 

2.1 无界读取

  1. import org.apache.flink.api.common.RuntimeExecutionMode;
  2. import org.apache.flink.api.common.eventtime.WatermarkStrategy;
  3. import org.apache.flink.connector.file.src.FileSource;
  4. import org.apache.flink.connector.file.src.reader.TextLineInputFormat;
  5. import org.apache.flink.core.fs.Path;
  6. import org.apache.flink.streaming.api.datastream.DataStreamSource;
  7. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  8. public class Demo2FileSource2 {
  9. public static void main(String[] args) throws Exception {
  10. /*
  11. *使用无界流读取文件数,很简单,其实就是对上面的代码修改运行模式并加个参数就可以了
  12. */
  13. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  14. //修改运行模式
  15. env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
  16. //构建fileSource
  17. FileSource<String> fileSource = FileSource.forRecordStreamFormat(new TextLineInputFormat("UTF-8"),
  18. new Path("spark/data/student.csv")).build();
  19. //使用fileSource
  20. DataStreamSource<String> linesDS = env.fromSource(fileSource, WatermarkStrategy.noWatermarks(), "fileSource");
  21. linesDS.print();
  22. env.execute();
  23. }
  24. }

 

3、本地端口的source

上一篇文章中的入门案例就属于这一种,后面在代码中也会用到,在此不在赘述了。

 

4、自定义的 source

举例:使用自定义source读取mysql中的数据

  1. /*实现方式:
  2. * 1、实现SourceFunction或ParallelSourceFunction接口来创建自定义的数据源。
  3. * 2、然后使用env.addSource(new CustomSourceFunction())或DataStreamSource.fromSource添加你自定义的数据源。
  4. */
  5. import lombok.AllArgsConstructor;
  6. import lombok.Data;
  7. import org.apache.flink.api.common.typeinfo.Types;
  8. import org.apache.flink.api.java.tuple.Tuple2;
  9. import org.apache.flink.streaming.api.datastream.DataStream;
  10. import org.apache.flink.streaming.api.datastream.DataStreamSource;
  11. import org.apache.flink.streaming.api.datastream.KeyedStream;
  12. import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
  13. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  14. import org.apache.flink.streaming.api.functions.source.SourceFunction;
  15. import java.sql.Connection;
  16. import java.sql.DriverManager;
  17. import java.sql.PreparedStatement;
  18. import java.sql.ResultSet;
  19. public class Demo3MysqlSource {
  20. public static void main(String[] args) throws Exception {
  21. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  22. //使用自定义的source
  23. DataStream<Student> studentDSSource = env.addSource(new MysqlSource());
  24. //统计学生表每个班级的人数
  25. //取出每一行的班级列并加上人数后缀1
  26. DataStream<Tuple2<String, Integer>> clazzKvDS = studentDSSource.map(line -> Tuple2.of(line.getClazz(), 1), Types.TUPLE(Types.STRING, Types.INT));
  27. //分组,将相同的键发送给同一个task中
  28. KeyedStream<Tuple2<String, Integer>, String> keyByDS = clazzKvDS.keyBy(kv -> kv.f0);
  29. //求和
  30. SingleOutputStreamOperator<Tuple2<String, Integer>> clazzSum = keyByDS.sum(1);
  31. //输出
  32. clazzSum.print();
  33. env.execute();
  34. }
  35. }
  36. /**
  37. * 自定义source读取mysql中的数据
  38. */
  39. class MysqlSource implements SourceFunction<Student> {
  40. /**
  41. * run()方法会在任务启动的时候执行一次
  42. */
  43. @Override
  44. public void run(SourceContext ctx) throws Exception {
  45. //1、加载mysq驱动
  46. Class.forName("com.mysql.jdbc.Driver");
  47. //2、创建数据库连接
  48. //注意:如果报连不上的错误,将参数补全(useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf8&useSSL=false)
  49. Connection conn = DriverManager.getConnection("jdbc:mysql://master:3306/bigdata29?useSSL=false", "root", "123456");
  50. //3、编写sql查询
  51. PreparedStatement sql = conn.prepareStatement("select * from students");
  52. //4、执行查询
  53. ResultSet resultSet = sql.executeQuery();
  54. //5、遍历查询出的数据
  55. while (resultSet.next()) {
  56. int id = resultSet.getInt("id");
  57. String name = resultSet.getString("name");
  58. int age = resultSet.getInt("age");
  59. String gender = resultSet.getString("gender");
  60. String clazz = resultSet.getString("clazz");
  61. //将数据发送到下游
  62. /*
  63. * collect():从 DataStream 收集所有的元素,并将它们作为列表或其他集合类型返回给客户端
  64. */
  65. ctx.collect(new Student(id, name, age, gender, clazz));
  66. }
  67. //6、释放资源
  68. sql.close();
  69. conn.close();
  70. }
  71. @Override
  72. public void cancel() {
  73. /*
  74. * cancel(),它用于在任务完成后执行清理操作
  75. */
  76. }
  77. }
  78. /**
  79. * 这里使用了lombok插件(小辣椒)
  80. * 这个插件的作用可以在代码编译的时候增加方法(相当于scala中的case class),就不用我们自己手动添加get、set、toString等方法了。
  81. * 使用方法:加@就行了
  82. */
  83. @Data
  84. @AllArgsConstructor
  85. class Student {
  86. private int id;
  87. private String name;
  88. private int age;
  89. private String gender;
  90. private String clazz;
  91. }

 

 

二、DataStream API Transformation

Transformation:数据流转换。

常见算子有 Map / FlatMap / Filter /KeyBy / Reduce / Fold / Aggregations / Window / WindowAll / Union / Window join / Split / Select / Project 等,操作很多,可以将数据转换计算成你想要的数据形式。

其实这些算子在功能上与scala或spark中的基本相同,只是形式和细节上会有些差别。

1、map

DataStream → DataStream    输入一个元素同时输出一个元素

  1. import org.apache.flink.api.common.functions.MapFunction;
  2. import org.apache.flink.streaming.api.datastream.DataStream;
  3. import org.apache.flink.streaming.api.datastream.DataStreamSource;
  4. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  5. public class Demo1Map {
  6. public static void main(String[] args) throws Exception {
  7. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  8. //使用nc -lk 8888 模拟实时数据的产生
  9. DataStreamSource<String> source = env.socketTextStream("master", 8888);
  10. //方式1:匿名内部类形式
  11. /*
  12. * 观察源码map发现:
  13. * MapFunction<T, O> 是一个函数接口,用于对流中的每个元素的处理
  14. * 这个接口定义了一个 map 方法,该方法接受一个输入元素(类型为 T)并返回一个输出元素(类型为 O)。
  15. */
  16. DataStream<String> map1DS = source.map(new MapFunction<String, String>() {
  17. @Override
  18. public String map(String word) throws Exception {
  19. return word.toUpperCase();
  20. }
  21. });
  22. // map1DS.print();
  23. //方式2:lambda表达式形式(更简洁常用)
  24. source.map(String::toUpperCase).print(); //是对source.map(word -> word.toUpperCase())的更简写
  25. env.execute();
  26. }
  27. }

结果:

45374801cb354dccbc098f73af160b18.png

 

 

2、flatMap

DataStream → DataStream

输入一个元素转换为一个或多个元素输出

  1. /*
  2. *flatMap 方法用于将输入流中的每个元素转换成一个或多个输出元素
  3. */
  4. import org.apache.flink.api.common.functions.FlatMapFunction;
  5. import org.apache.flink.api.common.typeinfo.Types;
  6. import org.apache.flink.streaming.api.datastream.DataStream;
  7. import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
  8. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  9. import org.apache.flink.util.Collector;
  10. public class Demo2FaltMap {
  11. public static void main(String[] args) throws Exception {
  12. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  13. DataStream<String> source = env.socketTextStream("master", 8888);
  14. //方式1:匿名内部类
  15. //看源码,这个方法接受一个FlatMapFunction<T, R>类型的参数,其中T是输入元素的类型,R是输出元素的类型
  16. DataStream<String> out2DS = source.flatMap(new FlatMapFunction<String, String>() {
  17. @Override
  18. public void flatMap(String line, Collector<String> out) throws Exception {
  19. for (String word : line.split(",")) {
  20. //循环将数据发送到下游
  21. out.collect(word);
  22. }
  23. }
  24. });
  25. // out2DS.print();
  26. //方式2:lambda表达式
  27. DataStream<String> out1DS = source.flatMap((line, out) -> {
  28. for (String word : line.split(",")) {
  29. //循环将数据发送到下游
  30. out.collect(word);
  31. }
  32. }, Types.STRING);
  33. out1DS.print();
  34. env.execute();
  35. }
  36. }

结果:

c82a409f9f824b6eba5ee6263c064070.png

 

3、filter

DataStream → DataStream 

为每个元素执行一个布尔 function,并保留那些 function 输出值为 true 的元素

  1. import org.apache.flink.api.common.functions.FilterFunction;
  2. import org.apache.flink.streaming.api.datastream.DataStream;
  3. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  4. public class Demo3Filter {
  5. public static void main(String[] args) throws Exception {
  6. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  7. DataStream<String> source = env.readTextFile("spark/data/student.csv");
  8. //需求:过滤出文科一班的学生的信息
  9. //方式一:匿名内部类
  10. source.filter(new FilterFunction<String>() {
  11. @Override
  12. public boolean filter(String lines) throws Exception {
  13. return "文科一班".equals(lines.split(",")[4]);
  14. }
  15. }); //.print();
  16. //方式2:lambda表达式
  17. source.filter(lines->"文科一班".equals(lines.split(",")[4])).print();
  18. env.execute();
  19. }
  20. }

结果:

d3e93c6a422c4b5abbd8ce6822799a0d.png

 

4、keyBy

作用为:分组

DataStream → KeyedStream

在逻辑层面将流划分为不相交的分区。具有相同 key 的记录都分配到同一个分区。在内部, keyBy() 是通过哈希分区实现的。有多种指定 key 的方式。

39272837e4da48c3b64487f8a16d2766.png

  1. import org.apache.flink.api.common.typeinfo.Types;
  2. import org.apache.flink.api.java.functions.KeySelector;
  3. import org.apache.flink.api.java.tuple.Tuple2;
  4. import org.apache.flink.streaming.api.datastream.DataStreamSource;
  5. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  6. public class Demo4KeyBy {
  7. public static void main(String[] args) throws Exception {
  8. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  9. DataStreamSource<String> source = env.socketTextStream("master", 8888);
  10. //方式1:匿名内部类
  11. /*
  12. * public <K> KeyedStream<T, K> keyBy(KeySelector<T, K> key)
  13. * 其中 T 是输入元素的类型,K 是键的类型
  14. */
  15. source.map(word-> Tuple2.of(word,1), Types.TUPLE(Types.STRING,Types.INT))
  16. .keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
  17. @Override
  18. public String getKey(Tuple2<String, Integer> kv) throws Exception {
  19. return kv.f0;
  20. }
  21. });//.print();
  22. //方式2:lambda表达式
  23. source.map(word-> Tuple2.of(word,1), Types.TUPLE(Types.STRING,Types.INT))
  24. .keyBy(kv->kv.f0).print();
  25. env.execute();
  26. }
  27. }

结果: 可以看出的确作了分区

48db7796907641b9b7c8480b6561ab4b.png

 

5、reduce

作用为:聚合

KeyedStream → DataStream

在相同 key 的数据流上“滚动”执行 reduce。将当前元素与最后一次 reduce 得到的值组合然后输出新值。

  1. import org.apache.flink.api.common.functions.ReduceFunction;
  2. import org.apache.flink.api.common.typeinfo.Types;
  3. import org.apache.flink.api.java.tuple.Tuple2;
  4. import org.apache.flink.streaming.api.datastream.DataStream;
  5. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  6. public class Demo5Reduce {
  7. public static void main(String[] args) throws Exception {
  8. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  9. DataStream<String> source = env.socketTextStream("master", 8888);
  10. //方式1:匿名内部类
  11. source.map(word -> Tuple2.of(word, 1), Types.TUPLE(Types.STRING, Types.INT))
  12. .keyBy(kv -> kv.f0)
  13. .reduce(new ReduceFunction<Tuple2<String, Integer>>() {
  14. @Override
  15. public Tuple2<String, Integer> reduce(Tuple2<String, Integer> kv1,
  16. Tuple2<String, Integer> kv2) throws Exception {
  17. //kv1和kv2的key是一样的
  18. String word = kv1.f0;
  19. int counts = kv1.f1 + kv2.f1;
  20. return Tuple2.of(word,counts);
  21. }
  22. }).print();
  23. env.execute();
  24. }
  25. }

结果:从结果来看说明reduce是一个有状态算子。 

6003735a7c804c60b0a2652d4f664ee0.png

 

6、Window

KeyedStream → WindowedStream 

可以在已经分区的 KeyedStreams 上定义 Window,Window 根据某些特征(例如,最近 5 秒内到达的数据)对每个 key Stream 中的数据进行分组。

窗口算子有很多,以后会专门出一章具体说明,下面写一个滑动窗口的案例。

  1. import org.apache.flink.api.common.typeinfo.Types;
  2. import org.apache.flink.api.java.tuple.Tuple2;
  3. import org.apache.flink.streaming.api.datastream.*;
  4. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  5. import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows;
  6. import org.apache.flink.streaming.api.windowing.time.Time;
  7. import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
  8. public class Demo6Window {
  9. public static void main(String[] args) throws Exception {
  10. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  11. /*
  12. * 每隔5秒统计最近15秒每个单词的数量 --- 滑动窗口
  13. */
  14. DataStream<String> wordsDS = env.socketTextStream("master", 8888);
  15. //转换成kv
  16. DataStream<Tuple2<String, Integer>> kvDS = wordsDS
  17. .map(word -> Tuple2.of(word, 1), Types.TUPLE(Types.STRING, Types.INT));
  18. //按照单词分组
  19. KeyedStream<Tuple2<String, Integer>, String> keyByDS = kvDS.keyBy(kv -> kv.f0);
  20. //划分窗口
  21. //SlidingEventTimeWindows:滑动的处理时间窗口
  22. //前一个参数为窗口大小(window size),后一个参数为滑动大小(window slide)
  23. WindowedStream<Tuple2<String, Integer>, String, TimeWindow> windowDS = keyByDS
  24. .window(SlidingProcessingTimeWindows.of(Time.seconds(30), Time.seconds(10)));
  25. //统计单词的数量
  26. DataStream<Tuple2<String, Integer>> countDS = windowDS.sum(1);
  27. countDS.print();
  28. env.execute();
  29. }
  30. }

 

7、Union

DataStream→ DataStream

将两个或多个数据流联合来创建一个包含所有流中数据的新流。注意:如果一个数据流和自身进行联合,这个流中的每个数据将在合并后的流中出现两次。

  1. import org.apache.flink.streaming.api.datastream.DataStream;
  2. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  3. public class Demo7Union {
  4. public static void main(String[] args) throws Exception {
  5. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  6. DataStream<String> source1 = env.socketTextStream("master", 8888);
  7. DataStream<String> source2 = env.socketTextStream("master", 9999);
  8. /*
  9. * 合并两个DataStream
  10. * 注意:在数据层面并没有合并,只是在逻辑层面合并了
  11. */
  12. DataStream<String> unionDS = source1.union(source2);
  13. unionDS.print();
  14. env.execute();
  15. }
  16. }

结果:

5e95fe6957a1463abe7dde2dce4af9d9.png

 

8、process

DataStream→ DataStream

process算子是flink的底层算子,可以用来代替map、faltMap、filter等算子

  1. import org.apache.flink.api.java.tuple.Tuple2;
  2. import org.apache.flink.streaming.api.datastream.DataStream;
  3. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  4. import org.apache.flink.streaming.api.functions.ProcessFunction;
  5. import org.apache.flink.util.Collector;
  6. public class Demo8Process {
  7. public static void main(String[] args) throws Exception {
  8. /*
  9. * process算子是flink的底层算子,可以用来代替map、faltMap、filter等算子
  10. *
  11. * public <R> SingleOutputStreamOperator<R> process(ProcessFunction<T, R> processFunction)
  12. * 其中 T 是输入数据的类型,R 是输出数据的类型
  13. */
  14. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  15. DataStream<String> source = env.socketTextStream("master", 8888);
  16. DataStream<Tuple2<String, Integer>> processDS = source.process(new ProcessFunction<String, Tuple2<String, Integer>>() {
  17. /*
  18. * processElement:在当前代码中相当于flatMap,每一条数据执行一次,可以返回一条或多条数据
  19. * ctx:上下文对象(代表flink执行环境)
  20. * out:输出,用于将数据发送到下游
  21. */
  22. @Override
  23. public void processElement(String line, ProcessFunction<String, Tuple2<String, Integer>>.Context ctx,
  24. Collector<Tuple2<String, Integer>> out) throws Exception {
  25. //这里的逻辑与flatMap的逻辑相同
  26. for (String word : line.split(",")) {
  27. out.collect(Tuple2.of(word, 1));
  28. }
  29. }
  30. });
  31. env.execute();
  32. /*
  33. * 注意:该算子不能用lambda表达式改写,因为ProcessFunction它包含了一些生命周期方法和状态管理的方法,
  34. * 这些方法使得它不适合直接简化为lambda表达式的形式。
  35. *
  36. * 在底层代码层面来说,ProcessFunction是一个抽象类,该类还有许多复杂的方法,使得它无法直接用lambda表达式来改写
  37. * 因为 lambda 表达式只能表示简单的函数接口(即那些只包含一个抽象方法的接口)
  38. * public abstract class ProcessFunction<I, O> extends AbstractRichFunction
  39. */
  40. }
  41. }

 

三、DataStream API Sink

Flink 将转换计算后的数据发送的地点 。

Flink 常见的 Sink 大概有如下几类:

(1)打印在控制台、写入文件。

(2)写入 socket(具体指的是将数据发送到网络套接字(例如端口))。

(3)自定义的 sink :常见的有 Apache kafka、RabbitMQ、MySQL、ElasticSearch、Apache Cassandra、Hadoop FileSystem 等,当然你也可以根据需求定义自己的 sink。

 

1、写入文件

对于写入文件,是否要将所有数据写入同一个文件?由于是流式写入,该文件就一直处于正在写入的状态,而且可能会造成文件过大的问题,所以DataStream API提供了滚动策略的方式来解决这样的问题。

9a5ed8bc0f1c42a4a16f59c8bcf80165.png

 

  1. import org.apache.flink.api.common.serialization.SimpleStringEncoder;
  2. import org.apache.flink.configuration.MemorySize;
  3. import org.apache.flink.connector.file.sink.FileSink;
  4. import org.apache.flink.core.fs.Path;
  5. import org.apache.flink.streaming.api.datastream.DataStream;
  6. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  7. import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.DefaultRollingPolicy;
  8. import java.time.Duration;
  9. public class Demo1FileSink {
  10. public static void main(String[] args) throws Exception {
  11. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  12. DataStream<String> source = env.socketTextStream("master", 8888);
  13. //创建fileSink
  14. /*
  15. *public static <IN> DefaultRowFormatBuilder<IN> forRowFormat(
  16. * final Path basePath, final Encoder<IN> encoder)}
  17. *
  18. *<IN> : The type of the elements that are being written by the sink.
  19. */
  20. FileSink<String> fileSink = FileSink.forRowFormat(new Path("flink/data/words"), new SimpleStringEncoder<String>("UTF-8"))
  21. .withRollingPolicy(
  22. DefaultRollingPolicy.builder()
  23. //每10秒进行一次滚动(生成文件)
  24. .withRolloverInterval(Duration.ofSeconds(10))
  25. //当延迟超过10秒进行一次滚动
  26. .withInactivityInterval(Duration.ofSeconds(5))
  27. //文件大小达到1MB进行一次滚动
  28. .withMaxPartSize(MemorySize.ofMebiBytes(1))
  29. .build())
  30. .build();
  31. //使用fileSink,将读取的数据写入另一到文件夹中
  32. source.sinkTo(fileSink);
  33. env.execute();
  34. }
  35. }

结果: 

d9cfbc1a28b94ad79646683db279d104.png

 

2、自定义的 sink

举例:使用自定义sink将数据存到mysql中

  1. import org.apache.flink.api.common.typeinfo.Types;
  2. import org.apache.flink.api.java.tuple.Tuple2;
  3. import org.apache.flink.configuration.Configuration;
  4. import org.apache.flink.streaming.api.datastream.DataStream;
  5. import org.apache.flink.streaming.api.datastream.DataStreamSource;
  6. import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
  7. import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
  8. import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
  9. import org.apache.flink.streaming.api.functions.sink.SinkFunction;
  10. import java.sql.Connection;
  11. import java.sql.DriverManager;
  12. import java.sql.PreparedStatement;
  13. public class Demo3MySqlSInk {
  14. public static void main(String[] args) throws Exception {
  15. StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  16. DataStream<String> wordsDS = env.socketTextStream("master", 8888);
  17. //统计单词的数量
  18. DataStream<Tuple2<String, Integer>> countDS = wordsDS
  19. .map(word -> Tuple2.of(word, 1), Types.TUPLE(Types.STRING, Types.INT))
  20. .keyBy(kv -> kv.f0)
  21. .sum(1);
  22. //将统计结果保存到数据
  23. countDS.addSink(new MySQlSink());
  24. env.execute();
  25. }
  26. }
  27. /**
  28. * 自定义sink将数据保存到mysql
  29. * RichSinkFunction:多了open和close方法,用于打开和关闭连接
  30. * SinkFunction
  31. */
  32. class MySQlSink extends RichSinkFunction<Tuple2<String, Integer>> {
  33. Connection con;
  34. PreparedStatement stat;
  35. /**
  36. * invoke方法每一条数据执行一次
  37. */
  38. @Override
  39. public void invoke(Tuple2<String, Integer> kv, Context context) throws Exception {
  40. stat.setString(1, kv.f0);
  41. stat.setInt(2, kv.f1);
  42. //执行sql
  43. stat.execute();
  44. }
  45. /**
  46. * open方法会在任务启动的时候,每一个task中执行一次
  47. */
  48. @Override
  49. public void open(Configuration parameters) throws Exception {
  50. System.out.println("创建数据库连接");
  51. //1、加载启动
  52. Class.forName("com.mysql.jdbc.Driver");
  53. //2、创建数据库连接
  54. con = DriverManager.getConnection("jdbc:mysql://master:3306/bigdata29", "root", "123456");
  55. //3、编写保存数据的sql
  56. //replace into 替换插入,如果没有就插入,如果有就更新,表需要有主键
  57. stat = con.prepareStatement("replace into word_count values(?,?)");
  58. }
  59. /**
  60. * close方法会在任务取消的时候,每一个task中执行一次
  61. */
  62. @Override
  63. public void close() throws Exception {
  64. //4、关闭数据库连接
  65. stat.close();
  66. con.close();
  67. }

 

 

 

 

 

---------------------------------------------------------------------------------------------------------------------------------

代码注意提示:

如果在写flink代码的过程中出现了以下错误,大概率就是有些算子使用没有写数据类型,与spark不同,spaark底层由scala编写,scala提供了自动类型推断机制,所以不写参数类型也不会报错,但是flink底层是java编写的,java没有这种机制。

23ddb4c8d57549598c2cdb15b640badf.png

 

 

基础的算子到这结束,其他算子后续也会写,以上内容具体详情皆参考apache flink官网,官网详细说明了各种算子的使用,网址贴在下面了:

https://nightlies.apache.org/flink/flink-docs-release-1.19/zh/docs/dev/datastream/operators/overview/

个人感觉写的很详细了,看不懂建议直接打死作者(^_^)

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号