前序:FlinkCDC-Hudi系列文章:
FlinkCDC-Hudi:Mysql数据实时入湖全攻略一:初试风云
FlinkCDC-Hudi:Mysql数据实时入湖全攻略二:Hudi与Spark整合时所遇异常与解决方案
FlinkCDC-Hudi:Mysql数据实时入湖全攻略三:探索实现FlinkCDC mysql 主从库同步高可用
一、背景
在生产实践中,通过FlinkCDC读取数据,除了落地hadoop入湖供下游离线使用外,也会存在写入kafka供实时程序消费使用。
那么flink里,kafka connector有哪些?各有什么特征?使用时要注意什么呢?且让我们开始flink kafka connector探索之旅。
二、测试环境准备
2.1 基础运行环境搭建
在开始实操探索之前,至少确保你已经搭建好了FlinkCDC-Hudi的运行环境。本文的测试环境基于FlinkCDC-Hudi:Mysql数据实时入湖全攻略一:初试风云。如果仅对flinkcdc写入kafka感兴趣,至少准备flink环境和flinkcdc依赖。
2.2 kafka sql connector环境搭建
测试flink sql写kafka,需要添加运行依赖flink-sql-connector-kafka。笔者使用的版本是flink-sql-connector-kafka_2.11-1.13.5.jar。
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-sql-connector-kafka_2.11</artifactId>
<version>1.13.5</version>
<scope>provided</scope>
</dependency>
读者可以根据自己的运行环境下载对应的依赖包。maven依赖下载: flink-connector-kafka
2.3 Kafka 集群环境
笔者kafka使用kafka-2.7.0版本。读者如未配置kafka,可参见官方文档Kafka快速入门
2.4 mysql 环境准备
笔者在FlinkCDC-Hudi:Mysql数据实时入湖全攻略三:探索实现FlinkCDC mysql 主从库同步高可用 搭建了一主二从的Mysql环境,笔者的运行环境依赖使用这个环境。读者可以依此搭建。
读者如果使用自己的环境,需要确认mysql开启binlog并授以flinkcdc测试账号相应权限。
三、前置运行代码
本文相关测试在flink sql上运行。在搭建好上述环境后,执行以下后置代码,然后进入flink sql kafka connector测试环境。
3.1 mysql ddl
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;
3.2 启动flink sql client
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
3.3 设置Flink sql 运行变量
Flink SQL> set execution.checkpointing.interval=30sec;
Flink SQL> set pipeline.name = flinkcdc_test_1;
3.4 flink sql mysqlcdc ddl:
Flink SQL> create table mysql_test_1(
table_name STRING METADATA FROM 'table_name' VIRTUAL,
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'
);
四、FlinkSQL Kafka connector的两种实现
通过查阅Flink官方文档Connector/Table API Connectors,我们知道Flink kafka connector有两种实现:kafka和upsert kafka。这两种connector有什么特点呢?下面一一揭晓。
五、Kafka sql connector
Kafka sql connector是基础的kafka应用封装,用于生产/消费指写topic的数据。
5.1 元数据
这个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'
);
5.2 配置与特征
使用这个connector时相关配置有很多,详情可见Flink/table/kafka connector。这里结合配置项做一些关键特征介绍。
5.2.1 connector类型
connector=kafka,必选 ,指定为使用kafka sql connector。
5.2.2 kafka相关基础配置
- 必选项,properties.bootstrap.servers,指定集群
- 必选项,topic/topic-pattern,二选一。topic-pattern可指定多个topic,用分号分隔。
- 可选项,properties.group.id,消费组信息,不配置会按格式“KafkaSource-{tableIdentifier}”生成。
- 更多的kafka原生配置通过 properties.*配置。如properties.security.protocol。
5.2.3 序列化相关
- 必选项,format,value.format,二选一,指定消息体序列与反序列格式。
- 可选项,key.format,指定消息key的序列化与反序列化格式。
- 可选项,key.fields,指定主键字段,多个字段使用分号分隔。key.fields在生产消息时会使用key.format格式,根据分区函数发到相应分区。
- 可选项,key.fields-prefix,如果key里的字段与value里的字段冲突时,可以配置Key前缀来解决冲突。
常用格式有csv,json,debezium-json,avro,raw。格式配置错误的话,会导致解析异常,进行导致作业失败。
更多格式与信息参看connetors/table/format
5.2.4 kafka sourse配置
scan.*,定义消费相关的配置。可以配置消费模式,起始offset,起始timestamp,分区发现时间间隔。
- scan.startup.mode,消费启动模式。支持以下值配置:
值 | 说明 |
---|---|
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 |
- scan.topic-partition-discovery.interval,配置动态发现扫描时间间隔。定期扫描更新元数据,用于动态topic、动态分区发现。
5.2.5 kafka sink配置
sink.*,定义生产相关的配置。可配置key partitionner,生产一致性语义,生产并发。
- sink.partitioner,分区函数。
- sink.delivery-guarantee,消息传递一致性保证。支持以下值:
值 | 说明 |
---|---|
none | 不提供任何保证,可能丢数,可能重复 |
at-least-once | 至少一次,保证不丢数,可能会重复 |
exactly-once | 精确一次。使用kafka事务保证。下游消费者需要配置隔离等级。isolation.level (read_committed 或 read_uncommitted |
- sink.parallelism,生产并发度。默认使用与flink算子链相同的并发度。
5.3 Kafka sql connector应用
5.3.1 Kafka sql table ddl
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格式。
5.3.2 启动flink kafka sql connector作业
5.3.2.1 启动sink kafka作业
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;
在flink session集群中顺利启动flink作业。
5.3.2.2 启动source kafka作业
在FlinkSql client中启动表查询:
Flink SQL> select * from kafka_test_1;
FlinkSql source kafka表视图:
5.3.3 Kafka消息验证
开启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
5.3.3.1 insert语句验证
在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字段为修改前内容
- after有修改后的内容
- op为操作类型,有两种值:c - create, d - delete。
{
"before": null,
"after": {
"table_name": "test_1",
"id": 149,
"data": "d1",
"create_time": "2022-02-18 20:1:55"
},
"op": "c"
}
对应flinkSql视图查询到一条新增数据:
5.3.3.2 update语句验证
通过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"}
对应FlinkSQL视图查询到update后的数据:
5.3.3.3 delete语句验证
在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"}
FlinkSQL视图中数据被删除。
5.3.4 Kafka sql connector应用总结
行为 | create | update | delete |
---|---|---|---|
source | 读取c记录 | 读取到2条记录,合并为最新镜像 | 删除1条记录 |
sink | 产生1条c记录 | 产生2条记录,c-d | 产生1条d记录 |
六、Upsert Kafka connector
6.1 使用特点
Upsert Kafka connector 允许以 upsert 方式从 Kafka topic中读取或写入数据。
在upsert模式中,变量日志流中,所有的insert、update、delete事件都可以理解为update事件,任一事件发生时,对应记录的所有字段值都会更新为最新的值,delete视为将值更新为null。将数据写入kafka的时候,数据会按key进行分区,确保相同的key都会以相同的顺序进入到相同的分区。
6.2 配置
Upsert kafka connector配置与kafka connector的大致相同,两个关键的新增的配置如下:
- sink.buffer-flush.max-rows,每次发送前最大的缓存记录数。缓存时,相同的key将会保留最新的记录。这样可以减少发送给kafka的数据,减少io shuffle。默认值为0,即不开启。配置时应与sink.buffer-flush.interval一起配置,配置值不可为负数。
- sink.buffer-flush.interval,缓存刷新的时间间隔,超过配置的时间间隔后,将会发送一次数据。
6.3 Upsert kafka connector应用
6.3.1 Upsert kafka sql table ddl
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作业
6.3.2.1启动sink kafka作业
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;
6.3.2.1 启动source kafka作业
Flink SQL>select * from upsert_kafka_test_2;
6.3.3 Kafka消息验证
6.3.3.1 insert语句验证
执行一条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"}
在FlinkSql client中直接新增数据:
6.3.3.2 update语句验证
在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"}
FlinkSQL client展示了更新后的数据:
6.3.3.3 delete语句验证
在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
在FlinkSQL client中对应的记录被删除:
6.3.4 Upsert kafka connector应用总结
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适用于哪种场景吗?
- Kafka sql connector提供常规的kafka生产与消费行为,适用于大部分应用场景。对于日志变更流,如果关心数据是如何变化的,可以选择Kafka sql connector。
- Upsert Kafka sql connector提供的是upsert模式的生产与消费,适用于有数据更新合并,但只关心结果状态,不关心过程变化的应用。Upsert kafka开启buff后还能进一步减少数据量,减轻shuffle的压力。
至此,我们完成Flink kafka connector的学习,相信读者已经可以根据自己的业务场景灵活进行应用。下一节我们讲如何在FlinkSql中实现多路输出,敬请期待!