Flink-Table StreamTableEnvironment实践编程(四)上

本文深入探讨了Flink StreamTableEnvironment的应用,通过实战演示如何利用socket连接获取数据流,进行学生分数统计。详细介绍了AppendStreamTableSink、RetractStreamTableSink和UpsertStreamTableSink的使用场景及差异,附带代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇文章中掌握了StreamTableEnvironment的基本概念,下面就进入代码实战环节:

一、实例中采用socket连接方式,获取数据流信息,使用 nc -l 端口号方式,开启端口监控,用来数据数据信息,实例中使用9999作为端口,命令行中输入:nc -l 9999
二、本实例主要功能是完成根据学生姓名实现分数统计
三、具体依赖实体类如下:
    @Data
    public class StudentInfo {
        private String name;
        private String sex;
        private String course;
        private Float score;
        private Long timestamp;
    }
    @Data
    public class StudentScoreResult {
        public String name;
        public float sum_total_score;
        public StudentScoreResult() {}
    }
四、流式tablesink包含三种方式
    1、AppendStreamTableSink:定义外部表链接,以便仅通过插入更改发出流式处理表。如果更新或删除更改也修改了表,则将引发TableException
    2、RetractStreamTableSink:定义外部表链接以发出包含插入、更新和删除更改的流式处理表。表将被转换为累加和收回消息流,这些消息被编码为Java Tuple2。第一个字段是指示消息类型的布尔标志(true表示插入,false表示删除)。第二个字段保存请求类型的记录
    3、UpsertStreamTableSink:定义外部表链接以发出包含插入、更新和删除更改的流式处理表。表必须具有唯一的键字段(原子或复合)或只能追加。如果表没有唯一键且不是仅追加,则将引发TableException。表的唯一键由upsertstreamtablelink#setKeyFields()方法配置。表将被转换为upsert和delete消息流,这些消息被编码为Java Tuple2。第一个字段是指示消息类型的布尔标志。第二个字段保存请求的类型T的记录。带有真布尔字段的消息是配置的密钥的upsert消息。带有false标志的消息是配置密钥的删除消息。如果表是append only,则所有消息都将有一个true标志,并且必须解释为insertions。

    RetractStreamTableSink中: Insert被编码成一条Add消息; Delete被编码成一条Retract消息;Update被编码成两条消息(先是一条Retract消息,再是一条Add消息),即先删除再增加。
    UpsertStreamTableSink: Insert和Update均被编码成一条消息(Upsert消息); Delete被编码成一条Delete消息。
    UpsertStreamTableSink和RetractStreamTableSink最大的不同在于Update编码成一条消息,效率上比RetractStreamTableSink高。

五、实现AppendStreamTableSink具体案例:

import com.springk.flink.bean.StudentInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.core.fs.FileSystem;
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.table.api.Table;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import org.apache.flink.table.sinks.CsvTableSink;
import org.apache.flink.table.sinks.StreamTableSink;
import org.apache.flink.util.Collector;

public class TableStreamFlinkStudentAppendTest {
    
    public static void main(String[] args) throws Exception {
 
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamTableEnvironment streamTableEnvironment = StreamTableEnvironment.create(env);
        
        //source,这里使用socket连接获取数据
        DataStreamSource<String> text = env.socketTextStream("127.0.0.1", 9999, "\n");
        
        //处理输入数据流,转换为StudentInfo类型,方便后续处理
        SingleOutputStreamOperator<StudentInfo> dataStreamStudent = text.flatMap(new FlatMapFunction<String, StudentInfo>() {
            @Override
            public void flatMap(String s, Collector<StudentInfo> collector){
                System.out.println(s);
                String infos[] = s.split(",");
                if(StringUtils.isNotBlank(s) && infos.length==4){
                    StudentInfo studentInfo = new StudentInfo();
                    studentInfo.setName(infos[0]);
                    studentInfo.setSex(infos[1]);
                    studentInfo.setCourse(infos[2]);
                    studentInfo.setScore(Float.parseFloat(infos[3]));
                    collector.collect(studentInfo);
                }
            }
        });
 
        //设置sink信息,对应字段,以及字段类型,案例中使用cvs写入
        String[] fieldNamesStream={"name","sex","course","score"}; //dataStreamStudent 所有数据append,不会做修改
        TypeInformation[] fieldTypesStream = {Types.STRING,Types.STRING, Types.STRING,Types.FLOAT};
        StreamTableSink tableSinkStream = new CsvTableSink("/Users/user/Documents/student-sink","  ",1, FileSystem.WriteMode.OVERWRITE);
        streamTableEnvironment.registerTableSink("studentinfostream", fieldNamesStream, fieldTypesStream, tableSinkStream);
        
        //一、写入sink有两种方式,一种如下琐事,使用table.insert
        //将dataStream转换为table
        Table table = streamTableEnvironment.fromDataStream(dataStreamStudent);
        table.insertInto("studentinfostream");
 
        //二、写入sink有两种方式,另外一种如下写入,使用registerDataStream,最后使用sqlUpdate实现,效果和第一种一样
        //注册dataStreamStudent流到表中,表名为:studentInfo
//        streamTableEnvironment.registerDataStream("studentInfo",dataStreamStudent,"name,sex,course,score");
//        streamTableEnvironment.sqlUpdate("insert into studentinfostream select name,sex,course,score from studentInfo");     env.execute("studentScoreInfo");
    }
}

    
    命令行中输入如下:
        
    student-sink中获取内容如下:
        
    上面实例只支持insert插入,当sink写入有修改的数据时,(例如:修改sql语句,变更为:insert into studentinfostream select name,sex,course,sum(score) as score from studentInfo group by name,sex,course
)会抛出异常:Exception in thread "main" org.apache.flink.table.api.TableException: AppendStreamTableSink requires that Table has only insert changes.

下一节继续

Flink 批流一体编程实践可以通过 Table/SQL API 和 DataStream/DataSet API 两种方式来实现。下面将分别介绍这两种方式的实现方法。 1. Table/SQL API 实现批流一体编程实践 Table/SQL API 是 Flink 提供的一种基于 SQL 的编程接口,可以将流处理和批处理统一起来。通过 Table/SQL API,用户可以使用 SQL 语句来操作流和批数据,从而实现批流一体的编程。 下面是一个使用 Table/SQL API 实现批流一体编程的示例代码: ```python from pyflink.table import StreamTableEnvironment, BatchTableEnvironment, EnvironmentSettings # 创建流处理环境 stream_env = StreamExecutionEnvironment.get_execution_environment() stream_env.set_parallelism(1) stream_table_env = StreamTableEnvironment.create(stream_env) # 创建批处理环境 batch_env = ExecutionEnvironment.get_execution_environment() batch_env.set_parallelism(1) batch_table_env = BatchTableEnvironment.create(batch_env) # 创建表 stream_table_env.execute_sql("CREATE TABLE source_table (id INT, name STRING) WITH ('connector' = 'kafka', 'topic' = 'source_topic', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json')") batch_table_env.execute_sql("CREATE TABLE sink_table (id INT, name STRING) WITH ('connector' = 'jdbc', 'url' = 'jdbc:mysql://localhost:3306/test', 'table-name' = 'sink_table', 'username' = 'root', 'password' = '123456', 'driver' = 'com.mysql.jdbc.Driver')") # 执行查询 result_table = stream_table_env.sql_query("SELECT id, name FROM source_table WHERE id > 10") result_table.insert_into("sink_table") # 执行作业 stream_table_env.execute("job_name") ``` 在上面的示例代码中,我们首先创建了一个流处理环境和一个批处理环境,然后分别使用 StreamTableEnvironment 和 BatchTableEnvironment 创建了对应的表环境。接着,我们使用 execute_sql() 方法创建了一个输入表和一个输出表,并使用 sql_query() 方法执行了一个查询操作,最后使用 insert_into() 方法将查询结果插入到输出表中。最后,我们使用 execute() 方法执行了整个作业。 2. DataStream/DataSet API 实现批流一体编程实践 除了 Table/SQL API,Flink 还提供了 DataStream/DataSet API 来实现批流一体编程。通过 DataStream/DataSet API,用户可以使用相同的 API 来操作流和批数据,从而实现批流一体的编程。 下面是一个使用 DataStream/DataSet API 实现批流一体编程的示例代码: ```python from pyflink.common.serialization import SimpleStringSchema from pyflink.datastream import StreamExecutionEnvironment from pyflink.datastream.connectors import FlinkKafkaConsumer from pyflink.dataset import ExecutionEnvironment # 创建流处理环境 stream_env = StreamExecutionEnvironment.get_execution_environment() stream_env.set_parallelism(1) # 创建批处理环境 batch_env = ExecutionEnvironment.get_execution_environment() batch_env.set_parallelism(1) # 创建数据源 stream_source = FlinkKafkaConsumer('source_topic', SimpleStringSchema(), {'bootstrap.servers': 'localhost:9092'}) batch_source = batch_env.from_elements([(1, 'a'), (2, 'b'), (3, 'c')]) # 执行流处理 stream_env.add_source(stream_source).filter(lambda x: int(x.split(',')[0]) > 10).print() # 执行批处理 batch_source.filter(lambda x: x[0] > 1).print() # 执行作业 stream_env.execute('job_name') ``` 在上面的示例代码中,我们首先创建了一个流处理环境和一个批处理环境,然后分别使用 FlinkKafkaConsumer 和 from_elements() 方法创建了对应的数据源。接着,我们使用 filter() 方法对数据进行过滤,并使用 print() 方法输出结果。最后,我们使用 execute() 方法执行了整个作业。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

springk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值