flink-基于 Table API 实现实时报表
Apache Flink提供了一个表API作为批处理和流处理的统一的关系API,也就是说,查询在无界、实时流或有界的批处理数据集上以相同的语义执行,并产生相同的结果。 Flink中的Table API通常用于简化数据分析、数据管道和ETL应用程序的定义。
在这个例子中,将学习如何构建实时仪表板,以按帐户跟踪金融交易。 管道将从Kafka读取数据,并通过Grafana将结果写入MySQL可视化。
可能遇到的问题:
如果在Windows上运行docker和你的数据生成器容器启动失败,那么请确保你正在使用正确的shell。 例如,table-walkthrough_data-generator_1容器的docker-entrypoint.sh需要bash。 如果不可用,它将抛出standard_init_linux错误。 执行:211:exec用户进程导致“没有这样的文件或目录”。 一个解决办法是在docker-entrypoint.sh的第一行将shell切换到sh。
所需的配置文件可以在flink-playground存储库中找到。 下载完成后,在IDE中打开项目flink-playground/table-walkthrough,并导航到文件spenreport。
1 EnvironmentSettings settings = EnvironmentSettings.inStreamingMode(); 2 TableEnvironment tEnv = TableEnvironment.create(settings); 3 4 tEnv.executeSql("CREATE TABLE transactions (\n" + 5 " account_id BIGINT,\n" + 6 " amount BIGINT,\n" + 7 " transaction_time TIMESTAMP(3),\n" + 8 " WATERMARK FOR transaction_time AS transaction_time - INTERVAL '5' SECOND\n" + 9 ") WITH (\n" + 10 " 'connector' = 'kafka',\n" + 11 " 'topic' = 'transactions',\n" + 12 " 'properties.bootstrap.servers' = 'kafka:9092',\n" + 13 " 'format' = 'csv'\n" + 14 ")"); 15 16 tEnv.executeSql("CREATE TABLE spend_report (\n" + 17 " account_id BIGINT,\n" + 18 " log_ts TIMESTAMP(3),\n" + 19 " amount BIGINT\n," + 20 " PRIMARY KEY (account_id, log_ts) NOT ENFORCED" + 21 ") WITH (\n" + 22 " 'connector' = 'jdbc',\n" + 23 " 'url' = 'jdbc:mysql://mysql:3306/sql-demo',\n" + 24 " 'table-name' = 'spend_report',\n" + 25 " 'driver' = 'com.mysql.jdbc.Driver',\n" + 26 " 'username' = 'sql-demo',\n" + 27 " 'password' = 'demo-sql'\n" + 28 ")"); 29 30 Table transactions = tEnv.from("transactions"); 31 report(transactions).executeInsert("spend_report");
代码解析:
执行环境
前两行设置了TableEnvironment。 表环境是您为作业设置属性、指定是编写批处理应用程序还是流应用程序以及创建源的方式。 此演练将创建一个使用流执行的标准表环境。
EnvironmentSettings settings = EnvironmentSettings.inStreamingMode();
TableEnvironment tEnv = TableEnvironment.create(settings);
注册table
接下来,在当前编目中注册表,您可以使用这些表连接到外部系统,以便读写批处理和流数据。 表源提供对存储在外部系统中的数据的访问,例如数据库、键值存储、消息队列或文件系统。 表汇向外部存储系统发送表。 根据源和接收器的类型,它们支持不同的格式,如CSV、JSON、Avro或Parquet。
tEnv.executeSql("CREATE TABLE transactions (\n" +
" account_id BIGINT,\n" +
" amount BIGINT,\n" +
" transaction_time TIMESTAMP(3),\n" +
" WATERMARK FOR transaction_time AS transaction_time - INTERVAL '5' SECOND\n" +
") WITH (\n" +
" 'connector' = 'kafka',\n" +
" 'topic' = 'transactions',\n" +
" 'properties.bootstrap.servers' = 'kafka:9092',\n" +
" 'format' = 'csv'\n" +
")");
注册两个表; 一个事务输入表,一个支出报告输出表。 事务(transactions)表允许我们读取信用卡事务,其中包含帐户ID (account_id)、时间戳(transaction_time)和美元金额(amount)。 这个表是一个包含了CSV数据的Kafka主题事务的逻辑视图。
tEnv.executeSql("CREATE TABLE spend_report (\n" +
" account_id BIGINT,\n" +
" log_ts TIMESTAMP(3),\n" +
" amount BIGINT\n," +
" PRIMARY KEY (account_id, log_ts) NOT ENFORCED" +
") WITH (\n" +
" 'connector' = 'jdbc',\n" +
" 'url' = 'jdbc:mysql://mysql:3306/sql-demo',\n" +
" 'table-name' = 'spend_report',\n" +
" 'driver' = 'com.mysql.jdbc.Driver',\n" +
" 'username' = 'sql-demo',\n" +
" 'password' = 'demo-sql'\n" +
")");
第二个表spend_report存储聚合的最终结果。 它的底层存储是MySql数据库中的一个表。
查询
配置了环境并注册了表之后,就可以开始构建第一个应用程序了。 在TableEnvironment中,可以从输入表中读取数据,然后使用executeInsert将这些结果写入输出表。 报告功能是实现业务逻辑的地方。 它目前尚未实现。
Table transactions = tEnv.from("transactions");
report(transactions).executeInsert("spend_report");
Flink的一个独特属性是,它跨批处理和流提供一致的语义。 这意味着您可以在静态数据集上以批处理模式开发和测试应用程序,并将其作为流应用程序部署到生产中。
现在有了Job设置的框架,就可以添加一些业务逻辑了。 目标是构建一个报告,显示每个帐户在一天中每个小时的总花费。 这意味着时间戳列需要从毫秒到小时的粒度进行四舍五入。
业务处理
Flink支持用纯SQL或使用Table API开发关系应用程序。 Table API是受SQL启发的流畅DSL,可以用Python、Java或Scala编写,并支持强大的IDE集成。 就像SQL查询一样,Table程序可以根据键选择所需的字段和分组。 这些特性,以及像floor和sum这样的内置函数,都可以编写这个报告。
public static Table report(Table transactions) { return transactions.select( $("account_id"), $("transaction_time").floor(TimeIntervalUnit.HOUR).as("log_ts"), $("amount")) .groupBy($("account_id"), $("log_ts")) .select( $("account_id"), $("log_ts"), $("amount").sum().as("amount")); }
用户定义函数
Flink包含数量有限的内置函数,有时需要使用用户定义的函数对其进行扩展。 如果没有预先定义楼层,您可以自己实现它。
import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import org.apache.flink.table.annotation.DataTypeHint; import org.apache.flink.table.functions.ScalarFunction; public class MyFloor extends ScalarFunction { public @DataTypeHint("TIMESTAMP(3)") LocalDateTime eval( @DataTypeHint("TIMESTAMP(3)") LocalDateTime timestamp) { return timestamp.truncatedTo(ChronoUnit.HOURS); } }
然后快速地将其集成到应用程序中。
public static Table report(Table transactions) { return transactions.select( $("account_id"), call(MyFloor.class, $("transaction_time")).as("log_ts"), $("amount")) .groupBy($("account_id"), $("log_ts")) .select( $("account_id"), $("log_ts"), $("amount").sum().as("amount")); }
该查询使用事务表中的所有记录,计算报告,并以一种高效、可伸缩的方式输出结果。 使用该实现运行测试将通过。
添加窗口函数
基于时间的数据分组是数据处理中的一种典型操作,特别是在处理无限流时。 基于时间的分组称为窗口,Flink提供了灵活的窗口语义。 最基本的窗口类型称为Tumble窗口,它有固定的大小,并且桶不重叠。
public static Table report(Table transactions) { return transactions .window(Tumble.over(lit(1).hour()).on($("transaction_time")).as("log_ts")) .groupBy($("account_id"), $("log_ts")) .select( $("account_id"), $("log_ts").start().as("log_ts"), $("amount").sum().as("amount")); }
这将您的应用程序定义为基于时间戳列使用一个小时的滚动窗口。 因此,时间戳为2019-06-01 01:23:47的行被放入2019-06-01 01:00:00窗口。
基于时间的聚合是惟一的,因为与其他属性不同,时间通常在连续流应用程序中向前移动。 与楼层和UDF不同,窗口函数是内在的,它允许运行时应用额外的优化。 在批处理上下文中,窗口提供了一个方便的API,通过时间戳属性将记录分组。
使用该实现运行测试也将通过。
再一次,流式计算!
这就是一个功能齐全、有状态的分布式流应用程序! 查询不断地消耗来自Kafka的事务流,计算每小时的开销,并在它们准备好时立即发出结果。 因为输入是无界的,所以查询会一直运行,直到手动停止为止。 因为Job使用基于时间窗口的聚合,所以当框架知道不再有记录到达某个特定窗口时,Flink可以执行特定的优化,比如状态清理。
表游乐场是完全dockerized的,并且可以作为流应用程序在本地运行。 该环境包含一个Kafka主题,一个连续数据生成器,MySql和Grafana。
从表遍历文件夹中启动docker-compose脚本。

查询mysql结果
$ docker-compose exec mysql mysql -Dsql-demo -usql-demo -pdemo-sql mysql> use sql-demo; Database changed mysql> select count(*) from spend_report; +----------+ | count(*) | +----------+ | 110 | +----------+
Finally, go to Grafana to see the fully visualized result!

项目pom依赖
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-sql-connector-kafka_2.12</artifactId>
<version>1.12.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc_2.12</artifactId>
<version>1.12.0</version>
<scope>provided</scope>
</dependency>
部署执行注意事项
1,需将相关依赖jar包上传至flink安装目录lib文件夹下,或项目打包时将依赖包一起打包
2,如依赖jar包有上传,但还是出现classNotFound异常,可以修改flink/bin目录下的config.sh的如下变量值,新增依赖jar包目录
INTERNAL_HADOOP_CLASSPATHS="${HADOOP_CLASSPATH}:${HADOOP_CONF_DIR}:${YARN_CONF_DIR}
修改为
INTERNAL_HADOOP_CLASSPATHS="${HADOOP_CLASSPATH}:${HADOOP_CONF_DIR}:${YARN_CONF_DIR}:/opt/flink-1.12.0/lib/"
3,kafka消息格式为csv格式:5,1,2022-02-02 00:00:00
浙公网安备 33010602011771号