關(guān)聯(lián)文章:
各種時間類型和timezone關(guān)系淺析
一、測試目的和值
1. 測試一般的數(shù)據(jù)庫不含time zone的類型的時區(qū)。
- mysql timestamp(3) 類型
- postgres timestamp(3) 類型
- sqlserver datetime2(3) 類型
- oracle類型 TIMESTAMP(3) 類型
在以下測試之中均為ts
字段
2.測試CDC中元數(shù)據(jù)op_ts 時區(qū)
op_tsTIMESTAMP_LTZ(3) NOT NULL當前記錄表在數(shù)據(jù)庫中更新的時間。如果從表的快照而不是 binlog 讀取記錄,該值將始終為0。|
在以下測試中cdc表建表均使用ts_ms TIMESTAMP_LTZ(3) METADATA FROM 'op_ts' VIRTUAL
表示。
cdc在讀取表時候分兩個階段:
- 全量讀取階段,特點是jdbc讀取,讀取數(shù)據(jù)中
op=r
- 增量讀取階段,特點是log讀取,讀取數(shù)據(jù)中
op=c或u或d
op在截圖中看到如3="r"
或者3="r"
,3是op字段的索引值。ts_ms
在全量階段讀取數(shù)據(jù)以下成為READ數(shù)據(jù)
ts_ms
在增量階段讀取數(shù)據(jù)以下成為CREATE數(shù)據(jù)
3. flink 數(shù)據(jù)時間表示和時區(qū)
flink Table中時間必須使用org.apache.flink.table.data.TimestampData
對象表示。
@PublicEvolving
public final class TimestampData implements Comparable<TimestampData> {
private final long millisecond;
private final int nanoOfMillisecond;
}
此類型使用如下兩個值聯(lián)合表示記錄時間。并不記錄時區(qū)數(shù)據(jù)。
實戰(zhàn)測試:
@Test
public void testTimeZone(){
// 常識:Epoch就是值utc的0時間點,是全局絕對時間點,本質(zhì)是`ZoneOffset.of("+0")`下的0時間。與`January 1, 1970, 00:00:00 GMT`視為等同。
// GMT是前世界標準時,UTC是現(xiàn)世界標準時。UTC 比 GMT更精準,以原子時計時,適應(yīng)現(xiàn)代社會的精確計時。
// 28800000=8*3600*1000。8小時毫秒值。
// 如下時間是+8時區(qū)的數(shù)據(jù)庫存儲的不帶時區(qū)的時間:2023-09-28T09:43:20.320
long ts=1695894200320L;
// 如果將ts當做utc時間0時刻轉(zhuǎn)為字符串則會導(dǎo)致時間+8 hour。2023-09-28 17:43:20。這是一般常用的在線轉(zhuǎn)換時間的結(jié)果。因其默認是是epoch時間,所以轉(zhuǎn)換后會+8h。
// 可見數(shù)據(jù)庫讀取的不帶timezone時間的毫秒值,并不是以utc0時間(epoch)為基準的,而是以當前時區(qū)0為基準的。
// LocalDateTime對象本質(zhì)支持LocalDate和LocalTime兩個對象,LocalDate持有Integer的`年`,`月`,`日`。LocalTime則持有Integer的`時`,`分`,`秒`等和java.util.Date類型并不一樣。
// LocalDateTime 的帶有ZoneOffset方法比較難理解,此處:
// epochSecond 當然值的是epoch的秒數(shù),是絕對時間概念和`java.util.Date.getTime()/1000`對應(yīng)的,而offset是指此epoch秒數(shù)需要偏移的時間量。
// 內(nèi)部代碼是`long localSecond = epochSecond + offset.getTotalSeconds();`。
// 如下代碼是正確的,因為java中的`java.util.Date`類和`java.sql.Timestamp`類型都是持有絕對時間的類,`Date.getTime`獲得也是相對于Epoch的毫秒值(Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT)。
LocalDateTime ldtFromDate = LocalDateTime.ofEpochSecond(new Date().getTime() / 1000, 0, ZoneOffset.of("+8"));
System.out.println(ldtFromDate); // 2023-09-28T16:16:45。此時時鐘也是16:17:44。
Date date0 = new Date(0); // number of milliseconds since the standard base time known as "the epoch"
System.out.println(date0.getTime()); // 0, date0.getTime()方法返回絕對時間Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
// 如下的提供`ZoneOffset.UTC`可以理解是告訴LocalDateTime我提供的epochSecond已是`localSecond=當?shù)貢r間-當?shù)貢r間的0點`不需要再做轉(zhuǎn)換了。
LocalDateTime ldt0 = LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC);
System.out.println(ldt0); // 1970-01-01T00:00
LocalDateTime ldt8 = LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.of("+8"));
System.out.println(ldt8); // 1970-01-01T08:00
// TimestampData 默認不會進行任何時區(qū)轉(zhuǎn)換。也不存儲任何時區(qū)信息。內(nèi)部僅靠`long millisecond`和`int nanoOfMillisecond`存儲信息,以便于序列化。
// millisecond 一般可以認為是本地時間。因其在toString方法中會不會進行時區(qū)轉(zhuǎn)換,toString方法僅是調(diào)用了`toLocalDateTime()`,中進行簡單運算,并最終調(diào)用`LocalDateTime.toString`方法。
TimestampData td0 = TimestampData.fromEpochMillis(0); // 相當于LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC)。
System.out.println(td0); // 1970-01-01T00:00??梢奣imestampData輸出轉(zhuǎn)字符串的時間就是以utc時間為基準的這和java.util.Date類型是一致的。
LocalDateTime ldt = LocalDateTime.ofEpochSecond(
ts / 1000
, (int) (ts % 1000 * 1_000_000)
, ZoneOffset.UTC);
System.out.println(ldt); // 2023-09-28T09:43:20.320
TimestampData td = TimestampData.fromEpochMillis(ts);
System.out.println(td); // 2023-09-28T09:43:20.320
Date date = new Date(ts); // 注意:參數(shù)date(the specified number of milliseconds since the standard base time known as "the epoch")應(yīng)該是epoch但此時ts并不是epoch基準的而是本地local基準的。
System.out.println(date); // Thu Sep 28 17:43:20 CST 2023,CST就是北京時間了,其在toString方法中`BaseCalendar.Date date = normalize();`進行了時區(qū)轉(zhuǎn)換即+8了。
}
4. 測試組件版本
- flink 1.13
- flink-cdc 2.2.1
- flink-connector-jdbc 自己定制的,根據(jù)
3.1.1-1.17
版本修改而來。
二、本測試共測試四大數(shù)據(jù)庫:
- mysql
- postgres
- sqlserver
- oracle
二、每種數(shù)據(jù)庫測試8項:
- database-SQL
直接從數(shù)據(jù)中讀取數(shù)據(jù),是測試的基準值 - cdc-RowData
使用cdc的SQL API從數(shù)據(jù)庫中讀取值并在com.ververica.cdc.debezium.table.AppendMetadataCollector#collect
方法中debug得到數(shù)據(jù) - cdc-SQL(測試除ts_ms的字段)
使用cdc的SQL API讀取值使用flink sql-client查詢,用于測試除ts_ms的字段。因ts_ms準確性需分兩種情況討論。 - cdc-SQL-RealTime(測試ts_ms)
使用cdc的SQL API從讀取值,左上角是系統(tǒng)時間,下側(cè)是實時讀取的數(shù)據(jù)。 - cdc-Read數(shù)據(jù)(測試snapshot讀取ts_ms字段)
測試snapshot讀取ts_ms字段,即全量讀取階段的ts_ms值,按照flink-cdc官方解釋此四個數(shù)據(jù)的全量階段值均為0(1970-01-01 00:00:00
)。非0即為不正確。 - cdc-Create數(shù)據(jù)(測試incremental讀取ts_ms字段)
測試incremental讀取ts_ms字段,即增量讀取階段的ts_ms值。按照flink-cdc官方解釋此四個數(shù)據(jù)的增量階段值為數(shù)據(jù)日志記錄時間。 - jdbc-RowData
使用flink SQL API 讀取connector是jdbc
的表數(shù)據(jù)org.apache.flink.connector.jdbc.table.JdbcRowDataInputFormat#nextRecord
的方法中debug得到數(shù)據(jù)。。不含tm_ms數(shù)據(jù)。 - jdbc-SQL
使用flink SQL API 讀取connector是jdbc
的表數(shù)據(jù)。使用flink sql-client查詢。。不含tm_ms數(shù)據(jù)。
三、測試過程數(shù)據(jù)
3.1 mysql
3.1.1 database-SQL
3.1.2 cdc-RowData
3.1.3 cdc-SQL(測試除ts_ms的字段)
3.1.4 cdc-SQL-RealTime(測試ts_ms)
如下:上側(cè)(win系統(tǒng)顯示時間截圖),下側(cè)(cdc-query的ts_ms)
如果基本一致(不是差值8h),說明cdc-query的ts_ms是正確的的。
3.1.5 cdc-Read數(shù)據(jù)(測試snapshot讀取ts_ms字段)
3.1.6 cdc-Create數(shù)據(jù)(測試incremental讀取ts_ms字段)
3.1.7 jdbc-RowData
3.1.8 jdbc-SQL
3.2 postgres
3.2.1 database-SQL
3.2.2 cdc
cdc-RowData
3.2.3 cdc-SQL(測試除ts_ms的字段)
3.2.4 cdc-SQL-RealTime(測試ts_ms)
3.2.5 cdc-Read數(shù)據(jù)(測試snapshot讀取ts_ms字段)
3.2.6 cdc-Create數(shù)據(jù)(測試incremental讀取ts_ms字段)
3.2.7 jdbc
jdbc-RowData
3.2.8 jdbc-SQL
3.3 sqlserver
3.3.1 database-SQL
3.3.2 cdc-RowData
3.3.3 cdc-SQL(測試除ts_ms的字段)
3.3.4 cdc-SQL-RealTime(測試ts_ms)
3.3.5 cdc-Read數(shù)據(jù)(測試snapshot讀取ts_ms字段)
3.3.6 cdc-Create數(shù)據(jù)(測試incremental讀取ts_ms字段)
3.3.7 jdbc-RowData
3.3.8 jdbc-SQL
3.4 oracle
3.4.1 database-SQL
3.4.2 cdc-RowData
3.4.3 cdc-SQL(測試除ts_ms的字段)
3.4.3 cdc-SQL-RealTime(測試ts_ms)
3.4.4 cdc-Read數(shù)據(jù)(測試snapshot讀取ts_ms字段)
3.4.5 cdc-Create數(shù)據(jù)(測試incremental讀取ts_ms字段)
3.4.7 jdbc-RowData
3.4.8 jdbc-SQL
四、結(jié)論
(1)數(shù)據(jù)庫獲取的without time zone
在flink中都是以本地時間的存儲的。可以使用LocalDateTime.ofEpochSecond(long epochSecond, int nanoOfSecond, ZoneOffset.UTC)
直接獲取。
(2)Flink中的TimestampData中存儲的一般可以認為是本地時間。但需要注意:TimestampData 不可將 instant 相關(guān)方法
和 localDateTime 、Timestamp 相關(guān)方法
混用。因為instant代表與epoch時間差。而后兩者代表與local是時間差。
(3)Flink程序中時間的標準值都是local本地的。因其在Sql API(sql-client)中打印出的結(jié)果會與原始數(shù)據(jù)庫中打印的一致。文章來源:http://www.zghlxwxcb.cn/news/detail-737634.html
如下圖中紅色字體的是錯誤的數(shù)據(jù),使用CDC需要額外注意并進行轉(zhuǎn)換。文章來源地址http://www.zghlxwxcb.cn/news/detail-737634.html
五、附錄
5.1 查詢數(shù)據(jù)庫時區(qū)SQL
-- mysql 以:time_zone 為準,system_time_zone至服務(wù)器時區(qū)
show variables like '%time_zone%';
-- postgres
show time zone;
-- sqlserver
DECLARE
@TimeZone NVARCHAR(255)
EXEC
master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE'
,
N'SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
,
N'TimeZoneKeyName'
,
@TimeZone
OUTPUT
SELECT
@TimeZone
-- oracle
select dbtimezone from dual;
到了這里,關(guān)于【時區(qū)】Flink JDBC 和CDC時間字段時區(qū) 測試及時間基準的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!