概述
- 1.11版本最重要的特性Hive Streaming在之前的博客,包括Flink中文社区的微信公众号上,都和大家聊过不少次了。那么除此之外,还有什么特性值得大家关注呢?
- CDC数据的解析:可以解析canal、debezium推到kafka中的binlog;如果在binlog中是个
DELETE
的操作,那么会自动解析成撤回流,将之前那条下发过的数据撤回;美中不足的是,只支持读,不支持写,这也就以为着无法将撤回流写入kafka - Postgres Catalog:如果要用Postgres中的表,不需要再
CREATE TABLE
了,直接用就好了,就像整合了Hive之后,用Hive中的表一样简单;美中不足的是,只支持使用已有的表,没法把表建在Postgres Catalog中
- CDC数据的解析:可以解析canal、debezium推到kafka中的binlog;如果在binlog中是个
- 今天我们要聊的,就是CDC中的canal-json;并且,我自己实现了将计算完的数据,再解析成canal-json,这也就意味着,可以将聚合过的结果插入kafka(1.10版本中,想将聚合结果插入kafka可以参考这个文章聚合结果写入Kafka)
- 二话不说,我们开始吧,先从canal-json source开始
canal-json source
-
先建一下表
drop table if exists topic_products; CREATE TABLE topic_products ( -- schema is totally the same to the MySQL "products" table id BIGINT, name STRING, description STRING, weight DECIMAL(10, 2) ) WITH ( 'connector' = 'kafka', 'topic' = 'products_binlog', 'properties.bootstrap.servers' = '10.70.98.1:9092', 'properties.group.id' = 'testGroup', 'scan.startup.mode' = 'earliest-offset', 'format' = 'canal-json' -- using canal-json as the format )
drop table if exists print_table; CREATE TABLE print_table WITH ('connector' = 'print') LIKE topic_products (EXCLUDING ALL)
-
这里之所以用
print
这个新的connector,一方面是因为Zeppelin展示的时候看不到撤回的效果,一方面考虑到有些同学可能用的是tEnv.executeSql(sql).print
,在接收撤回流时这个语句会报错,所以正好用上了这个新的connector -
用Kafka命令行插入两条数据到Topic中
{"data":[{"id":"4","name":"新增测试","description":"这是测试","weight":"100.1"}],"database":"test","es":1595487446000,"id":2,"isDdl":false,"mysqlType":{"id":"integer(255)","name":"varchar(255)","description":"varchar(255)","weight":"float"},"old":null,"pkNames":["id"],"sql":"","sqlType":{"id":4,"name":12,"description":12,"weight":7},"table":"products","ts":1595487446183,"type":"INSERT"}
{"data":[{"id":"4","name":"新增测试(修改)","description":"这是测试进行了修改","weight":"100.1"}],"database":"test","es":1595487509000,"id":3,"isDdl":false,"mysqlType":{"id":"integer(255)","name":"varchar(255)","description":"varchar(255)","weight":"float"},"old":[{"name":"新增测试","description":"这是测试"}],"pkNames":["id"],"sql":"","sqlType":{"id":4,"name":12,"description":12,"weight":7},"table":"products","ts":1595487509389,"type":"UPDATE"}
-
再让我们到flink web页面上看看我们
print
的结果 -
大家可能会很奇怪,为啥子出现了三条结果;大家可以看一下
CanalJsonDeserializationSchema
这个类的deserialize(byte[] message, Collector<RowData> out)
方法 -
它会将
UPDATE
类型的canal-json解析成两条数据,json中的old
数据代表修改之前的数据,标记为UPDATE_BEFORE
类型;data
代表是修改之后的数据,标记为UPDATE_AFTER
类型;将old
和data
都下发,引擎会根据类型去判断这条数据是撤回还是下发。
canal-json sink
- 说完了canal-json source,我们来聊聊我们今天的重点,canal-json sink
- 之前我们在讲聚合结果写入kafka的时候,说过一个缺点:因为Kafka只支持追加插入操作,不支持更新和删除操作,所以同样的Key有多条记录,我们需要在下游任务进行对数据的去重。这样会给下游使用这个topic的同学带来疑惑:为啥还要去重?聚合结果不应该是一条吗?这怎么和离线不一样?同样的,去重也会带来一定的性能损耗。
- 那么,有更好的做法吗?答:有的,用canal-json。遗憾的是,社区还未实现,直接使用的话会抛出异常
Canal format doesn't support as a sink format yet.
。不过我自己先简单的实现了一下,时间原因+工作繁忙,所以没有做太完善的测试,包括有些地方写的也不够优雅,不过大家可以自己再补充一下 - canal-json sink会将你的数据重新包装成canal-json的格式,只保留三个字段(因为canal-json source也只需要三个字段)
data old type
; - 大概说一下我的处理方式吧,聚合数据写到kafka会有多种类型(在flink中的类型,不是指canal-json中的
type
)INSERT
:没啥特殊的,直接序列化插入好了UPDATE_BEFORE
和UPDATE_AFTER
:按照canal-json的field定义,是要放在同一条数据中的;UPDATE_BEFORE
对应的是canal-json中的old
字段,UPDATE_AFTER
对应的是data
。但是我发现存在同一条字段中会有一些问题:怎么将before
和after
对应起来呢?也就是说key
是啥?第二个问题:这样搞得话需要将before
先放在内存里面缓存,那么很容易内存就炸了。然后我又参考了一下阿里云实时计算平台文档
所以我决定将UPDATE_BEFORE
解析为DELETE
,也就是将canal-json中的type
设置为DELETE
,old
为空,数据放到data
中;UPDATE_AFTER
解析为INSERT
,也就是将canal-json中的type
设置为INSERT
,old
一样为空,数据放到data
中。
- DELETE
:和UPDATE_BEFORE
的处理方式一样
-
然后将包装完的json再序列化发送到kafka
-
下面给大家演示一下吧
-
可以看到,我是将kafka中csv结构的数据,完成了一次聚合又写入了canal-json格式的kafka中,然后又从中查询再展示
-
原始数据
-
插入
print connector
中的数据。很明显的能看到,不正确的数据有被撤回的标记-D
-
最后再给大家看一下,kafka中的canal-json格式数据,也和我们预期的一样,很完美
写在最后
- 时间仓促,没有完整的测试,包括单测也没有写,但是基本的功能也有了可以先测试用用
- CDC的出现终于摆脱了无法将聚合结果写入kafka的困境,另外社区出的
flink-cdc-connectors
让我们看到了flink可以做的另一件事情:同步binlog。感觉在一定程度上可以替代canal这个中间件 - 下一期会和大家聊一下自定义mysql catalog,不过我还没开始看相关源码,什么时候写出来也就不知道了🤣