Flink1.12新特性和与Flink1.10语法差异
Flink1.12新特性
SQL Connectors 中的 Metadata 处理
Flink 1.12 中,元数据列是 SQL 标准的扩展,参数中connector和format配置提供的metadata字段。元数据列由METADATA关键字指示。例如,元数据列可用于在 Kafka 记录中读取和写入时间戳,以进行基于时间的操作。连接器和格式文档列出了每个组件的可用元数据字段。但是,在表的架构中声明元数据列是可选的。
//sql
CREATE TABLE source(
field1 varchar,
field2 int,
ts TIMESTAMP(3) METADATA FROM'timestamp',
partition BIGINT METADATA,
offset BIGINT METADATA,
proc_time AS PROCTIME()
)WITH(
'properties.bootstrap.servers'='110.42.146.82:9092',
'connector'='kafka-x',
'scan.parallelism'='1',
'format'='json',
'topic'='planet_MetaData',
'scan.startup.mode'='latest-offset'
);
CREATE TABLE sink_kafka(
field1 varchar,
field2 int,
ts timestamp,
partition bigint,
offset bigint
)WITH(
'properties.bootstrap.servers'='110.42.146.82:9092',
'connector'='kafka-x',
'format'='json',
'topic'='planetTest',
'sink.parallelism'='1'
);
create table sink(
field1 varchar,
field2 int,
ts timestamp,
partition bigint,
offset bigint
)with(
'connector'='stream-x',
'print'='true'
);
insert intosink
select
field1,
field2,
ts,
partition,
offset
from
source;
insert into sink_kafka
select
field1,
field2,
ts,
partition,
offset
from
source;
上述案例中 Kafka connector available metadata

## 时态表
Flink1.12中,一张随时间变化的表即可被称为时态表,时态表是一个概念解释,并没有实体。
时态表可以被分为两个实体概念,根据有无ChangeLog来区分定义。
**版本表**: 如果时态表中的记录可以追踪和并访问它的历史版本,这种表我们称之为版本表,来自数据库的 changelog 可以定义成版本表。
**普通表**: 如果时态表中的记录仅仅可以追踪并和它的最新版本,这种表我们称之为普通表,来自数据库 或 HBase 的表可以定义成普通表。
### 声明版本表
在 Flink1.12 中,定义了主键约束(1)和事件时间属性(2)的表就是版本表。
```sql
-- 定义一张版本表
CREATE TABLE product_changelog (
product_id STRING,
product_name STRING,
product_price DECIMAL(10, 4),
update_time TIMESTAMP(3) METADATA FROM 'value.source.timestamp' VIRTUAL,
PRIMARY KEY(product_id) NOT ENFORCED, -- (1) 定义主键约束
WATERMARK FOR update_time AS update_time -- (2) 通过 watermark 定义事件时间
) WITH (
'connector' = 'kafka-x',
'topic' = 'products',
'scan.startup.mode' = 'earliest-offset',
'properties.bootstrap.servers' = 'localhost:9092',
'format' = 'debezium-json' -- 时态表限定debezium-json
);
```
### 声明版本视图
Flink1.12还支持讲Append-Only流转成changlog流。
先声明一个Append-Only表
```sql
-- 定义一张 append-only 表
CREATE TABLE RatesHistory (
currency_time TIMESTAMP(3),
currency STRING,
rate DECIMAL(38, 10),
WATERMARK FOR currency_time AS currency_time -- 定义事件时间
) WITH (
'connector' = 'kafka',
'topic' = 'rates',
'scan.startup.mode' = 'earliest-offset',
'properties.bootstrap.servers' = 'localhost:9092',
'format' = 'json' -- 普通的 append-only 流
)
```
然后通过去重查询定义版本视图
```sql
CREATE VIEW versioned_rates AS
SELECT currency, rate, currency_time -- (1) `currency_time` 保留了事件时间
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY currency -- (2) `currency` 是去重 query 的 unique key,可以作为主键
ORDER BY currency_time DESC) AS rowNum
FROM RatesHistory )
WHERE rowNum = 1;
```
## 时间区间 Join
如果一个 Join 限定输入[时间属性](https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/table/streaming/time_attributes.html)必须在一定的时间限制中(即时间窗口),那么就称之为时间区间 Join。
```sql
SELECT *
FROM
Orders o,
Shipments s
WHERE o.id = s.orderId AND
o.ordertime BETWEEN s.shiptime - INTERVAL '4' HOUR AND s.shiptime
```
与常规 Join 操作相比,时间区间 Join 只支持带有时间属性的递增表。由于时间属性是单调递增的,Flink 可以从状态中移除过期的数据,而不会影响结果的正确性。
## 时态表Join
时态表 Join 意味着对任意表(左输入/探针侧)去关联一个时态表(右输入/构建侧)的版本,时态表可以是一张跟踪所有变更记录的表(例如数据库表的 changelog,包含多个表快照),也可以是物化所有变更之后的表(例如数据库表,只有最新表快照)。
Flink 使用了 SQL:2011 标准引入的时态表 Join 语法,时态表 Join 的语法如下:
```sql
SELECT [column_list]
FROM table1 [AS <alias1>]
[LEFT] JOIN table2 FOR SYSTEM_TIME AS OF table1.{ proctime | rowtime } [AS <alias2>]
ON table1.column-name1 = table2.column-name1
```
### 基于事件时间的时态 Join
基于事件时间的时态表 join 使用(左侧输入/探针侧) 的 事件时间 去关联(右侧输入/构建侧) [版本表](https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/table/streaming/versioned_tables.html#声明版本表) 对应的版本。 基于事件时间的时态表 join 仅支持关版本表或版本视图,版本表或版本视图只能是一个 changelog 流。 但是,Flink 支持将 append-only 流转换成 changelog 流,因此版本表也可以来自一个 append-only 流。 查看[声明版本视图](https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/table/streaming/versioned_tables.html#声明版本视图) 获取更多的信息关于如何声明一张来自 append-only 流的版本表。
将事件时间作为时间属性时,可将 *过去* 时间属性与时态表一起使用。这允许对两个表中在相同时间点的记录执行 Join 操作。 与基于处理时间的时态 Join 相比,时态表不仅将构建侧记录的最新版本(是否最新由所定义的主键所决定)保存在 state 中,同时也会存储自上一个 watermarks 以来的所有版本(按时间区分)。
例如,在探针侧表新插入一条事件时间时间为 `12:30:00` 的记录,它将和构建侧表时间点为 `12:30:00` 的版本根据[时态表的概念](https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/table/streaming/versioned_tables.html)进行 Join 运算。 因此,新插入的记录仅与时间戳小于等于 `12:30:00` 的记录进行 Join 计算(由主键决定哪些时间点的数据将参与计算)。
通过定义事件时间,[watermarks](https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/event_time.html) 允许 Join 运算不断向前滚动,丢弃不再需要的构建侧快照。因为不再需要时间戳更低或相等的记录。
下面的例子展示了订单流关联产品表这个场景举例,`orders` 表包含了来自 Kafka 的实时订单流,`product_changelog` 表来自数据库表 `products` 的 changelog , 产品的价格在数据库表 `products` 中是随时间实时变化的。
```sql
SELECT * FROM product_changelog;
(changelog kind) update_time product_name price
================= =========== ============ =====
+(INSERT) 00:01:00 scooter 11.11
+(INSERT) 00:02:00 basketball 23.11
-(UPDATE_BEFORE) 12:00:00 scooter 11.11
+(UPDATE_AFTER) 12:00:00 scooter 12.99 <= 产品 `scooter` 在 `12:00:00` 时涨价到了 `12.99`
-(UPDATE_BEFORE) 12:00:00 basketball 23.11
+(UPDATE_AFTER) 12:00:00 basketball 19.99 <= 产品 `basketball` 在 `12:00:00` 时降价到了 `19.99`
-(DELETE) 18:00:00 scooter 12.99 <= 产品 `scooter` 在 `18:00:00` 从数据库表中删除
```
如果我们想输出 `product_changelog` 表在 `10:00:00` 对应的版本,表的内容如下所示:
```sql
update_time product_id product_name price
=========== ========== ============ =====
00:01:00 p_001 scooter 11.11
00:02:00 p_002 basketball 23.11
```
如果我们想输出 `product_changelog` 表在 `13:00:00` 对应的版本,表的内容如下所示:
```sql
update_time product_id product_name price
=========== ========== ============ =====
12:00:00 p_001 scooter 12.99
12:00:00 p_002 basketball 19.99
```
通过基于事件时间的时态表 join, 我们可以 join 上版本表中的不同版本:
```sql
CREATE TABLE orders (
order_id STRING,
product_id STRING,
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time -- defines the necessary event time
) WITH (
...
);
-- 设置会话的时间区间, changelog 里的数据库操作时间是以 epoch 开始的毫秒数存储的,
-- 在从毫秒转化为时间戳时,Flink SQL 会使用会话的时间区间
-- 因此,请根据 changelog 中的数据库操作时间设置合适的时间区间
SET table.local-time-zone=UTC;
-- 声明一张版本表
CREATE TABLE product_changelog (
product_id STRING,sq
product_name STRING,
product_price DECIMAL(10, 4),
update_time TIMESTAMP(3) METADATA FROM 'value.source.timestamp' VIRTUAL, -- 注意:自动从毫秒数转为时间戳
PRIMARY KEY(product_id) NOT ENFORCED, -- (1) defines the primary key constraint
WATERMARK FOR update_time AS update_time -- (2) defines the event time by watermark
) WITH (
'connector' = 'kafka',
'topic' = 'products',
'scan.startup.mode' = 'earliest-offset',
'properties.bootstrap.servers' = 'localhost:9092',
'value.format' = 'debezium-json'
);
-- 基于事件时间的时态表 Join
SELECT
order_id,
order_time,
product_name,
product_time,
price
FROM orders AS O
LEFT JOIN product_changelog FOR SYSTEM_TIME AS OF O.order_time AS P
ON O.product_id = P.product_id;
order_id order_time product_name product_time price
======== ========== ============ ============ =====
o_001 00:01:00 scooter 00:01:00 11.11
o_002 00:03:00 basketball 00:02:00 23.11
o_003 12:00:00 scooter 12:00:00 12.99
o_004 12:00:00 basketball 12:00:00 19.99
o_005 18:00:00 NULL NULL NULL
```
基于事件时间的时态表 Join 通常用在通过 changelog 丰富流上数据的场景。
**注意**: 基于事件时间的时态表 Join 是通过左右两侧的 watermark 触发,请确保为 join 两侧的表设置了合适的 watermark。
**注意**: 基于事件时间的时态表 Join 的 join key 必须包含时态表的主键,例如:表 `product_changelog` 的主键 `P.product_id` 必须包含在 join 条件 `O.product_id = P.product_id` 中。
### 基于处理时间的时态 Join
基于处理时间的时态表 join 使用任意表 (左侧输入/探针侧) 的 处理时间 去关联 (右侧输入/构建侧) [普通表](https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/table/streaming/versioned_tables.html#声明普通表)的最新版本. 基于处理时间的时态表 join 当前只支持关联普通表或普通视图,且支持普通表或普通视图当前只能是 append-only 流。
如果将处理时间作为时间属性,*过去* 时间属性将无法与时态表一起使用。根据定义,处理时间总会是当前时间戳。 因此,关联时态表的调用将始终返回底层表的最新已知版本,并且底层表中的任何更新也将立即覆盖当前值。
可以将处理时间的时态 Join 视作简单的 `HashMap <K,V>`,HashMap 中存储来自构建侧的所有记录。 当来自构建侧的新插入的记录与旧值具有相同的 Key 时,旧值会被覆盖。 探针侧的每条记录将总会根据 `HashMap` 的最新/当前状态来计算。
接下来的示例展示了订单流 `Orders` 该如何与实时变化的汇率表 `Lates` 进行基于处理时间的时态 Join 操作,`LatestRates` 总是表示 HBase 表 `Rates` 的最新内容。
表 `LastestRates` 中的数据在时间点 `10:15` 和 `10:30` 时是相等的。欧元汇率在时间点 `10:52` 从 114 变化至 116 。
表 `Orders` 包含了金额字段 `amount` 和货币字段 `currency` 的支付记录数据。例如在 `10:15` 有一笔金额为 `2` 欧元的订单记录。
```
SELECT * FROM Orders;
amount currency
====== =========
2 Euro <== arrived at time 10:15
1 US Dollar <== arrived at time 10:30
2 Euro <== arrived at time 10:52
```
基于以上,我们想要计算所有 `Orders` 表的订单金额总和,并同时转换为对应成日元的金额。
例如,我们想要以表 `LatestRates` 中的汇率将以下订单转换,则结果将为:
```
amount currency rate amout*rate
====== ========= ======= ============
2 Euro 114 228 <== arrived at time 10:15
1 US Dollar 102 102 <== arrived at time 10:30
2 Euro 116 232 <== arrived at time 10:52
```
通过时态表 Join,我们可以将上述操作表示为以下 SQL 查询:
```
SELECT
o.amout, o.currency, r.rate, o.amount * r.rate
FROM
Orders AS o
JOIN LatestRates FOR SYSTEM_TIME AS OF o.proctime AS r
ON r.currency = o.currency
```
探针侧表中的每个记录都将与构建侧表的当前版本所关联。 在此示例中,查询使用`处理时间`作为处理时间,因而新增订单将始终与表 `LatestRates` 的最新汇率执行 Join 操作。注意,结果对于处理时间来说不是确定的。 基于处理时间的时态表 Join 通常用在通过外部表(例如维度表)丰富流上数据的场景。
与[常规 Join](https://nightlies.apache.org/flink/flink-docs-release-1.12/zh/dev/table/streaming/joins.html#常规-join) 相比,尽管构建侧表的数据发生了变化,但时态表 Join 的变化前结果不会随之变化。
- 对于基于事件时间的时态 Join, join 算子保留 Join 两侧流的状态并通过 watermark 清理。
- 对于基于处理时间的时态 Join, join 算子保留仅保留右侧(构建侧)的状态,且构建侧的状态只包含数据的最新版本,右侧的状态是轻量级的; 对于在运行时有能力查询外部系统的时态表,join 算子还可以优化成不保留任何状态,此时算子是非常轻量级的。
与[时间区间 Join](https://nightlies.apache.org/flink/flink-docs-release-1.12/zh/dev/table/streaming/joins.html#时间区间-join) 相比,时态表 Join 没有定义决定构建侧记录所属的将被 Join 时间窗口。 探针侧的记录将总是与构建侧在对应`处理时间`的最新数据执行 Join,因而构建侧的数据可能是任意旧的。
## Flink1.12与Flink1.10的差异
#### Flink1.10维表关联方式
```sql
create table sink(
flag varchar,
field1 varchar,
field2 varchar,
field3 varchar
)with(
type='console'
);
insert into sink
select
'flink110' as flag
,s1.field1 as field1
,s1.field2 as field2
,s2.field2 as field3
from source s1
left join dim as s2
on s1.field1 = s2.field1;
```
#### Flink1.12维表关联方式
```sql
create table sink(
flag varchar,
field1 varchar,
field2 varchar,
field3 varchar
)with(
'connector'='stream-x',
'print'='true'
);
insert into sink
select
'flink112' as flag
,s1.field1 as field1
,s1.field2 as field2
,s2.field2 as field3
from source s1
left join dim for SYSTEM_TIME AS OF s1.proc_time as s2
on s1.field1 = s2.field1;
```
#### Flink1.10嵌套Json
```sql
CREATE TABLE source(
column.field1 string as field1,
column.field2 int as field2
)WITH(
type ='kafka',
bootstrapServers ='110.42.146.82:9092',
offsetReset ='latest',
topic ='planet',
charsetName ='utf-8',
timezone='Asia/Shanghai',
updateMode ='append',
enableKeyPartitions ='false',
topicIsPattern ='false',
parallelism ='1'
);
insert into sink
select
*
from(
select
'110_demo1' as flag
,field1 as field1
,field2 as field2
FROM source );
```
#### Flink1.12嵌套Json
```sql
CREATE TABLE source(
`column` ROW<field1 string,
field2 int>,
proc_time AS PROCTIME()
)WITH(
'properties.bootstrap.servers'='110.42.146.82:9092',
'connector'='kafka-x',
'scan.parallelism'='1',
'format'='json',
'topic'='planet',
'scan.startup.mode'='latest-offset'
);
insert into sink
select
*
,ROW(field1,field2) as columns
from(
select
'112_demo1' as flag
,`column`.field1 as field1
,`column`.field2 as field2
FROM source );
```
> 本文由博客一文多发平台 [OpenWrite](https://openwrite.cn?from=article_bottom) 发布!
浙公网安备 33010602011771号