FLinkCDC读取MySQl中的日期问题

问题描述:

在通过FlinkCDC读取MySQL的BinLog日志的时候,发现读取到日期类型的数据和数据库中存储的相差八小时。flink版本:1.15.1,MySQLCDC版本:2.3.0如下图

解决办法:

自定义时间转换配置。代码如下


package com.yzh;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;

import io.debezium.spi.converter.CustomConverter;
import io.debezium.spi.converter.RelationalColumn;
import org.apache.kafka.connect.data.SchemaBuilder;

/**
 *  @Description:实现CustomConverter接口,重写对应方法对mysql的时间类型进行标准转换
 * @author yzh
 *
 */
public class MySqlDateTimeConverter implements CustomConverter<SchemaBuilder, RelationalColumn> {
	private DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_DATE;
	private DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_TIME;
	private DateTimeFormatter datetimeFormatter = DateTimeFormatter.ISO_DATE_TIME;
	private DateTimeFormatter timestampFormatter = DateTimeFormatter.ISO_DATE_TIME;
	private ZoneId timestampZoneId = ZoneId.systemDefault();

	@Override
	public void configure(Properties properties) {}

	@Override
	public void converterFor(RelationalColumn column, ConverterRegistration<SchemaBuilder> registration) {
		String sqlType = column.typeName().toUpperCase();
		SchemaBuilder schemaBuilder = null;
		Converter converter = null;

		if ("DATE".equals(sqlType)) {
			schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.date.string");
			converter = this::convertDate;
		}

		if ("TIME".equals(sqlType)) {
			schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.time.string");
			converter = this::convertTime;
		}

		if ("DATETIME".equals(sqlType)) {
			schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.datetime.string");
			converter = this::convertDateTime;
		}

		if ("TIMESTAMP".equals(sqlType)) {
			schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.timestamp.string");
			converter = this::convertTimestamp;
		}

		if (schemaBuilder != null) {
			registration.register(schemaBuilder, converter);
		}
	}

	private String convertDate(Object input) {
		if (input == null)
			return null;

		if (input instanceof LocalDate) {
			return dateFormatter.format((LocalDate) input);
		}

		if (input instanceof Integer) {
			LocalDate date = LocalDate.ofEpochDay((Integer) input);
			return dateFormatter.format(date);
		}
		return String.valueOf(input);
	}

	private String convertTime(Object input) {
		if (input == null)
			return null;

		if (input instanceof Duration) {
			Duration duration = (Duration) input;
			long seconds = duration.getSeconds();
			int nano = duration.getNano();
			LocalTime time = LocalTime.ofSecondOfDay(seconds).withNano(nano);
			return timeFormatter.format(time);
		}
		return String.valueOf(input);
	}

	private String convertDateTime(Object input) {
		if (input == null)
			return null;

		if (input instanceof LocalDateTime) {
			return datetimeFormatter.format((LocalDateTime) input).replaceAll("T", " ");
		}
		return String.valueOf(input);
	}

	private String convertTimestamp(Object input) {
		if (input == null)
			return null;
		if (input instanceof ZonedDateTime) {
			// mysql的timestamp会转成UTC存储,这里的zonedDatetime都是UTC时间
			ZonedDateTime zonedDateTime = (ZonedDateTime) input;
			LocalDateTime localDateTime = zonedDateTime.withZoneSameInstant(timestampZoneId).toLocalDateTime();
			return timestampFormatter.format(localDateTime).replaceAll("T", " ");
		}
		return String.valueOf(input);
	}
}

--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

		// 关键代码
		Properties debeziumProperties = new Properties();
        debeziumProperties.setProperty("converters", "dateConverters");
        debeziumProperties.setProperty("dateConverters.type", "com.yzh.MySqlDateTimeConverter"); // "com.yzh.MySqlDateTimeConverter" 更换成自己的路径


        MySqlSource<String> sourceFunction = MySqlSource.<String>builder()
                .hostname("localhost")
                .port(3306)
                .username("root")
                .password("root")
                .databaseList("1226")
                .tableList("1226.users")
                .deserializer(new JsonDebeziumDeserializationSchema())
                .startupOptions(StartupOptions.initial())
                .serverTimeZone("Asia/Shanghai")
                .debeziumProperties(debeziumProperties)	// 自定义debeziumProperties
                .build();

结果:

完美解决!!!!!

参考:

实测解决 flink cdc mysql 时间字段差8小时/差13小时问题_普罗米修斯之火的博客-CSDN博客

### Java Flink CDC 和 Doris 实现每日全量数据存储 为了满足每天存储一份完整数据的需求,可以通过以下方式利用 Java 结合 Apache Flink 的 Change Data Capture (CDC) 功能以及 Apache Doris 来完成。 #### 数据流设计 Flink CDC 能够捕获数据库中的变更日志并将其转换为事件流。这些事件流随后会被发送到 Doris 中进行处理和存储。由于 Doris 支持 `sequence` 列来管理重复键的数据更新逻辑[^1],因此可以在导入过程中通过设置该列来控制数据覆盖行为。 以下是具体实现方法: #### 配置 Sequence 列 在 Doris 表结构定义阶段,需创建一个具有唯一约束模型 (`UNIQUE`) 的表,并加入 `sequence` 列用于标记每条记录的时间戳或其他递增字段。如果未特别指定,则默认采用当前时间作为序列值(即 `CURRENT_TIMESTAMP`)。这有助于确保每次写入操作都能依据最新版本的数据执行替换动作而不丢失旧有信息。 ```sql CREATE TABLE IF NOT EXISTS user_data ( id BIGINT, name STRING, age INT, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY(id) ) ENGINE=OLAP UNIQUE KEY(id) DISTRIBUTED BY HASH(id); ``` 上述 SQL 创建了一个名为 `user_data` 的表格,其中包含了四个主要属性:用户的 ID(`id`)、姓名(`name`)、年龄(`age`) 及最后修改日期/时间(`update_time`) 。这里我们将 `update_time` 设置成自动填充的 timestamp 类型,默认情况下它会取系统现在时刻点;当某一行被更改时也会同步刷新此字段至此刻刻度上。 #### 使用 Flink 连接器读取源端变化 借助于 Debezium 或其他兼容 JDBC 协议的工具可以从关系型数据库提取实时变动情况并将它们转化为标准化 JSON 格式的 Kafka 主题消息队列形式供下游消费方订阅获取最新的业务动态详情描述如下所示: ```java import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import com.ververica.cdc.connectors.mysql.MySqlSource; public class MysqlToKafka { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); MySqlSource<String> mySqlSource = MySqlSource.<String>builder() .hostname("localhost") .port(3306) .databaseList("test_db") // monitor all tables under test_db .tableList("test_db.user_table") // alternatively specific table list can be provided here. .username("root") .password("") .deserializer(new JsonDebeziumDeserializationSchema()) .build(); env.addSource(mySqlSource).print(); env.execute("MySQL to Kafka"); } } ``` 上面这段程序展示了怎样构建起从 MySQL 数据库里持续抽取增量改动并通过打印出来展示效果的过程。实际应用当中我们通常还会把这些变更推送到像 Apache Kafka 这样的分布式消息总线之上以便后续进一步加工分析或者持久化保存起来待查用等等[^2]. #### 将变更加载至 Doris 对于已经存在于 Kafka 上面的消息体而言,可以选用多种途径把他们投递给最终目标——Doris。一种常见做法就是运用官方所提供的 RESTful API 接口来进行批量提交作业请求。下面给出了一段示范性的代码片段说明了这一过程的具体实施细节: ```java import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import org.json.JSONObject; public class SendDataToFlink { private static String constructPostRequestPayload(List<Map<String, Object>> records){ JSONObject jsonBody=new JSONObject(); JSONArray jsonArrayRecords=new JSONArray(); for(Map<String,Object> record :records ){ JSONObject jsonObjectRecord=new JSONObject(record); jsonArrayRecords.put(jsonObjectRecord); } jsonBody.put("data",jsonArrayRecords); return jsonBody.toString(); } public static int sendPOST(String urlStr,List<Map<String, Object>> payload )throws IOException{ URL obj = new URL(urlStr); HttpURLConnection con = (HttpURLConnection)obj.openConnection(); //add reuqest header con.setRequestMethod("POST"); con.setRequestProperty("Content-Type","application/json; charset=UTF-8"); //send post request body content String postData=constructPostRequestPayload(payload ); OutputStream os =con.getOutputStream (); os.write(postData.getBytes()); os.flush (); BufferedReader in =new BufferedReader(new InputStreamReader(con.getInputStream())); String inputLine ; StringBuilder response =new StringBuilder(); while ((inputLine=in.readLine())!=null){ response.append(inputLine); } in.close(); System.out.println(response.toString()); return con.getResponseCode(); } } // Example usage: try{ List<Map<String, Object>> dataPoints = Arrays.asList( Map.ofEntries(entry("id", 1L), entry("name", "Alice"), entry("age", 25)), Map.ofEntries(entry("id", 2L), entry("name", "Bob"), entry("age", 30)) ); int statusCode =SendDataToFlink.sendPOST("http://<your-doris-fe-host>:8030/api/_stream_load?db=test&label=my_label",dataPoints); }catch(Exception e){ e.printStackTrace(); } ``` 以上实例演示了如何向 Doris 发送 POST 请求以上传一批新的用户资料。注意这里的 `<your-doris-fe-host>` 应替换成真实的前端服务地址[^3]。 #### 总结 综上所述,在日常工作中要达成保留每一天完整的副本这样的需求的话,就可以考虑采取本文所介绍的技术方案组合—即先依靠 Flink-CDC 不断捕捉源头系统的任何改变再经由中间件比如 Kafka 缓冲之后最后才抵达目的地 Doris 完成入库归档整个流程闭环运作模式从而达到预期目的。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值