【Flink】DataStream API使用之输出算子(Sink)

输出算子(Sink)

在这里插入图片描述
Flink作为数据处理框架,最终还是需要把计算处理的结果写入到外部存储,为外部应用提供支持。Flink提供了很多方式输出到外部系统。

1. 连接外部系统

Flink中我们可以在各种Fuction中处理输出到外部系统,但是Flink作为一个快速的分布式实时流处理系统,对稳定性和容错性要求极高。一旦出现故障,我们应该有能力恢复之前的状态,保障处理结果的正确性。这种性质一般被称为"状态一致性"。Flink内部提供了一致性检查点checkpoint来保障我们可以回滚到正确的状态,但是我们在处理过程中任意读写外部系统,发生故障后就很难回退到从前了。

所以FlinkDataStream API提供了专门向外部写入数据的方法,通过addSink实现,与addSource类似,addSink方法对应着一个Sink算子,主要就是来实现与外部系统连接,并将数据提交写入;Flink程序中所有对外的输出操作一般都是利用Sink算子完成的。比如我们经常使用的print 方法返回的就是一个 DataStreamSink

Sink算子的创建主要是通过DataSream的.addSink()实现的,并且需要重写default void invoke(IN value, Context context) throws Exception 方法

Flink提供的连接器,这个是1.17版本的,比1.13版本的多很多
在这里插入图片描述
除了官方的,Flink也可以使用Apache Bahir的扩展连接器

在这里插入图片描述

2. 输出到文件

输出文件Flink有writeAsText()writeAsCsv()可以直接输出到文件,但是这种不支持同时写一份文件,必须设定为并行度1,所以Flink又提供了一个专门的流式文件系统的连接器StreamingFileSink

SreamingFileSink继承自抽象类RichSinkFunction,而且集成Flink检查点机制(checkpoint)用来保证精确一次的一致性语义StreamingFileSInk主要操作是将数据写入桶,每个桶中的数据都可以分割成一个个大小有限的分区文件,并且也可以通过各种配置来控制分桶的操作;默认的分桶方式是基于时间的。

StreamingFileSink(Row-encoded)支持行编码批量编码(Bulk-encoded,比如 Parquet)格式,这两种不同的方式都有各自的构建器:

  • 行编码:StreamingFileSink.forRowFormat(basePath,rowEncoder)
  • 批量编码:StreamingFileSink.forBulkFormat(basePath,bulkWriterFactory)

代码实例:

public static void main(String[] args) throws Exception {
        // 1. 直接调用getExecutionEnvironment 方法,底层源码可以自由判断是本地执行环境还是集群的执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(2);
        // 2. 从集合中读取数据
        ArrayList<Event> list = new ArrayList<>();
        list.add(new Event("ming","www.baidu1.com",1200L));
        list.add(new Event("xiaohu","www.baidu2.com",1200L));
        list.add(new Event("xiaohu","www.baidu5.com",1267L));
        list.add(new Event("gala","www.baidu6.com",1200L));
        list.add(new Event("ming","www.baidu7.com",4200L));
        list.add(new Event("xiaohu","www.baidu8.com",5500L));
        // 3. 读取数据
        DataStreamSource<Event> eventDataStreamSource = env.fromCollection(list, BasicTypeInfo.of(Event.class));
        // 4. 构建File Sink
        StreamingFileSink<String> streamingFileSink = StreamingFileSink.<String>forRowFormat(new Path("./out"), new SimpleStringEncoder<>("UTF-8"))
                .withRollingPolicy(
                        DefaultRollingPolicy.builder()
                                .withInactivityInterval(TimeUnit.MINUTES.toMillis(5))
                                .withRolloverInterval(TimeUnit.MINUTES.toMillis(15))
                                .withMaxPartSize(1024 * 1024 * 1024)
                                .build()
                ).build();
        eventDataStreamSource.map(Event::toString).addSink(streamingFileSink);
        // 5. 执行程序
        env.execute();
    }

这里设置了并行度是2,所以是两个桶文件。通过.withRollingPolicy()方法指定滚动策略,策略配置说明:

  • withInactivityInterval : 最近 5 分钟没有收到新的数据
  • withRolloverInterval : 至少包含 15 分钟的数据
  • withMaxPartSize : 文件大小已达到 1 GB
    在这里插入图片描述

3. 输出到Kafka

Flink 与 Kafka 的连接器提供了端到端的精确一次(exactly once)语义保证,这在实际项目中是最高级别的一致性保证。
代码实例:

public static void main(String[] args) throws Exception {
        // 1. 直接调用getExecutionEnvironment 方法,底层源码可以自由判断是本地执行环境还是集群的执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 2. 设置属性
        Properties properties = new Properties();
        properties.put("bootstrap.servers","hadoop102:9092");
        // 3. 读取数据
        DataStreamSource<String> stringDataStreamSource = env.readTextFile("input/clicks.csv");
        // 4. 构建File Sink
        FlinkKafkaProducer<String> kafkaProducer = new FlinkKafkaProducer<>("clicks", new SimpleStringSchema(), properties);
        // 5. addSink
        stringDataStreamSource.addSink(kafkaProducer);
        // 6. 执行程序
        env.execute();
    }

addSink 传入的参数是一个 FlinkKafkaProducerFlinkKafkaProducer继承了抽象类TwoPhaseCommitSinkFunction,这是一个实现了两阶段提交的RichSinkFuction,两阶段提交提供了FlinkKafka写入数据的事务性保证,能够真正做到精确一次的状态一致性

4. 自定义Sink输出

如果Flink提供的Sink不满足自己的要求,也可以通过自定义Sink来满足自己的要求,通过Flink提供的SinkFuction接口和对应的RichSinkFuction抽象类重写invoke()就可以自定义Sink

这里以Hbase为例,使用RichSinkFuction,创建Hbase的连接以及关闭Hbase的连接分别放到openclose方法中。

代码实例:

public static void main(String[] args) throws Exception {
        // 1. 直接调用getExecutionEnvironment 方法,底层源码可以自由判断是本地执行环境还是集群的执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 2. 设置属性
        Properties properties = new Properties();
        properties.put("bootstrap.servers","hadoop102:9092");
        // 3. 读取数据
        DataStreamSource<String> stringDataStreamSource = env.readTextFile("input/clicks.csv");
        // 4. 构建File Sink
        stringDataStreamSource.addSink(new RichSinkFunction<String>() {

            public Configuration configuration; // 管理 Hbase 的配置信息,这里因为 Configuration 的重名问题,将类以完整路径
            public Connection connection; // 管理 Hbase 连接

            @Override
            public void open(Configuration parameters) throws Exception {
                super.open(parameters);
                configuration = HBaseConfiguration.create();
                configuration.set("hbase.zookeeper.quorum", "hadoop102:2181");
                connection = ConnectionFactory.createConnection(configuration);

            }

            @Override
            public void close() throws Exception {
                super.close();
                connection.close(); // 关闭连接

            }

            @Override
            public void invoke(String value, Context context) throws Exception {
                Table table = connection.getTable(TableName.valueOf("test")); // 表名为 test
                Put put = new Put("rowkey".getBytes(StandardCharsets.UTF_8)); // 指定 rowkey
                put.addColumn("info".getBytes(StandardCharsets.UTF_8) // 指定列名
                        , value.getBytes(StandardCharsets.UTF_8) // 写入的数据
                        , "1".getBytes(StandardCharsets.UTF_8)); // 写入的数据
                table.put(put); // 执行 put 操作
                table.close(); // 将表关闭
            }
        });
        // 6. 执行程序
        env.execute();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用 Flink DataStream API 分析 MySQL 表并将结果写入另一个 MySQL 表的示例程序: ```java import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple.Tuple; import org.apache.flink.api.java.tuple.Tuple2; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.sink.SinkFunction; import org.apache.flink.streaming.connectors.jdbc.JdbcConnectionOptions; import org.apache.flink.streaming.connectors.jdbc.JdbcSink; import org.apache.flink.streaming.connectors.jdbc.JdbcStatementBuilder; import org.apache.flink.streaming.connectors.jdbc.JdbcTableSink; import org.apache.flink.streaming.connectors.jdbc.JdbcWriter; import org.apache.flink.streaming.connectors.jdbc.JdbcWriterBuilder; import org.apache.flink.streaming.connectors.jdbc.JdbcUpsertTableSink; import org.apache.flink.streaming.connectors.jdbc.JdbcUpsertTableSinkBuilder; import org.apache.flink.streaming.connectors.jdbc.JdbcUtils; import org.apache.flink.table.api.Table; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import org.apache.flink.table.descriptors.ConnectorDescriptor; import org.apache.flink.table.descriptors.Jdbc; import org.apache.flink.table.descriptors.Schema; import org.apache.flink.types.Row; import java.sql.PreparedStatement; import java.sql.SQLException; public class MySQLTableAnalysis { public static void main(String[] args) throws Exception { // 设置执行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env); // 定义 MySQL 连接信息 String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; // 定义输入表的连接器描述符 Jdbc jdbc = new Jdbc() .url(url) .username(username) .password(password) .table("input_table") .lookupOptions(JdbcOptions.builder() .setFetchSize(1000) .build()); // 定义输出表的连接器描述符 Jdbc outputJdbc = new Jdbc() .url(url) .username(username) .password(password) .table("output_table"); // 注册输入表 tableEnv .connect(jdbc) .withFormat(new Csv()) .withSchema(new Schema() .field("id", DataTypes.INT()) .field("name", DataTypes.STRING()) .field("age", DataTypes.INT())) .createTemporaryTable("input_table"); // 读取输入表并进行分析 Table inputTable = tableEnv.from("input_table"); Table resultTable = inputTable .groupBy("name") .select("name, count(id) as cnt"); // 将结果转换为 DataStream<Tuple2<String, Integer>> DataStream<Tuple2<String, Integer>> resultStream = tableEnv.toRetractStream(resultTable, Row.class) .map(new MapFunction<Tuple2<Boolean, Row>, Tuple2<String, Integer>>() { @Override public Tuple2<String, Integer> map(Tuple2<Boolean, Row> value) throws Exception { String name = value.f1.getField(0).toString(); Integer cnt = Integer.parseInt(value.f1.getField(1).toString()); return Tuple2.of(name, cnt); } }); // 定义 KeySelector 和 SinkFunction KeySelector<Tuple2<String, Integer>, String> keySelector = new KeySelector<Tuple2<String, Integer>, String>() { @Override public String getKey(Tuple2<String, Integer> value) throws Exception { return value.f0; } }; SinkFunction<Tuple2<String, Integer>> sinkFunction = new SinkFunction<Tuple2<String, Integer>>() { @Override public void invoke(Tuple2<String, Integer> value, Context context) throws Exception { String sql = "INSERT INTO output_table (name, cnt) VALUES (?, ?) ON DUPLICATE KEY UPDATE cnt = ?"; JdbcUtils.write( value, PreparedStatement::setString, PreparedStatement::setInt, new int[]{3}, sql, JdbcConnectionOptions.builder() .withUrl(url) .withUsername(username) .withPassword(password) .build()); } }; // 将结果写入输出表 JdbcUpsertTableSink<Tuple2<String, Integer>> jdbcSink = JdbcUpsertTableSink.builder() .setOptions(outputJdbc) .setFlushIntervalMills(5000) .setKeyFields(new String[]{"name"}) .build(); resultStream .addSink(jdbcSink) .name("MySQL Upsert Sink"); // 执行程序 env.execute("MySQL Table Analysis"); } } ``` 在这个示例程序中,我们首先定义了输入表和输出表的连接信息,并使用 Flink Table API 定义了输入表的结构。然后,我们使用 Flink Table API 计算了输入表的分析结果,并将结果转换为 DataStream。最后,我们使用 Flink JDBC Upsert Table Sink 将结果写入输出表。 请注意,这个示例程序假设输入表和输出表都已经在 MySQL 中存在,并且输出表具有一个名为“name”的唯一索引。如果您需要创建表,请使用 SQL DDL 语句或其他适当的工具创建表。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值