Flink Table API

Flink API架构:
在这里插入图片描述

Table API &SQL特点

Table API和SQL都是Apache Flink中最高层的分析API,SQL所具备的特点Table API也都具有,如下:

  • 声明式 - 用户只关心做什么,不用关心怎么做;
  • 高性能 - 支持查询优化,可以获取最好的执行性能;
  • 流批统一 - 相同的统计逻辑,既可以流模式运行,也可以批模式运行;
  • 标准稳定 - 语义遵循SQL标准,语法语义明确,不易变动。

当然除了SQL的特性,因为Table API是在Flink中专门设计的,所以Table API还具有自身的特点:

  • 表达方式的扩展性 - 在Flink中可以为Table API开发很多便捷性功能,如:Row.flatten(), map/flatMap
  • 功能的扩展性 - 在Flink中可以为Table API扩展更多的功能,如:Iteration,flatAggregate 等新功能
  • 编译检查 - Table API支持java和scala语言开发,支持IDE中进行编译检查。
    在这里插入图片描述

Table API编程

worldCount例子
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.java.BatchTableEnvironment;
import org.apache.flink.table.descriptors.FileSystem;
import org.apache.flink.table.descriptors.OldCsv;
import org.apache.flink.table.descriptors.Schema;
import org.apache.flink.types.Row;

public class JavaBatchWordCount {

	public static void main(String[] args) throws Exception {
		ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
		BatchTableEnvironment tEnv = BatchTableEnvironment.create(env);

		String path = JavaBatchWordCount.class.getClassLoader().getResource("words.txt").getPath();
		tEnv.connect(new FileSystem().path(path))
			.withFormat(new OldCsv().field("word", Types.STRING).lineDelimiter("\n"))
			.withSchema(new Schema().field("word", Types.STRING))
			.registerTableSource("fileSource");

		Table result = tEnv.scan("fileSource")
			.groupBy("word")
			.select("word, count(1) as count");

		tEnv.toDataSet(result, Row.class).print();
	}
}

关于里面用到的Environment,主要有以下几种:
在这里插入图片描述

如何得到一个table

1.Table descriptor
2.User defined
3.Register a DataStream
在这里插入图片描述

如何emit a table

在这里插入图片描述

Table API操作

在这里插入图片描述

Table API 算子

虽然Table API与SQL的算子语义一致,但在表达方式上面SQL以文本的方式展现,Table API是以java或者scala语言的方式进行开发。

SELECT

SELECT 用于从数据集/流中选择数据,语义是关系代数中的投影(Projection),对关系进行垂直分割,消去或增加某些列,
SELECT 不仅可以使用普通的字段选择,还可以使用ScalarFunction,当然也包括User-Defined Function,同时还可以进行字段的alias设置。

从customer_tab选择用户姓名,并用内置的CONCAT函数拼接客户信息,如下:

val result = customer
  .select('c_name, concat_ws('c_name, " come ", 'c_desc))
WHERE

WHERE 用于从数据集/流中过滤数据,与SELECT一起使用,语义是关系代数的Selection,根据某些条件对关系做水平分割,即选择符合条件的记录
WHERE是对满足一定条件的数据进行过滤,WHERE支持=, <, >, <>, >=, <=以及&&, ||等表达式的组合,最终满足过滤条件的数据会被选择出来。 SQL中的IN和NOT IN在Table API里面用intersect 和 minus描述(flink-1.7.0版本)

在customer_tab查询客户id为c_001和c_003的客户信息,如下:

 val result = customer
   .where("c_id = 'c_001' || c_id = 'c_003'")
   .select( 'c_id, 'c_name, 'c_desc)

Intersect
Intersect只在Batch模式下进行支持,Stream模式下我们可以利用双流JOIN来实现,如:在customer_tab查询已经下过订单的客户信息,如下:

// 计算客户id,并去重
 val distinct_cids = order
   .groupBy('c_id) // 去重
   .select('c_id as 'o_c_id)

 val result = customer
   .join(distinct_cids, 'c_id === 'o_c_id)
   .select('c_id, 'c_name, 'c_desc)

Minus
Minus只在Batch模式下进行支持,Stream模式下我们可以利用双流JOIN来实现,如:在customer_tab查询没有下过订单的客户信息,如下:

 // 查询下过订单的客户id,并去重
 val distinct_cids = order
   .groupBy('c_id)
   .select('c_id as 'o_c_id)
   
// 查询没有下过订单的客户信息
val result = customer
  .leftOuterJoin(distinct_cids, 'c_id === 'o_c_id)
  .where('o_c_id isNull)
  .select('c_id, 'c_name, 'c_desc)
  
GROUP BY

GROUP BY 是对数据进行分组的操作

将order_tab信息按c_id分组统计订单数量,简单示例如下:

val result = order
  .groupBy('c_id)
  .select('c_id, 'o_id.count)

在实际的业务场景中,GROUP BY除了按业务字段进行分组外,很多时候用户也可以用时间来进行分组(相当于划分窗口),比如统计每分钟的订单数量:
按时间进行分组,查询每分钟的订单数量,如下:

val result = order
  .select('o_id, 'c_id, 'o_time.substring(1, 16) as 'o_time_min)
  .groupBy('o_time_min)
  .select('o_time_min, 'o_id.count)
UNION ALL

UNION ALL 将两个表合并起来,要求两个表的字段完全一致,包括字段类型、字段顺序,语义对应关系代数的Union,只是关系代数是Set集合操作,会有去重复操作,UNION ALL 不进行去重

我们简单的将customer_tab查询2次,将查询结果合并起来,如下:

val result = customer.unionAll(customer)

UNION ALL 对结果数据不进行去重,如果想对结果数据进行去重,传统数据库需要进行UNION操作。

UNION

UNION 将两个流给合并起来,要求两个流的字段完全一致,包括字段类型、字段顺序,并其UNION 不同于UNION ALL,UNION会对结果数据去重,与关系代数的Union语义一致
我们简单的将customer_tab查询2次,将查询结果合并起来,如下:

val result = customer.union(customer)

完全一样的表数据进行 UNION之后,数据是被去重的,UNION之后的数据并没有增加。

JOIN

OIN 用于把来自两个表的行联合起来形成一个宽表,Apache Flink支持的JOIN类型:

JOIN - INNER JOIN
LEFT JOIN - LEFT OUTER JOIN
RIGHT JOIN - RIGHT OUTER JOIN
FULL JOIN - FULL OUTER JOIN
JOIN与关系代数的Join语义相同.

INNER JOIN只选择满足ON条件的记录,我们查询customer_tab 和 order_tab表,将有订单的客户和订单信息选择出来

val result = customer
  .join(order.select('o_id, 'c_id as 'o_c_id, 'o_time, 'o_desc), 'c_id === 'o_c_id)

LEFT JOIN与INNER JOIN的区别是当右表没有与左边相JOIN的数据时候,右边对应的字段补NULL输出
对应的SQL语句如下(LEFT JOIN):

SELECT ColA, ColB, T2.ColC, ColE FROM TI LEFT JOIN T2 ON T1.ColC = T2.ColC ;
当两张表有字段名字一样的时候,我需要指定是从那个表里面投影的

RIGHT JOIN 相当于 LEFT JOIN 左右两个表交互一下位置。FULL JOIN相当于 RIGHT JOIN 和 LEFT JOIN 之后进行UNION ALL操作。

Time-Interval JOIN

Time-Interval JOIN 相对于UnBounded的双流JOIN来说是Bounded JOIN。就是每条流的每一条数据会与另一条流上的不同时间区域的数据进行JOIN。对应Apache Flink官方文档的 Time-windowed JOIN(release-1.7之前都叫Time-Windowed JOIN)。

val result = left
  .join(right)
  // 定义Time Interval
  .where('a === 'd && 'c >= 'f - 5.seconds && 'c < 'f + 6.seconds)
Lateral JOIN

Apache Flink Lateral JOIN 是左边Table与一个UDTF进行JOIN.

Window

一种是OverWindow,即传统数据库的标准开窗,每一个元素都对应一个窗口。一种是GroupWindow,目前在SQL中GroupWindow都是基于时间进行窗口划分的.

Over Window

Apache Flink中对OVER Window的定义遵循标准SQL的定义语法。
按ROWS和RANGE分类是传统数据库的标准分类方法,在Apache Flink中还可以根据时间类型(ProcTime/EventTime)和窗口的有限和无限(Bounded/UnBounded)进行分类,共计8种类型。为了避免大家对过细分类造成困扰,我们按照确定当前行的不同方式将OVER Window分成两大类进行介绍,如下:

  • ROWS OVER Window - 每一行元素都视为新的计算行,即,每一行都是一个新的窗口。
  • RANGE OVER Window - 具有相同时间值的所有元素行视为同一计算行,即,具有相同时间值的所有行都是同一个窗口。
    Bounded ROWS OVER Window
    Bounded ROWS OVER Window 每一行元素都视为新的计算行,即,每一行都是一个新的窗口。
    Bounded RANGE OVER Window
    Bounded RANGE OVER Window 具有相同时间值的所有元素行视为同一计算行,即,具有相同时间值的所有行都是同一个窗口。
    OverWindow最重要是要理解每一行数据都确定一个窗口,同时目前在Apache Flink中只支持按时间字段排序。并且OverWindow开窗与GroupBy方式数据分组最大的不同在于,GroupBy数据分组统计时候,在SELECT中除了GROUP BY的key,不能直接选择其他非key的字段,但是OverWindow没有这个限制,SELECT可以选择任何字段。比如一张表table(a,b,c,d)4个字段,如果按d分组求c的最大值,两种写完如下:

GROUP BY - tab.groupBy('d).select(d, MAX©)
OVER Window = tab.window(Over… as 'w).select('a, 'b, 'c, 'd, c.max over 'w)
如上 OVER Window 虽然PARTITION BY d,但SELECT 中仍然可以选择 a,b,c字段。但在GROUPBY中,SELECT 只能选择 d 字段。

Group Window

根据窗口数据划分的不同,目前Apache Flink有如下3种Bounded Winodw:

  • Tumble - 滚动窗口,窗口数据有固定的大小,窗口数据无叠加;
  • Hop - 滑动窗口,窗口数据有固定大小,并且有固定的窗口重建频率,窗口数据有叠加;
  • Session - 会话窗口,窗口数据没有固定的大小,根据窗口数据活跃程度划分窗口,窗口数据无叠加。
    说明: Aapche Flink 还支持UnBounded的 Group Window,也就是全局Window,流上所有数据都在一个窗口里面,语义非常简单,这里不做详细介绍了。

Tumble
Tumble 滚动窗口有固定size,窗口数据不重叠,
利用pageAccess_tab测试数据,我们需要按不同地域统计每2分钟的淘宝首页的访问量(PV)。

 val result = pageAccess
   .window(Tumble over 2.minute on 'rowtime as 'w)
   .groupBy('w, 'region)
   .select('region, 'w.start, 'w.end, 'region.count as 'pv)

Hop
Hop 滑动窗口和滚动窗口类似,窗口有固定的size,与滚动窗口不同的是滑动窗口可以通过slide参数控制滑动窗口的新建频率。因此当slide值小于窗口size的值的时候多个滑动窗口会重叠。
利用pageAccessCount_tab测试数据,我们需要每5分钟统计近10分钟的页面访问量(PV).

val result = pageAccessCount
  .window(Slide over 10.minute every 5.minute on 'rowtime as 'w)
  .groupBy('w)
  .select('w.start, 'w.end, 'accessCount.sum as 'accessCount)

Session
Seeeion 会话窗口 是没有固定大小的窗口,通过session的活跃度分组元素。不同于滚动窗口和滑动窗口,会话窗口不重叠,也没有固定的起止时间。一个会话窗口在一段时间内没有接收到元素时,即当出现非活跃间隙时关闭。一个会话窗口 分配器通过配置session gap来指定非活跃周期的时长.

val result = pageAccessSession
  .window(Session withGap 3.minute on 'rowtime as 'w)
  .groupBy('w, 'region)
  .select('region, 'w.start, 'w.end, 'region.count as 'pv)

嵌套Window
在Window之后再进行Window划分也是比较常见的统计需求,那么在一个Event-Time的Window之后,如何再写一个Event-Time的Window呢?一个Window之后再描述一个Event-Time的Window最重要的是Event-time属性的传递,在Table API中我们可以利用’w.rowtime来传递时间属性,比如:Tumble Window之后再接一个Session Window 示例如下:

 val result = pageAccess
   .window(Tumble over 2.minute on 'rowtime as 'w1)
   .groupBy('w1)
   .select('w1.rowtime as 'rowtime, 'col1.count as 'cnt)
   .window(Session withGap 3.minute on 'rowtime as 'w2)
   .groupBy('w2)
   .select('cnt.sum)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值