flink-cdc 能够读取binlog日志,从而实现mysql数据到ES的秒级同步。
好用的同时又有很多烦恼,其中时间格式就是一个很头痛的问题。
直接进入正题。
使用es7和mysql 5.7为例
1.时间类型参照
首先我们已知mysql 有date 和timestamp(或者datetime)两种时间格式。
对应到ES是标准的date格式。
mysql 的date 类型格式如:"1993-02-01", 对应的ES的标准格式为:"1993-01-31T16:00:00.000Z"。
mysql的timestamp或datetime类型格式如: "1993-02-01 08:45:27",对应到es的ES标准格式类型为:"1993-02-01T00:45:27.000Z"
flink-cdc 时间格式与Mysql和es时间格式类型对照
flink-cdc | mysql | elasticsearch |
date | date | date |
timestamp | timestamp(datetime) | date |
timestamp_ltz | timestamp(datetime) | date |
那么处理时间格式上我们就可以用两种方法:
1.对于新建的ES索引在建立mapping的时候将date进行格式化处理
PUT db_test {
"mappings": {
"properties": {
"update_time": {
type: "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
这样新的的索引就只需要对照flink-cdc和es的时间格式,进行处理操作就行。
以下是sql-client的展现
create table tb_test_source (
id bigint,
update_time timestamp,
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
...
);
create table tb_test_sink (
id binint,
update_time timestamp,
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'elasticsearch-7',
...
);
insert into tb_test_sink select * from tb_test_source;
2.当索引原本存在,且数据重要,数据量非常大的时候,要转为标准的ESdate格式。
首先我们知道timestamp_ltz(3)带时区的时间格式,这种时间格式转化出来就是ES的标准格式样式,但是会丢失毫秒级的精度。
如果对时间精度没有要求同步到es以后时间,es中update_time 的时间格式就是:"yyyy-MM-ddTHH:mm:ssZ",如果你原本的时间格式对精度没有要求,以下的这段sql已经可以满足你的要求了。但是注意两行标注非常重要的配置,如果没有这两行配置,那么默认的时间格式标准就会是sql标准。在同步到ES中是会报错的。
create table tb_test_source (
id bigint,
update_time timestamp,
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
...
);
create table tb_test_sink (
id binint,
update_time timestamp_ltz(3),
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'elasticsearch-7',
...
'format' = 'json', //非常重要的配置
'json.timestamp-format.standard' = 'ISO-8601' //非常重要的配置
);
insert into tb_test_sink select id,cast(update_time as timestamp_ltz(3)) from tb_test_source;
非常不幸的是,我遇到的情况是需要保留时间精度为3的一堆原始数据,这样导致程序查询时序列化失败。
于是我就考虑是不是能把时间转化为"1993-01-31T16:00:00.000Z"类似的时间字符串,让es自动去识别时间字符串。
下面就用代码展示下我的解决方法,虽然暂时还不算完美:
create table tb_test_source (
id bigint,
update_time timestamp,
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
...
);
create table tb_test_sink (
id binint,
update_time string, //注意是字符串类型
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'elasticsearch-7',
...
);
insert into tb_test_sink
select
id,
date_format(timestampadd(HOUR,-8,update_time),'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''') //对时间进行格式化
from tb_test_source;
通过以上方法,终于将时间格式问题解决。
同时在ES中检索,发现同步入的数据是具有时间属性的,虽然有些波折,但是也得到了解决。
同时附上一个pyflink的代码,以便后期阐释后期关于链表出现的问题。
from datetime import datetime,timedelta
from pyflink.table import EnvironmentSettings as envs,TableEnvironment as tenv,StreamTableEnvironment as stenv,TableConfig,DataTypes
from pyflink.datastream import StreamExecutionEnvironment as senv
from pyflink.common.types import RowKind
settings = senv.get_execution_environment()
settings.set_parallelism(1)
settings.enable_checkpointing(300)
tableconfig = TableConfig()
tableconfig.set_null_check(False)
t_env = stenv.create(settings,tableconfig)
t_env.get_config().get_configuration().set_string("pipeline.jar",
("file:///data/flink-1.13.5/lib/flink-sql-connector-mysql-cdc-2.1.1.jar;"
+"file:///data/flink-1.13.5/lib/flink-sql-connector-elasticsearch6_2.11-1.13.5.jar"))
t_env.get_config().get_configuration().set_string("pipeline.classpaths",
("file:///data/flink-1.13.5/lib/flink-sql-connector-mysql-cdc-2.1.1.jar;"
+"file:///data/flink-1.13.5/lib/flink-sql-connector-elasticsearch6_2.11-1.13.5.jar"))
t_env.execute_sql("""CREATE TABLE tb_test_source (
id bigint,
create_time timestamp(3),
modify_time timestamp(3),
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
......
)""")
t_env.execute_sql("""
CREATE TABLE tb_test_sink (
`id` bigint,
`create_time` STRING,
`modify_time` STRING,
PRIMARY KEY (`id`) NOT ENFORCED
)WITH (
'connector' = 'elasticsearch-6',
....
)""")
sourceTable = t_env.from_path("tb_linkman_in")
sdata = sourceTable.select(
sourceTable.id,sourceTable.name,sourceTable.sex,
sourceTable.create_time,sourceTable.modify_time) #为什么这里不直接进行时间处理?
tres = sdata.execute() #为什么这里不直接插入ES?要绕一次再写入数据,这个就涉及连表查询时,mysql多源表数据的时间差问题。 待下次在阐述其中缘由。
with tres.collect() as resluts:
for reslut in resluts:
if reslut.get_row_kind() == RowKind.UPDATE_BEFORE:
continue
reslut.set_field_names(["id","create_time","modify_time"])
resl = reslut.as_dict()
createtime = resl["create_time"]
modify_time = resl["modify_time"]
if isinstance(createtime,datetime):
createtime = createtime + timedelta(hours=-8)
resl["create_time"] = createtime.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]+"Z"
if isinstance(modify_time,datetime):
modify_time = modify_time + timedelta(hours=-8)
resl["modify_time"] = modify_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]+"Z"
res = tuple(resl.values())
rows = list()
rows.append(res)
s = t_env.from_elements(rows,DataTypes.ROW([
DataTypes.FIELD('id',DataTypes.BIGINT()),
DataTypes.FIELD('create_time',DataTypes.STRING()),
DataTypes.FIELD('modify_time',DataTypes.STRING()),
]),False)
s.execute_insert('tb_test_link')