Flink-CDC 2.0学习


前言

实时数仓项目用到了Flink-CDC,这里记录一下学习的过程。


一、CDC简介

1.什么是CDC

CDC是Change Data Capture(变更数据获取)的简称。核心思想是监测并捕获数据库的变动包括数据或者数据表的插入、更新以及删除等),将这些变更按发生的完成顺序完整记录下来,写入到消息中间件以供其他服务进行订阅及消费。

2.CDC的种类

CDC主要分为基于查询和基于Binlog两种方式,如下表:

基于查询的CDC基于Binlog的CDC
开源产品Sqoop、Kafka JDBC SourceCanal、Maxwell、Debezium
执行模式BatchStreaming
是否可以捕获所有数据变化否(只能查询出最终结果)是(一条数据多次修改的中间过程也可以捕获)
延迟性高延迟(攒成一批的,延迟高)低延迟(类似于流式的,一条一条,延迟低)
是否增加数据库压力是(因为要select表)否 (是基于文件的)

3.Flink-CDC开源地址

开源地址:https://github.com/ververica/flink-cdc-connectors

二、Flink-CDC案例实操

1.依赖导入

使用Flink-CDC需要先导入以下依赖:

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

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

    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients_2.12</artifactId>
        <version>1.12.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>3.1.3</version>
    </dependency>

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

<dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-planner-blink_2.12</artifactId>
        <version>1.12.0</version>
</dependency>

<dependency>
        <groupId>com.ververica</groupId>
        <artifactId>flink-connector-mysql-cdc</artifactId>
        <version>2.0.0</version>
</dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.75</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.0.0</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2.DataStream方式编写代码

public class FlinkCDC {
    public static void main(String[] args) throws Exception {
        //1、创建流式执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        //2、Flink-CDC将读取binlog的位置信息以状态的方式保存在CheckPoint

        //2.1 所以需要开启检查点,这里开启checkpoint,每隔5s做一次
        env.enableCheckpointing(5000L);
        //2.2 指定checkpoint的语义
        env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
        //2.3 设置任务关闭的时候保留最后一次checkpoint的数据
        env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
        //2.4 指定从Checkpoint自动重启策略
        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3,2000L));
        //2.5 设置状态后端
        env.setStateBackend(new FsStateBackend("hdfs://hadoop102:8020/flinkCDC"));
        //2.6 设置访问HDFS的用户名
        System.setProperty("HADOOP_USER_NAME","atguigu");


        //3、创建Flink-MySQL-CDC的Source
        DebeziumSourceFunction<String> mysqlSource = MySqlSource.<String>builder()
                .hostname("hadoop102")
                .port(3306)
                .username("root")
                .password("000000")
                .databaseList("gmall-flink")
                .tableList("gmall-flink.z_user_info")  //可选配置,默认读取数据库中所有的表
                .startupOptions(StartupOptions.initial())
                .deserializer(new StringDebeziumDeserializationSchema())
                .build();

        DataStreamSource<String> mysqlDataStream = env.addSource(mysqlSource);

        mysqlDataStream.print();

        env.execute();
    }
}

3.StartupOptions参数

3.1 initial

会先基于查询读取mysql表中现有的数据,然后会切换到binlog模式到最新的位置开始读取新增及变化的数据。
在这里插入图片描述
英文翻译:
在第一次启动时对被监视的数据库表执行初始快照,并继续读取最新的binlog。

什么是第一次?
从保存点或检查点恢复叫做不是第一次(不需要先把表中现有的数据读取出来),其余情况都叫做第一次。第一次都需要把表中现有的数据读取出来。

3.2 earliest

会从binlog开始的位置,从表开始创建读取表中的数据。
如果要使用这个,我们必须先开启某个库某个表的binlog,然后再去建表,也就是说binlog要包含从建表开始的所有信息。
在这里插入图片描述
英文翻译:
永远不要在第一次启动时对监视的数据库表执行快照,只从binlog的开始读取。应该小心使用,因为只有当binlog保证包含数据库的整个历史记录时,它才有效。

3.3 latest

直接从binlog最新的位置开始读取数据,也就是启动连接器连接到了以后。
在这里插入图片描述
英文翻译:
永远不要在第一次启动时对被监视的数据库表执行快照,只从binlog的末尾读取,这意味着只有自连接器启动以来的更改。

4.Flink SQL方式编写代码

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

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        //使用Flink SQL方式构建CDC表
        tableEnv.executeSql("CREATE TABLE user_info ( " +
                "id STRING primary key, " +
                "name STRING, " +
                "sex STRING " +
                ") WITH ( " +
                "'connector'='mysql-cdc', " +
                "'scan.startup.mode'='latest-offset', " +
                "'hostname'='hadoop102', " +
                "'port'='3306', " +
                "'username'='root', " +
                "'password'='000000', " +
                "'database-name'='dc_test', " +
                "'table-name'='user_info' " +
                ")");
        Table table = tableEnv.sqlQuery("select * from user_info");
        DataStream<Tuple2<Boolean, Row>> retractStream = tableEnv.toRetractStream(table, Row.class);
        retractStream.print();

        env.execute();
    }
}

注意:
1)这里使用的是Flink-CDC2.0版本,所以需要使用1.13版本的Flink,否则会报错
2)Flink SQL方式创建表时,只支持监控单个表
3)Flink SQL中不需要指定序列化器
4)Flink SQL中可以用如下参数指定对应的执行模式(默认为initial,还可以指定为latest-offset):
在这里插入图片描述

5.自定义反序列化器

自动生成的数据格式:
在这里插入图片描述
定义序列化器:

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

        DebeziumSourceFunction<String> sourceFunction = MySqlSource.<String>builder()
                .hostname("hadoop102")
                .port(3306)
                .username("root")
                .databaseList("gmall_flink")
                .tableList("gmall_flink.z_user_info")
                .startupOptions(StartupOptions.initial())
                .deserializer(new MyDeserializer())
                .build();


        env.execute();
    }

    public static class MyDeserializer implements DebeziumDeserializationSchema<String>{

        /*自定义的返回类型的格式
         *{
         *  "db":"",
         *  "tableName":"",
         *  "before":{"id":"1001","name":""...},
         *  "after":{"id":"1001","name":""...},
         *  "op":""
         * }
         */


        @Override
        public void deserialize(SourceRecord sourceRecord, Collector<String> collector) throws Exception {
            //创建JSON对象用于封装结果数据
            JSONObject result = new JSONObject();

            //获取库名&表名
            String topic = sourceRecord.topic();
            String[] fields = topic.split("\\.");
            result.put("db",fields[1]);
            result.put("tableName",fields[2]);

            //获取before数据
            Struct value = (Struct) sourceRecord.value();
            Struct before = value.getStruct("before");
            JSONObject beforeJson = new JSONObject();
            if(before!=null){
                //如果before里面有数据,就获取对应的数据
                //先获取列名
                Schema schema = before.schema();
                List<Field> fieldList = schema.fields();
                for (Field field : fieldList) {
                    beforeJson.put(field.name(),before.get(field));
                }
            }
            //如果before里面为空,就直接返回一个空的jsonObject
            result.put("before",beforeJson);

            //获取after数据,同理
            Struct after = value.getStruct("after");
            JSONObject afterJson = new JSONObject();
            if(after!=null){
                //如果after里面有数据,就获取对应的数据
                //先获取列名
                Schema schema = after.schema();
                List<Field> fieldList = schema.fields();
                for (Field field : fieldList) {
                    afterJson.put(field.name(),after.get(field));
                }
            }
            //如果before里面为空,就直接返回一个空的jsonObject
            result.put("after",afterJson);

            //获取op,READ DELETE UPDATE CREATE
            Envelope.Operation op = Envelope.operationFor(sourceRecord);
            result.put("op",op);

            //输出数据
            collector.collect(result.toJSONString());
        }

        @Override
        public TypeInformation<String> getProducedType() {
            return BasicTypeInfo.STRING_TYPE_INFO;
        }
    }
}

运行结果:
在这里插入图片描述
注意:
1)在获取before和after时,要先判断是否为空,然后再获取列名,最后才能获取对应列的数据。
2)获取op的方法:Envelope.operationFor(sourceRecord);

三、Flink-CDC 2.0

1. Flink-CDC 1.x存在的问题

在这里插入图片描述

2. Flink-CDC 2.0的改进

在这里插入图片描述

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值