FlinkCDC-Hudi:Mysql数据实时入湖全攻略一:初试风云
FlinkCDC-Hudi:Mysql数据实时入湖全攻略二:Hudi与Spark整合时所遇异常与解决方案
FlinkCDC-Hudi:Mysql数据实时入湖全攻略三:探索实现FlinkCDC mysql 主从库同步高可用
在生产实践中,通过FlinkCDC读取数据,除了落地hadoop入湖供下游离线使用外,也会存在写入kafka供实时程序消费使用。
那么flink里,kafka connector有哪些?各有什么特征?使用时要注意什么呢?且让我们开始flink kafka connector探索之旅。
在开始实操探索之前,至少确保你已经搭建好了FlinkCDC-Hudi的运行环境。本文的测试环境基于FlinkCDC-Hudi:Mysql数据实时入湖全攻略一:初试风云。如果仅对flinkcdc写入kafka感兴趣,至少准备flink环境和flinkcdc依赖。
测试flink sql写kafka,需要添加运行依赖flink-sql-connector-kafka。笔者使用的版本是flink-sql-connector-kafka_2.11-1.13.5.jar。
org.apache.flink
flink-sql-connector-kafka_2.11
1.13.5
provided
读者可以根据自己的运行环境下载对应的依赖包。maven依赖下载: flink-connector-kafka
笔者kafka使用kafka-2.7.0版本。读者如未配置kafka,可参见官方文档Kafka快速入门
笔者在FlinkCDC-Hudi:Mysql数据实时入湖全攻略三:探索实现FlinkCDC mysql 主从库同步高可用 搭建了一主二从的Mysql环境,笔者的运行环境依赖使用这个环境。读者可以依此搭建。
读者如果使用自己的环境,需要确认mysql开启binlog并授以flinkcdc测试账号相应权限。
本文相关测试在flink sql上运行。在搭建好上述环境后,执行以下后置代码,然后进入flink sql kafka connector测试环境。
mysql> CREATE TABLE `test_1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`data` varchar(10) DEFAULT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cd FLINK_HOME
./bin/yarn-session.sh -s 4 -jm 1024 -tm 2048 -nm flink-hudi -d
./bin/sql-client.sh embedded -s yarn-session -j /home/zhangsirun/flink-1.13.5/lib/hudi-flink-bundle_2.11-0.11.0-SNAPSHOT.jar shell
Flink SQL> set execution.checkpointing.interval=30sec;
Flink SQL> set pipeline.name = flinkcdc_test_1;
Flink SQL> create table mysql_test_1(
id bigint primary key not enforced,
data String,
create_time Timestamp(3)
) with (
'connector'='mysql-cdc',
'hostname'='192.168.2.101',
'port'='3306',
'server-id'='5800-5804',
'username'='user_test',
'password'='user_test_password',
'server-time-zone'='Asia/Shanghai',
'debezium.snapshot.mode'='initial',
'database-name'='flink_cdc',
'table-name'='test_1'
);
通过查阅Flink官方文档Connector/Table API Connectors,我们知道Flink kafka connector有两种实现:kafka和upsert kafka。这两种connector有什么特点呢?下面一一揭晓。
Kafka sql connector是基础的kafka应用封装,用于生产/消费指写topic的数据。
这个connector提供了额外的元数据可用于表定义,topic,partittion,headers,leader-epoch,offset,timestamp,timestamp-type,这些都是与生产/消费相关的kafka基础信息。官方提供的应用样例:
CREATE TABLE KafkaTable (
`event_time` TIMESTAMP(3) METADATA FROM 'timestamp',
`partition` BIGINT METADATA VIRTUAL,
`offset` BIGINT METADATA VIRTUAL,
`user_id` BIGINT,
`item_id` BIGINT,
`behavior` STRING
) WITH (
'connector' = 'kafka',
'topic' = 'user_behavior',
'properties.bootstrap.servers' = 'localhost:9092',
'properties.group.id' = 'testGroup',
'scan.startup.mode' = 'earliest-offset',
'format' = 'csv'
);
使用这个connector时相关配置有很多,详情可见Flink/table/kafka connector。这里结合配置项做一些关键特征介绍。
connector=kafka,必选 ,指定为使用kafka sql connector。
常用格式有csv,json,debezium-json,avro,raw。格式配置错误的话,会导致解析异常,进行导致作业失败。
更多格式与信息参看connetors/table/format
scan.*,定义消费相关的配置。可以配置消费模式,起始offset,起始timestamp,分区发现时间间隔。
值 | 说明 |
---|---|
group-offsets | 从zk/kafka上记录的消费者组offset开始消费,默认 |
earliest-offset | 从最早的offset开始 |
latest-offset | 从最新的offset开始 |
timestamp | 每个分区从对应时间戳开始消费。时间戳通过scan.startup.timestamp-millis指定 |
specific-offsets | 从用户指定的offset开始消费。offset通过配置scan.startup.specific-offsets指定,示例为:partition:0,offset:42;partition:1,offset:300 |
sink.*,定义生产相关的配置。可配置key partitionner,生产一致性语义,生产并发。
值 | 说明 |
---|---|
none | 不提供任何保证,可能丢数,可能重复 |
at-least-once | 至少一次,保证不丢数,可能会重复 |
exactly-once | 精确一次。使用kafka事务保证。下游消费者需要配置隔离等级。isolation.level (read_committed 或 read_uncommitted |
Flink SQL> create table kafka_test_1(
table_name String,
id bigint primary key not enforced,
data String,
create_time Timestamp(3)
) with (
'connector'='kafka',
'topic'='test',
'properties.bootstrap.servers' = 'broker:9092',
'key.format'='json',
'key.fields'='table_name;id',
'value.format'='debezium-json'
);
由于我们使用FlinkCDC mysql connector作为数据源,使用的value.format是debezium-json,同时定义了两个主键table_name;id,主键格式为json格式。
Flink SQL> set execution.checkpointing.interval=30sec;
Flink SQL> set pipeline.name = flinkcdc_test_1;
Flink SQL> insert into kafka_test_1 select * from mysql_test_1;
在FlinkSql client中启动表查询:
Flink SQL> select * from kafka_test_1;
开启Kafka消费者,用于读取kafka消息,验证sink kafka。
KAFKA_HOME/bin/kafka-console-consumer.sh --bootstrap-server broker:9092 --topic test1 --property print.partition=true --property print.offset=true --property print.key=true --from-beginning
在mysql中插入数据:
mysql> insert into test_1 values(149,'data','2022-02-18 20:31:55');
消费到的数据:
Partition:3 Offset:11 {"table_name":"test_1","id":149} {"before":null,"after":{"table_name":"test_1","id":149,"data":"d1","create_time":"2022-02-18 20:31:55"},"op":"c"}
消息按table_name,id以json格式生成key: {“table_name”:“test_1”,“id”:149}。
消息体为debezium-json格式。内容如下。其中
{
"before": null,
"after": {
"table_name": "test_1",
"id": 149,
"data": "d1",
"create_time": "2022-02-18 20:1:55"
},
"op": "c"
}
通过mysql更新上述记录:
mysql> update test_1 set data='bigdata' where id=149;
一条mysyl update语句产生了两条kakfa消息。第一条代表delete旧值,旧值放在before字段。第二条代表创建新值,新值放在after字段。由于key相同,数据都发到了相同的分区。
Partition:3 Offset:12 {"table_name":"test_1","id":149} {"before":{"table_name":"test_1","id":149,"data":"d1","create_time":"2022-02-18 20:31:55"},"after":null,"op":"d"}
Partition:3 Offset:13 {"table_name":"test_1","id":149} {"before":null,"after":{"table_name":"test_1","id":149,"data":"bigdata","create_time":"2022-02-18 20:38:46"},"op":"c"}
在mysql中delete该记录:
mysql> delete from test_1 where id=149;
一条delete语句对应产生一条kafka消息。
Partition:3 Offset:14 {"table_name":"test_1","id":149} {"before":{"table_name":"test_1","id":149,"data":"bigdata","create_time":"2022-02-18 20:38:46"},"after":null,"op":"d"}
行为 | create | update | delete |
---|---|---|---|
source | 读取c记录 | 读取到2条记录,合并为最新镜像 | 删除1条记录 |
sink | 产生1条c记录 | 产生2条记录,c-d | 产生1条d记录 |
Upsert Kafka connector 允许以 upsert 方式从 Kafka topic中读取或写入数据。
在upsert模式中,变量日志流中,所有的insert、update、delete事件都可以理解为update事件,任一事件发生时,对应记录的所有字段值都会更新为最新的值,delete视为将值更新为null。将数据写入kafka的时候,数据会按key进行分区,确保相同的key都会以相同的顺序进入到相同的分区。
Upsert kafka connector配置与kafka connector的大致相同,两个关键的新增的配置如下:
Flink SQL> create table upsert_kafka_test_2(
table_name String,
id bigint,
data String,
create_time Timestamp(3),
PRIMARY KEY (`table_name`,`id`) NOT ENFORCED
) with (
'connector'='upsert-kafka',
'properties.bootstrap.servers' = 'broker:9092',
'topic'='test2',
'key.format'='json',
'value.format'='json'
);
6.3.2 启动Flink upsert kafka connector作业
Flink SQL> set execution.checkpointing.interval=30sec;
Flink SQL> set pipeline.name = flinkcdc_upsert_kafka_test_1;
Flink SQL> insert into upsert_kafka_test_2 select * from mysql_test_1;
Flink SQL>select * from upsert_kafka_test_2;
执行一条insert语句:
mysql> insert into test_1 values(151,'data','2022-02-21 10:31:55');
kafka consumer查看消息,收到的消息体就是按表ddl字段组织成的json。
Partition:1 Offset:152 {"table_name":"test_1","id":151} {"table_name":"test_1","id":151,"data":"data","create_time":"2022-02-21 10:31:55"}
在mysql中执行一条update:
mysql> update test_1 set data='bigdata' where id=151;
在kafka中收到一条更新数据,一条json里包含所有字段的最新值:
Partition:1 Offset:153 {"table_name":"test_1","id":151} {"table_name":"test_1","id":151,"data":"bigdata","create_time":"2022-02-21 10:48:36"}
在mysql中执行一条delete语句:
mysql> delete from test_1 where id=151;
在kafka consumer中收到一条delete消息,消息的key为定义的主键,消息体为“null”。
Partition:1 Offset:154 {"table_name":"test_1","id":151} null
Upsert kafka的增改时,消息体是表定义的ddl字段,以value.format定义的格式组织。删除时,消息体是null字符串。与Kafka connector不同的是,update只生产了一条记录。
行为 | create | update | delete |
---|---|---|---|
source | 读到key+数据json | 读到key+数据json | 读到key+null字符 |
sink | 产生key+数据json | 产生key+数据json | 产生key+null字符 |
至此,我们详细介绍了flink sql kafka的两个connector,对其配置、特征与应用。就我们观察到的现象而言,这两种connector适用于哪种场景吗?
至此,我们完成Flink kafka connector的学习,相信读者已经可以根据自己的业务场景灵活进行应用。下一节我们讲如何在FlinkSql中实现多路输出,敬请期待!