东八区转为0时区_踩坑记 | Flink 天级别窗口中存在的时区问题
本系列每篇文章都是從一些實(shí)際的 case 出發(fā),分析一些生產(chǎn)環(huán)境中經(jīng)常會(huì)遇到的問(wèn)題,拋磚引玉,以幫助小伙伴們解決一些實(shí)際問(wèn)題。本文介紹 Flink 時(shí)間以及時(shí)區(qū)問(wèn)題,分析了在天級(jí)別的窗口時(shí)會(huì)遇到的時(shí)區(qū)問(wèn)題,如果對(duì)小伙伴有幫助的話,歡迎點(diǎn)贊 + 再看~
?本文主要分為兩部分:
第一部分(第 1 - 3 節(jié))的分析主要針對(duì) flink,分析了 flink 天級(jí)別窗口的中存在的時(shí)區(qū)問(wèn)題以及解決方案。
第二部分(第 4 節(jié))的分析可以作為所有時(shí)區(qū)問(wèn)題的分析思路,主要以解決方案中的時(shí)區(qū)偏移量為什么是加 8 小時(shí)為案例做了通用的深度解析。
為了讓讀者能對(duì)本文探討的問(wèn)題有一個(gè)大致了解,本文先給出問(wèn)題 sql,以及解決方案。后文給出詳細(xì)的分析~
1.問(wèn)題以及解決方案
問(wèn)題 sql
sql 很簡(jiǎn)單,用來(lái)統(tǒng)計(jì)當(dāng)天累計(jì) uv。
---------------?偽代碼?---------------INSERT?INTO
??kafka_sink_table
SELECT
??--?窗口開(kāi)始時(shí)間
??CAST(
????TUMBLE_START(proctime,?INTERVAL?'1'?DAY)?AS?bigint
??)?AS?window_start,
??--?當(dāng)前記錄處理的時(shí)間
??cast(max(proctime)?AS?BIGINT)?AS?current_ts,
??--?每個(gè)桶內(nèi)的?uv
??count(DISTINCT?id)?AS?part_daily_full_uv
FROM
??kafka_source_table
GROUP?BY
??mod(id,?bucket_number),
??--?bucket_number?為常數(shù),根據(jù)具體場(chǎng)景指定具體數(shù)值
??TUMBLE(proctime,?INTERVAL?'1'?DAY)
---------------?偽代碼?---------------
你是否能一眼看出這個(gè) sql 所存在的問(wèn)題?(PS:數(shù)據(jù)源以及數(shù)據(jù)匯時(shí)區(qū)都為東八區(qū))
「沒(méi)錯(cuò),天級(jí)別窗口所存在的時(shí)區(qū)問(wèn)題,即這段代碼統(tǒng)計(jì)的不是樓主所在東八區(qū)一整天數(shù)據(jù)的 uv,這段代碼統(tǒng)計(jì)的一整天的范圍在東八區(qū)是第一天早 8 點(diǎn)至第二天早 8 點(diǎn)。」
解決方案
樓主目前所處時(shí)區(qū)為東八區(qū),解決方案如下:
---------------?偽代碼?---------------CREATE?VIEW?view_table?AS
SELECT
???id,
???--?通過(guò)注入時(shí)間解決
???--?加上東八區(qū)的時(shí)間偏移量,設(shè)置注入時(shí)間為時(shí)間戳列
???CAST(CURRENT_TIMESTAMP?AS?BIGINT)?*?1000?+?8?*?60?*?60?*?1000?as?ingest_time
FROM?
???source_table;
INSERT?INTO
??target_table
SELECT
??CAST(
????TUMBLE_START(ingest_time,?INTERVAL?'1'?DAY)?AS?bigint
??)?AS?window_start,
??cast(max(ingest_time)?AS?BIGINT)?-?8?*?3600?*?1000?AS?current_ts,
??count(DISTINCT?id)?AS?part_daily_full_uv
FROM
??view_table
GROUP?BY
??mod(id,?1024),
???--?根據(jù)注入時(shí)間劃分天級(jí)別窗口
??TUMBLE(ingest_time,?INTERVAL?'1'?DAY)
---------------?偽代碼?---------------
通過(guò)上述方案,就可以將統(tǒng)計(jì)的數(shù)據(jù)時(shí)間范圍調(diào)整為東八區(qū)的今日 0 點(diǎn)至明日 0 點(diǎn)。下文詳細(xì)說(shuō)明整個(gè)需求場(chǎng)景以及解決方案的實(shí)現(xiàn)和分析過(guò)程。
2.需求場(chǎng)景以及實(shí)現(xiàn)方案
需求場(chǎng)景
coming,需求場(chǎng)景比較簡(jiǎn)單,就是消費(fèi)上游的一個(gè)埋點(diǎn)日志數(shù)據(jù)源,根據(jù)埋點(diǎn)中的 id 統(tǒng)計(jì)當(dāng)天 0 點(diǎn)至當(dāng)前時(shí)刻的累計(jì) uv,按照分鐘級(jí)別產(chǎn)出到下游 OLAP 引擎中進(jìn)行簡(jiǎn)單的聚合,最后在 BI 看板進(jìn)行展示,沒(méi)有任何維度字段(感動(dòng)到哭?)。
數(shù)據(jù)鏈路以及組件選型
客戶端用戶行為埋點(diǎn)日志 -> logServer -> kafka -> flink(sql) -> kafka -> druid -> BI 看板。
實(shí)現(xiàn)方案以及具體的實(shí)現(xiàn)方式很多,這次使用的是 sql API。
flink sql schema
source 和 sink 表 schema 如下(只保留關(guān)鍵字段):
---------------?偽代碼?---------------CREATE?TABLE?kafka_sink_table?(
??--?天級(jí)別窗口開(kāi)始時(shí)間
??window_start?BIGINT,
??--?當(dāng)前記錄處理的時(shí)間
??current_ts?BIGINT,
??--?每個(gè)桶內(nèi)的?uv(處理過(guò)程對(duì)?id?進(jìn)行了分桶)
??part_daily_full_uv?BIGINT
)?WITH?(
?--?...?
);
CREATE?TABLE?kafka_source_table?(
??--?...?
??--?需要進(jìn)行?uv?計(jì)算的?id
??id?BIGINT,
??--?處理時(shí)間
??proctime?AS?PROCTIME()
)?WITH?(
??--?...?
);
---------------?偽代碼?---------------
flink sql transform
---------------?偽代碼?---------------INSERT?INTO
??kafka_sink_table
SELECT
??--?窗口開(kāi)始時(shí)間
??CAST(
????TUMBLE_START(proctime,?INTERVAL?'1'?DAY)?AS?bigint
??)?AS?window_start,
??--?當(dāng)前記錄處理的時(shí)間
??cast(max(proctime)?AS?BIGINT)?AS?current_ts,
??--?每個(gè)桶內(nèi)的?uv
??count(DISTINCT?id)?AS?part_daily_full_uv
FROM
??kafka_source_table
GROUP?BY
??mod(id,?bucket_number),
??--?bucket_number?為常數(shù),根據(jù)具體場(chǎng)景指定具體數(shù)值
??TUMBLE(proctime,?INTERVAL?'1'?DAY)
---------------?偽代碼?---------------
使用 early-fire 機(jī)制(同 DataStream API 中的 ContinuousProcessingTimeTrigger),并設(shè)定觸發(fā)間隔為 60 s。
在上述實(shí)現(xiàn) sql 中,我們對(duì) id 進(jìn)行了分桶,那么每分鐘輸出的數(shù)據(jù)條數(shù)即為 bucket_number 條,最終在 druid 中按照分鐘粒度將所有桶的數(shù)據(jù)進(jìn)行 sum 聚合,即可得到從當(dāng)天 0 點(diǎn)累計(jì)到當(dāng)前分鐘的全量 uv。
時(shí)區(qū)問(wèn)題
?18???:
「頭文字 ∩ 技術(shù)小哥哥」:使用 sql,easy game,閑坐摸魚...
「頭文字 ∩ 技術(shù)小哥哥」:等到 「00:00」 時(shí),發(fā)現(xiàn)指標(biāo)還在不停地往上漲,難道是 sql 邏輯錯(cuò)了,不應(yīng)該啊,試過(guò)分鐘,小時(shí)級(jí)別窗口都木有這個(gè)問(wèn)題
「頭文字 ∩ 技術(shù)小哥哥」:摳頭ing,算了,稍后再分析這個(gè)問(wèn)題吧,現(xiàn)在還有正事要干?
「頭文字 ∩ 技術(shù)小哥哥」:到了早上,瞅了一眼配置的時(shí)間序列報(bào)表,發(fā)現(xiàn)在 「08:00」 點(diǎn)的時(shí)候指標(biāo)歸零,重新開(kāi)始累計(jì)。woc,想法一閃而過(guò),東八區(qū)?(當(dāng)時(shí)為啥沒(méi) format 下 sink 數(shù)據(jù)中的 window_start...)
?3.問(wèn)題定位
問(wèn)題說(shuō)明
flink 在使用時(shí)間的這個(gè)概念的時(shí)候是基于 java 時(shí)間紀(jì)元(即格林威治 1970/01/01 00:00:00,也即 Unix 時(shí)間戳為 0)概念的,窗口對(duì)齊以及觸發(fā)也是基于 java 時(shí)間紀(jì)元[1]。
問(wèn)題場(chǎng)景復(fù)現(xiàn)
可以通過(guò)直接查看 sink 數(shù)據(jù)的 window_start 得出上述結(jié)論。
但為了還原整個(gè)過(guò)程,我們按照如下 source 和 sink 數(shù)據(jù)進(jìn)行整個(gè)問(wèn)題的復(fù)現(xiàn):
source 數(shù)據(jù)如下:
sink 數(shù)據(jù)(「為了方便理解,直接按照 druid 聚合之后的數(shù)據(jù)展示」):
從上述數(shù)據(jù)可以發(fā)現(xiàn),天級(jí)別窗口「開(kāi)始時(shí)間」在 UTC + 8(北京)的時(shí)區(qū)是每天早上 8 點(diǎn),即 UTC + 0(格林威治)的凌晨 0 點(diǎn)。
「下文先給出解決方案,然后詳細(xì)解析各個(gè)時(shí)間以及時(shí)區(qū)概念~」
解決方案
- 「框架層面解決」:Blink Planner 支持時(shí)區(qū)設(shè)置[2]
- 「sql層面解決」:從 sql 實(shí)現(xiàn)層面給出解決方案
sql 層面解決方案
---------------?偽代碼?---------------CREATE?VIEW?view_table?AS
SELECT
???id,
???--?通過(guò)注入時(shí)間解決
???--?加上東八區(qū)的時(shí)間偏移量,設(shè)置注入時(shí)間為時(shí)間戳列
???CAST(CURRENT_TIMESTAMP?AS?BIGINT)?*?1000?+?8?*?60?*?60?*?1000?as?ingest_time
FROM?
???source_table;
INSERT?INTO
??target_table
SELECT
??CAST(
????TUMBLE_START(ingest_time,?INTERVAL?'1'?DAY)?AS?bigint
??)?AS?window_start,
??cast(max(ingest_time)?AS?BIGINT)?-?8?*?3600?*?1000?AS?current_ts,
??count(DISTINCT?id)?AS?part_daily_full_uv
FROM
??view_table
GROUP?BY
??mod(id,?1024),
???--?根據(jù)注入時(shí)間劃分天級(jí)別窗口
??TUMBLE(ingest_time,?INTERVAL?'1'?DAY)
---------------?偽代碼?---------------
我目前所屬的時(shí)區(qū)是東八區(qū)(北京時(shí)間),通過(guò)上述 sql,設(shè)置注入時(shí)間,并對(duì)注入時(shí)間加上 8 小時(shí)的偏移量進(jìn)行天級(jí)別窗口的劃分,就可以對(duì)此問(wèn)題進(jìn)行解決(也可以在 create table 時(shí),在 schema 中根據(jù)計(jì)算列添加對(duì)應(yīng)的注入時(shí)間戳進(jìn)行解決)。如果你在 sql 層面有更好的解決方案,歡迎討論~
?Notes:
- 「東 n 區(qū)的解決方案就是時(shí)間戳 +n * 3600 秒的偏移量,西 n 區(qū)的解決方案就是時(shí)間戳 -n * 3600 秒的偏移量」
- 「DataStream API 存在相同的天級(jí)別窗口時(shí)區(qū)問(wèn)題」
這里提出一個(gè)問(wèn)題,為什么東八區(qū)是需要在時(shí)間戳上加 8 小時(shí)偏移量進(jìn)行天級(jí)別窗口計(jì)算,而不是減 8 小時(shí)或是加上 32(24 + 8) 小時(shí),小伙伴們有詳細(xì)分析過(guò)嘛~
根據(jù)上述問(wèn)題,引出本文的第二大部分,即深度解析時(shí)區(qū)偏移量問(wèn)題,這部分可以作為所有時(shí)區(qū)問(wèn)題的分析思路。
4.為什么東八區(qū)是加 8 小時(shí)?
時(shí)間和時(shí)區(qū)基本概念
「時(shí)區(qū)[3]」:由于世界各國(guó)家與地區(qū)經(jīng)度不同,地方時(shí)也有所不同,因此會(huì)劃分為不同的時(shí)區(qū)。
「Unix 時(shí)間戳(Unix timestamp)[4]」:Unix 時(shí)間戳(Unix timestamp),或稱 Unix 時(shí)間(Unix time)、POSIX 時(shí)間(POSIX time),是一種時(shí)間表示方式,定義為從格林威治時(shí)間 1970 年 01 月 01 日 00 時(shí) 00 分 00 秒(UTC/GMT的午夜)起至現(xiàn)在的總秒數(shù)。Unix 時(shí)間戳不僅被使用在 Unix 系統(tǒng)、類 Unix 系統(tǒng)中,也在許多其他操作系統(tǒng)中被廣泛采用。
「GMT」:Greenwich Mean Time 格林威治標(biāo)準(zhǔn)時(shí)間。這是以英國(guó)格林威治天文臺(tái)觀測(cè)結(jié)果得出的時(shí)間,這是英國(guó)格林威治當(dāng)?shù)貢r(shí)間,這個(gè)地方的當(dāng)?shù)貢r(shí)間過(guò)去被當(dāng)成世界標(biāo)準(zhǔn)的時(shí)間。
「UT」:Universal Time 世界時(shí)。根據(jù)原子鐘計(jì)算出來(lái)的時(shí)間。
「UTC」:Coordinated Universal Time 協(xié)調(diào)世界時(shí)。因?yàn)榈厍蜃赞D(zhuǎn)越來(lái)越慢,每年都會(huì)比前一年多出零點(diǎn)幾秒,每隔幾年協(xié)調(diào)世界時(shí)組織都會(huì)給世界時(shí) +1 秒,讓基于原子鐘的世界時(shí)和基于天文學(xué)(人類感知)的格林威治標(biāo)準(zhǔn)時(shí)間相差不至于太大。并將得到的時(shí)間稱為 UTC,這是現(xiàn)在使用的世界標(biāo)準(zhǔn)時(shí)間。協(xié)調(diào)世界時(shí)不與任何地區(qū)位置相關(guān),也不代表此刻某地的時(shí)間,所以在說(shuō)明某地時(shí)間時(shí)要加上時(shí)區(qū)也就是說(shuō) GMT 并不等于 UTC,而是等于 UTC + 0,只是格林威治剛好在 0 時(shí)區(qū)上。
白話時(shí)間和時(shí)區(qū)
當(dāng)時(shí)看完這一系列的時(shí)間以及時(shí)區(qū)說(shuō)明之后我大腦其實(shí)是一片空白。...ojbk...,我用自己現(xiàn)在的一些理解,嘗試將上述所有涉及到時(shí)間的概念解釋一下。
- 「GMT」:格林威治標(biāo)準(zhǔn)時(shí)間。
- 「UTC」:基于原子鐘協(xié)調(diào)之后的世界標(biāo)準(zhǔn)時(shí)間。可以認(rèn)為 UTC 時(shí)間和格林威治標(biāo)準(zhǔn)時(shí)間一致。即 GMT = UTC + 0,其中 0 代表格林威治為 0 時(shí)區(qū)。
- 「時(shí)區(qū)」:逆向思維來(lái)解釋下(只從技術(shù)層面解釋,不從其他復(fù)雜層面解釋),沒(méi)有時(shí)區(qū)劃分代表著全世界都是同一時(shí)區(qū),那么同一時(shí)刻看到的外顯時(shí)間是一樣的。舉個(gè)?:假如全世界都按照格林威治時(shí)間作為統(tǒng)一時(shí)間,在格林威治時(shí)間 0 點(diǎn)時(shí),對(duì)于北京和加拿大的兩個(gè)同學(xué)來(lái)說(shuō),這兩個(gè)同學(xué)感知到的是北京是太陽(yáng)剛剛升起(清晨),加拿大是太陽(yáng)剛剛落下(傍晚)。但是由于沒(méi)有時(shí)區(qū)劃分,這兩個(gè)同學(xué)看到的時(shí)間都是 0 點(diǎn),因此這是不符合人類對(duì)「感知到的時(shí)間」和自己「看到的時(shí)間」的理解的。所以劃分時(shí)區(qū)之后,可以滿足北京(東八區(qū) UTC + 8)同學(xué)看到的時(shí)間是上午 8 點(diǎn),加拿大(西四區(qū) UTC - 4)同學(xué)看到的時(shí)間是下午 8 點(diǎn)。注意時(shí)區(qū)的劃分是和 UTC 綁定的。東八區(qū)即 UTC + 8。
- 「flink 時(shí)間」:flink 使用的時(shí)間基于 java 時(shí)間紀(jì)元(GMT 1970/01/01 00:00:00,UTC + 0 1970/01/01 00:00:00)。
- 「Unix 時(shí)間戳」:世界上任何一個(gè)地方,同時(shí)接收到的數(shù)據(jù)的對(duì)應(yīng)的 Unix 時(shí)間戳都是相同的,類似時(shí)區(qū)中我們舉的不分時(shí)區(qū)的?,全世界同一時(shí)刻的 Unix 時(shí)間戳一致。
- 「Unix 時(shí)間戳為 0」:對(duì)應(yīng)的格林威治時(shí)間:1970-01-01 00:00:00,對(duì)應(yīng)的北京時(shí)間(東八區(qū)):1970-01-01 08:00:00**
概念關(guān)系如圖所示:
為什么東八區(qū)是加 8 小時(shí)?
下述表格只對(duì)一些重要的時(shí)間進(jìn)行了標(biāo)注:
拿第一條數(shù)據(jù)解釋下,其代表在北京時(shí)間 1970/01/01 00:00:00 時(shí),生成的一條數(shù)據(jù)所攜帶的 Unix 時(shí)間戳為 -8 * 3600。
根據(jù)需求和上圖和上述表格內(nèi)容,我們可以得到如下推導(dǎo)過(guò)程:
需求場(chǎng)景是統(tǒng)計(jì)一個(gè)整天的 uv,即天級(jí)別窗口,比如統(tǒng)計(jì)北京時(shí)間 1970/01/01 00:00:00 - 1970/01/02 00:00:00 范圍的數(shù)據(jù)時(shí),這個(gè)日期范圍內(nèi)的數(shù)據(jù)所攜帶的 Unix 時(shí)間戳范圍為 -8 * 3600 到 16 * 3600
對(duì)于 flink 來(lái)說(shuō),默認(rèn)情況下它所能統(tǒng)計(jì)的一個(gè)整天的 Unix 時(shí)間戳的范圍是 0 到 24 * 3600
所以當(dāng)我們想通過(guò) flink 實(shí)現(xiàn)正確統(tǒng)計(jì)北京時(shí)間(1970/01/01 00:00:00 - 1970/01/02 00:00:00)范圍內(nèi)的數(shù)據(jù)時(shí),即統(tǒng)計(jì) Unix 時(shí)間戳為 -8 * 3600 到 16 * 3600 的數(shù)據(jù)時(shí),就需要對(duì)時(shí)間戳做個(gè)映射。
映射方法如下,就是將整體范圍內(nèi)的時(shí)間戳做在時(shí)間軸上做平移映射,就是把 -8 * 3600 映射到 0,16 * 3600 映射到 24 * 3600。相當(dāng)于是對(duì)北京時(shí)間的 Unix 時(shí)間戳整體加 8 * 3600。
最后在產(chǎn)出的時(shí)間戳上把加上的 8 小時(shí)再減掉(因?yàn)橥怙@時(shí)間會(huì)自動(dòng)按照時(shí)區(qū)對(duì) Unix 時(shí)間戳進(jìn)行格式化)。
Notes:
- 「可以加 32 小時(shí)嗎?答案是可以。在東八區(qū),對(duì)于天級(jí)別窗口的劃分,加 8 小時(shí)和加 8 + n * 24(其中 n 為整數(shù))小時(shí)后進(jìn)行的天級(jí)別窗口劃分和計(jì)算的效果是一樣的,flink 都會(huì)將東八區(qū)的整一天內(nèi)的數(shù)據(jù)劃分到一個(gè)天級(jí)別窗口內(nèi)。所以加 32(8 + 24),56(8 + 48),-16(8 - 24)小時(shí)效果都相同,上述例子只是選擇了時(shí)間軸平移最小的距離,即 8 小時(shí)。注意某些系統(tǒng)的 Unix 時(shí)間戳為負(fù)值時(shí)會(huì)出現(xiàn)異常。」
- 「此推理過(guò)程適用于所有遇到時(shí)區(qū)問(wèn)題的場(chǎng)景,如果你也有其他應(yīng)用場(chǎng)景有這個(gè)問(wèn)題,也可以按照上述方式解決」
Appendix
求輸入 Unix 時(shí)間戳對(duì)應(yīng)的東八區(qū)每天 0 點(diǎn)的 Unix 時(shí)間戳。
public?static?final?long?ONE_DAY_MILLS?=?24?*?60?*?60?*?1000L;public?static?long?transform(long?timestamp)?{
????return?timestamp?-?(timestamp?+?8?*?60?*?60?*?1000)?%?ONE_DAY_MILLS;
}
5.總結(jié)
本文首先介紹了直接給出了我們的問(wèn)題 sql 和解決方案。
第二節(jié)從需求場(chǎng)景以及整個(gè)數(shù)據(jù)鏈路的實(shí)現(xiàn)方案出發(fā),解釋了我們?cè)鯓邮褂?flink sql 進(jìn)行了需求實(shí)現(xiàn),并進(jìn)而引出了 sql 中天級(jí)別窗口存在的時(shí)區(qū)問(wèn)題。
第三節(jié)確認(rèn)了天級(jí)別窗口時(shí)區(qū)問(wèn)題原因,引出了 flink 使用了 java 時(shí)間紀(jì)元,并針對(duì)此問(wèn)題給出了引擎層面和 sql 層面的解決方案。也進(jìn)而提出了一個(gè)問(wèn)題:為什么我們的解決方案是加 8 小時(shí)偏移量?
第四節(jié)針對(duì)加 8 小時(shí)偏移量的原因進(jìn)行了分析,并詳細(xì)闡述了時(shí)區(qū),UTC,GMT,Unix 時(shí)間戳之間的關(guān)系。
最后一節(jié)對(duì)本文進(jìn)行了總結(jié)。
如果你有更方便的時(shí)區(qū)偏移量理解方式,歡迎留言~
Reference
[1]java 時(shí)間紀(jì)元: https://cloud.tencent.com/developer/article/1447368
[2]Blink Planner 時(shí)區(qū)設(shè)置: https://www.alibabacloud.com/help/zh/doc-detail/96910.htm?spm=a2c63.p38356.b99.48.28613830DXb6FQ
[3]時(shí)區(qū): https://baike.baidu.com/item/%E6%97%B6%E5%8C%BA/491122?fr=aladdin
[4]Unix 時(shí)間戳(Unix timestamp): https://baike.baidu.com/item/unix%E6%97%B6%E9%97%B4%E6%88%B3/2078227?fr=aladdin
?踩坑記 | Flink 事件時(shí)間語(yǔ)義下數(shù)據(jù)亂序丟數(shù)更多 Flink 實(shí)時(shí)大數(shù)據(jù)分析相關(guān)技術(shù)博文,視頻。后臺(tái)回復(fù)?“flink”?獲取。
點(diǎn)個(gè)贊+在看,少個(gè) bug?? 與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的东八区转为0时区_踩坑记 | Flink 天级别窗口中存在的时区问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: js循环判断有无重复值_JavaScri
- 下一篇: jni jvm 内存泄漏_内存泄漏