Flink输入输出&算子&FlinkCDC

本文介绍了Flink与SparkStreaming的区别,详细讲解了Flink的流处理核心编程,包括Environment、Source、Transforms(如算子、重分区、控制流和聚合)以及Sink。特别强调了Flink的Kafka、Redis和ES等Sink的使用,并探讨了Flink CDC的实现,包括CDC种类、代码编写、任务提交和自定义序列化。
摘要由CSDN通过智能技术生成

Flink

版本问题,flink使用Scala时,flink1.12兼容scala2.11,flink1.12不兼容scala2.12

一、简介

flink是对有界流和无界数据流进行有状态的计算(所谓状态,指的是之前的数据sparkStreaming中只有updatestateByKey有状态,但是flink中任何的算子都可以有状态,可以自己定义)

1.1sparkStreaming与flink的区别


(1)事件驱动型

sparkStreaming是时间驱动,一个批次中假如没有数据依然会从kafka拉取数据,走计算逻辑,只不过没有结果。
flink是事件驱动的(事件即指数据)来一条计算一条,不会浪费资源且实时性更好。(是和spark批处理的本质区别)

(2)流处理与批处理

spark处理数据都是按照批次处理的,如果每隔批次的时间较长,可认为是批处理,若每个批次相隔的时间很短,是微批处理来模拟的流处理。
flink的流与批是根据数据有无结束限定的。如果flink读取文本文件有开头有结尾是批处理(批处理时也是事件驱动型,来一条数据处理一条),如果从kafka读取数据,有开头没有结尾是流处理(流处理是事件驱动型)
flink使用时间窗口就能实现spark的微批处理。

(3)关于并行度的问题
spark在代码中可以更改并行度(即进行分区数的设置,但是只有一些特定的算子能进行指定,像map,flapmap是不能的)
flink中任何算子都能设置并行度(map.setParallelism(3))注意keyby没有,因为keyby是hash操作将数据送入后面的算子中。
/**
 * 并行度的设置
 * 1、在代码中算子单独设置 sum:2 flatmap:3
 * 2、代码中env 设置:4
 * 3、提交任务参数:6
 * 4、flink配置文件:1
 * 如果总共6个slot,提交的时候设置并行度是9,任务时调动不起来的,且默认的一个任务所需要的slot个数就是任务中的最大并行度
 */
 flink与spark任务划分的方式不同,spark根据产生shuffer操作会产生新的stage,而flink是只要算子之间的并行度不同就会产生新的stage
 
(4)

以上(3)结论的图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJpOUwix-1641372478560)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211210220337526.png)]

代码如下:读端口并行度为1,flatmap:3,keyby,sum:2,print:4是env设置的全局并行度

public class Flink03_WordCount_Unbounded {
    public static void main(String[] args) throws Exception {
        //创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(4);
        //读端口数据
        DataStreamSource<String> sourceDS = env.socketTextStream("linux2", 9999);
        //读取的数据扁平化
        SingleOutputStreamOperator<Tuple2<String, Integer>> tupleDS = sourceDS.flatMap(new Flink02_WordCount_Bounded.MyFlapMapFunc()).setParallelism(3);
        KeyedStream<Tuple2<String, Integer>, String> keyDS = tupleDS.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
            public String getKey(Tuple2<String, Integer> value) throws Exception {
                return value.f0;
            }
        });
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = keyDS.sum(1).setParallelism(2);
        result.print();
        //启动任务
        env.execute();

    }
}

1.2分层API

2020年12月8日发布的最新版本****1.12.0****, 已经完成实现了真正的****流批一体****

从简到易:
sql:SQL查询可以直接在Table API定义的表上执行。
table api:表可以动态变化,API提供可比较的操作,例如select、project、join、group-by、aggregate等,用户可自定义函数(UDF)进行扩展,Table API程序在执行之前会经过内置优化器进行优化。
datastream/dataSet API:DataStream/DataSet 与表可以无缝切换。
process function:最底层的API,算子无法实现的需要使用最底层process编写程序。
sql也能应用与流处理

1.3flink 流批处理不同

flink不向spark,没有reducrByKey,分组和聚合是分开的,先groupby,再聚合。?
flink流(StreamExecutionEnvironment)处理与批(ExecutionEnvironment)处理所需的执行环境不同。
flink批处理有groupby,流处理只有keyBy。
flink批处理也是来一条执行一条,只不过最后输出一次结果,flink流处理来一条执行一条输出一次结果。

1.4 flink 部署模式

(1)local模式

(2)standaloan模式:提交任务前要先启动flink集群。是flink自己提供计算资源,不依赖于其他框架,可在没有hadoop节点中单独安装flink集群
flink-conf.xml配置文件:
1、可配置taskmanager总共内存,单节点可用的插槽,默认并行度
2、jobmanager的高可用,包含zk的配置
3、状态后端:指的是存储状态的地址,通常有3种,jobmanager(是内存级别的),hdfs,rocksdb(是指一种kv的存储格式,且支持增量的操作,是最常用的)

(3)yarn模式:用yarn做资源的分配,基于hadoop集群之上。
flink基于yarn墨水有3种提交作业的方式
① session-cluster:启动flink集群后向yarn申请得到资源,这个资源是永远不变的,当提交多个任务时,会共用申请到的资源,如果前面的job用尽了申请到的slot且任务不结束,后面的job则申请不到资源无法执行。此种模式适合job比较短的需要频繁提交的批处理任务。
备注:session和per-job的区别:session是一次性申请flink集群资源,所有的任务用这一个资源,job运行完后下一个任务只需要去获得slot即可,而per-job需要针对每隔任务都去yarn申请资源,所以per-job模式提交会比session提交反应慢。(数据库连接池与普通获取数据库连接方式的区别吧)
② application mode:client
备注:application和per-job区别:用户自主程序运行的位置。application相当于spark的client,用户住程序运行在本地的jobmanager
③ per-job-cluster:cluster,用的最多的是per-job-cluster

1.5 flink运行架构

1、jobManager:作用
①. 响应客户端请求
②.向resureManager申请资源
③.将job生成作业图-数据流图-执行图(切分任务)
④.分发执行图到taskManager上,负责任务的协调

2、resourceManager:
①.此处是flink单独的resourceManger,不是yarn的
②.flink的resourceManager和jobManager运行在同一个节点上

3、dispatcher:分发器:
只有能在网页上提交任务的模式才有触发器

4、taskManager:tm上有slot插槽,slot是对tm的内存资源进行隔离,并不会隔离cpu

5、并行度
一个流程序的并行度是代码算子中最大的并行度,而每个算子可以有不同的并行度,每个算子的并行度就叫subtask
stream在算子之间传输数据的形式有两种:
① one-to-one:source--map(flatMap)算子之间没有重分区是one-to-one的对应关系,类似spark的窄依赖。
②Redistributing:flatMap-keyBy(or window)有重分区,每一个算子的子任务依据所选择的transformation发送数据到不同的目标任务。例如,keyBy()基于hashCode重分区、broadcast和rebalance会随机重新分区,这些算子都会引起redistribute过程,类似spark的宽依赖。

6、Operator Chains(任务链)
类似spark中的stage
但是flink的任务链划分和spark中stage划分是不一样的。
想要算子划分到同一个任务链中必须满足以下3个条件:
第一:算子没有产生重分区
第二:算子之间的并行度要相同
第三:算子属于同一个共享组
这样划分的目的是为了减少网络IO的。
7、共享组
//全局并行度为 1
// souorce --flatMap.slotSharingGroup("group1")--map --keyBy--sum.slotSharingGroup("group2")--print
map是在default还是在group1组?
任务链划分如下:
souorce / flatMap及map在group1组 /keyBy及sum及print在default组

思考:

slot的个数通常是job中最大的并行度,这里并行度是2,却使用了5个slot,原因是什么?

是不同算子设置了不同的共享组。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqSJz6Bj-1641372478561)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214213600616.png)]

例1:
//全局并行度为 1
// souorce --flatMap.slotSharingGroup("group1")--map.setParallelism(3) --keyBy--sum.slotSharingGroup("group2")--print

会切分4个任务链,使用5个slot(可认为共享组有就近原则?)
所以之前说的slot的数量是算子中的最大并行度是同一个共享组的时候,完善应是:
任务所需的slot数量是每个共享组中最大的并行度之和。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiAr61RG-1641372478561)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214223635769.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pjDK6lnH-1641372478562)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214223957658.png)]

例2:
代码中设置:
//设置全局禁用任务链
env.disableOperatorChaining();
keyBy和sum是一个操作在一起,其余的任务链都分开了。需要3个slot
此外可针对算子单独设置:
flatMap.startNewChain()是和前边的划分开,出来一个新任务链。
flatMap.disableChaining()是前后都划分开,自己单独一个任务链。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a50HRZFF-1641372478562)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214224757409.png)]

flatmap开启新链条

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hh5NT52K-1641372478563)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214225535380.png)]

flatmap.disableChaining()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2kVd3ADE-1641372478563)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214225713026.png)]

总结:

任务链及共享组总结:
1、默认情况下
  所有算子都在一个共享组内,开启了任务链和并
	任务需要的slot数量:最大并行度
	任务链的个数:要看有无宽依赖,算子的并行度是否相同
2、	给算子独立设置了共享组
  开启了任务链合并(就近)
  所需的slot数量:每个共享组中最大并行度之和
	任务链的个数:宽依赖,并行度,共享组
3、全局禁用任务链
  任务就是在一个共享组内
	任务所需的slot数量:最大并行度
	任务链的个数:算子的个数
4、给某个算子开启新的任务链
  同一个共享组
	任务所需的slot数量:最大并行度
	任务链的个数:宽依赖,并行度,开启的新任务链的算子
5、给某个算子禁用任务链
  同一个共享组
	任务所需的slot数量:最大并行度
	任务链的个数:宽依赖,并行度,禁用任务链的算子
总之:
	slot:只跟共享组和最大并行度有关
	任务链的个数:跟宽依赖,并行度,并行度,时否开启和禁用任务链
	

subtask

默认共享组:
同一个算子的subtask肯定是在不同的slot执行的,因为flink是从任务的空闲时间和资源共享的角度考虑的
例如一个任务两个并行度
slot1        slot2
source       flatmap
flapmap      map
map          keyBy
keyBy
slot1中的算子是从上到下执行的,前边的执行完后空出资源,后边的算子可继续使用,且在同一个节点上
减少了网络传输且资源进行了充分利用,如果两个flatmap放一起,忙的时候都忙闲的时候都闲会争抢资源。

1.6 flink 流处理核心编程

1.6.1 environment
// 批处理环境
ExecutionEnvironment benv = ExecutionEnvironment.getExecutionEnvironment();
// 流式数据处理环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
1.6.2 source
@Test
//从集合中读数据
      public void SourceCollection() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //准备集合
        List<WaterSensor> waterSensors = Arrays.asList(new WaterSensor("ws_001", 1577844001L, 45),
                new WaterSensor("ws_002", 1577844015L, 43),
                new WaterSensor("ws_003", 1577844020L, 42));
        //从集合读数据
        DataStreamSource<WaterSensor> waterSensorDataStreamSource = env.fromCollection(waterSensors);
        waterSensorDataStreamSource.print();
        env.execute();
      }
//从文件读数据
      @Test
      public void SourceFile() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DataStreamSource<String> wordcount = env.readTextFile("F:\\bigdata\\flink\\flink_learn_code\\src\\main\\resources\\input\\wordcount");
        wordcount.print();
        env.execute();
      }
//从端口读数据
      @Test
      public void SourceScoket() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DataStreamSource<String> wordcount = env.socketTextStream("linux1",9999);
        wordcount.print();
        env.execute();
      }
//从kafka读数据
    @Test
    public void SourceKafka() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        Properties properties = new Properties();
        properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"linux1:9092,linux2:9092,linux3:9092");
        properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG,"flinkkafka");
        DataStreamSource kafkaSD = env.addSource(new FlinkKafkaConsumer("sink_table", new SimpleStringSchema(), properties));
        kafkaSD.print();
        env.execute();
    }

自定义source

package operateFunction;

import bean.WaterSensor;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class MySurce {
    public static void main(String[] args) throws Exception {
        // 1. 创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env
                .addSource(new MySource("linux1", 9999))
                .print();

        env.execute();
    }

    public static class MySource implements SourceFunction<WaterSensor> {
        private String host;
        private int port;
        private volatile boolean isRunning = true;
        private Socket socket;

        public MySource(String host, int port) {
            this.host = host;
            this.port = port;
        }


        @Override
        public void run(SourceContext<WaterSensor> ctx) throws Exception {
            // 实现一个从socket读取数据的source
            socket = new Socket(host, port);
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
            String line = reader.readLine();
            while (isRunning && line != null) {
                String[] split = line.split(" ");
                ctx.collect(new WaterSensor(split[0], Long.valueOf(split[1]), Integer.valueOf(split[2])));
                line= reader.readLine();
            }
        }

        /**
         * 大多数的source在run方法内部都会有一个while循环,
         * 当调用这个方法的时候, 应该可以让run方法中的while循环结束
         */

        @Override
        public void cancel() {
            isRunning = false;

                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
1.6.3 transforms
①对数据操作的算子
map
flatmap:一进多出,炸裂

所有Flink函数类都有其Rich版本。它与常规函数的不同在于,可以获取运行环境的上下文,并拥有一些生命周期方法,所以可以实现更复杂的功能。也有意味着提供了更多的,更丰富的功能。例如:RichMapFunction
②重分区算子
package operateFunction;

import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * 重分区算子
 */
public class TranceformRebalance {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(5);
        DataStreamSource<String> socketDS = env.socketTextStream("linux1", 9999);
        SingleOutputStreamOperator<String> map = socketDS.map(x -> x).setParallelism(2);
        map.print("map").setParallelism(2);

//        socketDS.keyBy(data -> data).print("keyby");
        //随机分区
//        socketDS.shuffle().print("shuffer");
        //rebalance map1是先找一个开始分区,比如5,然后 45-12345-12345  map2:345-12345-12345
        map.rebalance().print("rebalance");
        //rescale 是先将分区平分,然后数据在每个分区内轮询。map1: 45-345-345 map2:12-12-12-12
        map.rescale().print("rescale");
        //并行度是1
//        socketDS.global().print("gloal");
        //此处会报错,8个并行度往socket一个并行度发会报错 forward 上下算子的并行度要相同
        //socketDS.forward().print("forward");
        //broadcast发往下游所有分区,每个一份
//        socketDS.broadcast().print("broadcast");
        //自定义重分区
        env.execute();
    }
}

③控制流的算子

split切分后类型不可变,output侧输出流类型是可变的


split(数据类型不可变)和select:可以使用output侧输出流(数据类型可以改变)代替他

将流中数根据某一特征划分,给流打上标记,然后根据select能筛选出被打上指定标记的流(1.12已移除)
connect:
允许连接两个不同类型的流(String,Integer),只能连接两个流,连接后依然保持着各自的数据类型。
union:只能连接相同类型的流,可以连接2个以上甚至多个流。
返回的是ConnectedStreams类型,后面可以调用map,flatmap,process方法。
join:经常配合开窗操作
connect算子
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DataStreamSource<Integer> intStream = env.fromElements(1, 2, 3, 4, 5);
        DataStreamSource<String> stringStream = env.fromElements("a", "b", "c");
ConnectedStreams<Integer, String> connect = intStream.connect(stringStream);
        // TODO ConnectedStreams 的map flatmap需要传CoMapFunction
        connect.map(new CoMapFunction<Integer, String, Object>() {
            @Override
            public Object map1(Integer value) throws Exception {
                return value;
            }

            @Overrides
            public Object map2(String value) throws Exception {
                return value;
            }
        });
④聚合算子
集合算子要在keyBy之后
常见的滚动聚合算子
sum, min,max,minBy,maxBy
如果流中存储的是POJO或者scala的样例类, 参数使用字段名
如果流中存储的是元组, 参数就是位置(基于0...)
reduce

//max只改变当前统计的这一个字段,其他字段会保持第一次输出的字段值不变
SingleOutputStreamOperator<WaterSensor> vc = keyedDS.max("vc");
//maxBy除了改变统计字段取最大值,如果两条数据最大值相同,其他值不同,其他字段的值通过后一个参数设置
//first参数:不是是取第一个
如果是false:取本条数据的字段,若是TRUE,取上一个最大值中的字段
SingleOutputStreamOperator<WaterSensor> vc1 = keyedDS.maxBy("vc",first=false);

reduce:
如果WaterSensor 温度和时间都要最大的:可使用reduce更灵活
SingleOutputStreamOperator<WaterSensor> reduceDS = keyedDS.reduce(new ReduceFunction<WaterSensor>() {
            @Override
            public WaterSensor reduce(WaterSensor value1, WaterSensor value2) throws Exception {
                //id:keyby后的id都一样,随便取其中一个,ts:取最大时间 vc:取最大水位
                return new WaterSensor(value1.getId(), value2.getTs(), Math.max(value1.getVc(), value2.getVc()));
            }
        });

⑤富函数
示例:
RichMapFunction:
1、open close生命周期方法:
程序在和其他第三方数据库有交互时使用,全局在每个并行度上只调用一次。
比如要在map中读完kafka数据写到mysql,map每次处理一条数据都创建关闭一次连接,可以使用声明周期方法中的open(创建连接方法) close(关闭连接方法)
2、getRuntimeContext() 获取运行时上下文:
可以做状态编程

⑥ process
最后process算子:
其他算子都搞不定,可以用最底层的process算子
package operateFunction;

import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimerService;
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 org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;

public class TransformProcess {
   
    public static void main(String[] args) throws Exception {
   
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(5);
        DataStreamSource<String> sockDS = env.socketTextStream("linux1", 9999);
        //process 实现flapmap功能
        SingleOutputStreamOperator<String> flatMapDS = sockDS.process(new MyFlatmapProcess());
        //process 实现pmap功能
        SingleOutputStreamOperator<Tuple2<String, Integer>> wordToOneDS =
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值