使用 Alluxio 最佳化 EMR 上 Flink Join

亞馬遜雲開發者發表於2023-04-04

業務背景&痛點

  • 流式處理的業務場景,經常會遇到實時訊息資料需要與歷史存量資料關聯查詢或者聚合,比如電商常見的訂單場景,訂單表做為實時事實表,是典型的流式訊息資料,通常會在 kafka 中,而客戶資訊,商品 SKU 表是維度表,通常存在業務資料庫或者數倉中,是典型的離線資料。實時訂單資料在實時處理時通常需要事實表與維度表 join 做 reference 補全,以便拿到訂單詳情並實時統計當天或截至當天的所有訂單的商品分佈詳情。
亞馬遜雲科技開發者社群為開發者們提供全球的開發技術資源。這裡有技術文件、開發案例、技術專欄、培訓影片、活動與競賽等。幫助中國開發者對接世界最前沿技術,觀點,和專案,並將中國優秀開發者或技術推薦給全球雲社群。如果你還沒有關注/收藏,看到這裡請一定不要匆匆劃過,點這裡讓它成為你的技術寶庫!
  • 流式計算通常採用 Flink 做為資料處理平臺,上文中提到的實時和離線資料join 的場景,Flink 提供了 Hive/ jdbc/Hudi/ filesystem 各種 connector 實現與離線資料的提取和讀寫,這樣一來在 Flink 應用程式中,即可使用 Table,Sql API 來 join 關聯流態表和離線表資料,實現聚合計算等操作

使用 Flink Sql 離線表 Join 流態表的常規 lookup join,是透過 Flink hive sql connector 或者 filesystem connector,對離線 hive 庫表或者 S3上離線資料建 Flink Table,然後對 kafka 訊息流中的資料建流態表,然後直接做量表做 join 操作

該方式架構如下圖所示:

該方式主要面臨的問題是:

  • lookup 維度表資料只會在首次拉起 Flink 應用的時候,儲存在 task manager state 中,後續持續查詢或者開窗聚合等操作時,是不會再次拉取維度表資料,業務需要定期重啟 Flink 應用,或者重新整理維度表資料到臨時表,以便 join 聚合時和最新的維度資料關聯:
  • 每次需要重新全量拉取維度表資料,存在冷啟動問題,且維度表資料量大的時候(如上千萬註冊使用者資訊表,上萬的商品 SKU 屬性欄位),造成很大 IO 開銷,存在效能瓶頸
  • Flink 的 checkpoint 機制在持續查詢或者開窗聚合時,需要儲存 state 狀態及處理資料到檢查點快照中,造成 state 快照資料膨脹

    解決方案思路

    基於以上業務難點,本文提出一種解決方案思路,即透過 Alluxio 快取層,將 hive 維度表資料自動載入至 Alluxio UFS 快取中,同時透過 Flink 時態表 join,把維度表資料做成持續變化表上某一時刻的檢視

同時使用 Flink 的 Temporal table function 表函式,傳遞一個時間引數,返回 Temporal table 這一指定時刻的檢視,這樣實時動態表主表與這個 Temporal table 表關聯的時候,可以關聯到某一個版本(歷史上某一個時刻)的維度資料

最佳化後的整體架構如下圖所示:

方案實施落地Detail

本文以 Kafka 中使用者行為日誌資料做為實時流態的事實表資料,hive 上使用者資訊資料做為離線維度表資料,採用 Alluxio+Flink temproal 的 demo,來驗證其 flink join 最佳化的解決方案

實時事實表

本例項中我們使用 json-data-generator 開源元件模擬的使用者行為 json 資料,實時寫入 kafka 中,透過 Flink kafka connector 轉換為持續查詢的 Flink 流態表,從而做為實時 join 的時候的 Fact 事實表資料

使用者行為 json 模擬資料如下格式:

[{          "timestamp": "nowTimestamp()",
                    "system": "BADGE",
                    "actor": "Agnew",
                    "action": "EXIT",
                    "objects": ["Building 1"],
                    "location": "45.5,44.3",
                    "message": "Exited Building 1"
                }]

包含使用者行為的業務時間,登入系統,使用者署名,行為 activity 動作,操作涉及物件,位置資訊,及相關文字訊息欄位。我們在

Flink Sql 中建選擇主要欄位建事實表如下

CREATE TABLE logevent_source (`timestamp`  string, 
`system` string,
 actor STRING,
 action STRING
) WITH (
'connector' = 'kafka',
'topic' = 'logevent',
'properties.bootstrap.servers' = 'b-6.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092,b-5.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092,b-1.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092 (http://b-6.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092%2Cb-5.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092%2Cb-1.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092/)',
'properties.group.id' = 'testGroup6',
'scan.startup.mode'='latest-offset',
'format' = 'json'
);

Alluxio 快取維度表

Alluxio 是大資料技術堆疊的分散式快取,它提供了一個統一的 UFS 檔案系統可以對接底層 S3,hdfs 資料,在讀寫 Alluxio UFS 的時候,可以針對 S3,HDFS 分散式儲存層實現 warm up,顯著提升吞吐量和減少網路開銷,且與上層計算引擎如 Hive,spark,Trino 都有深度的整合,很適合做為離線維度資料的快取加速器

Amazon EMR 對 Alluxio 提供了良好的整合,可以透過 boostrap 啟動指令碼方式,在 EMR 建立時自動部署 Alluxio 元件並啟動 Alluxio master、worker 程式,詳細 EMR 安裝和部署 Alluxio 步驟可以參考另一篇文章 Alluxio EMR 整合實踐

在整合 Alluxio 的 Amazon EMR 叢集中,使用 Alluxio 中建立 hive 離線維表資料的快取表方法如下:

hive-env.sh中設定設定client jar包:
$ export HIVE_AUX_JARS_PATH=/<PATH_TO_ALLUXIO>/client/alluxio-2.2.0-client.jar:${HIVE_AU

確保安裝部署alluxio的EMR叢集上ufs已配置,並且表或者db路徑已建立
alluxio fs mkdir alluxio://ip-xxx-xxx-xxx-xxx.ap-southeast-1.compute.internal:19998/s3/customer
alluxio fs chown hadoop:hadoop alluxio://ip-xxx-xxx-xxx-xxx.ap-southeast-1.compute.internal:19998/s3/customer

在AWS EMR叢集上,建立hive表路徑指向alluxio namespace uri:
!connect jdbc:hive2://xxx.xxx.xxx.xxx:10000/default;
hive> CREATE TABLE customer(
    c_customer_sk             bigint,
    c_customer_id             string,
    c_current_cdemo_sk        bigint,
    c_current_hdemo_sk        bigint,
    c_current_addr_sk         bigint,
    c_first_shipto_date_sk    bigint,
    c_first_sales_date_sk     bigint,
    c_salutation              string,
    c_first_name              string,
    c_last_name               string,
    c_preferred_cust_flag     string,
    c_birth_day               int,
    c_birth_month             int,
    c_birth_year              int,
    c_birth_country           string,
    c_login                   string,
    c_email_address           string
)
    ROW FORMAT DELIMITED
    FIELDS TERMINATED BY '|'
    STORED AS TEXTFILE
    LOCATION 'alluxio://ip-xxx-xxx-xxx-xxx.ap-southeast-1.compute.internal:19998/s3/customer';
OK
Time taken: 3.485 seconds

如上所示,該 Alluxio 表 location 指向的路徑即為 hive 維度表所在 S3路徑,因此對 Customer 使用者維度資訊表的寫入操作會自動同步到 alluxio 快取中。

建立好 Alluxio hive 離線維度表後,在 flink sql中,可以透過 hive 的 catalog,連線到 hive 後設資料,即可以檢視到 alluxio 快取表的詳細資訊:

CREATE CATALOG hiveCatalog WITH (  'type' = 'hive',
    'default-database' = 'default',
    'hive-conf-dir' = '/etc/hive/conf/',
    'hive-version' = '3.1.2',
    'hadoop-conf-dir'='/etc/hadoop/conf/'
);
-- set the HiveCatalog as the current catalog of the session
USE CATALOG hiveCatalog;
show create table customer;
create external table customer(
    c_customer_sk             bigint,
    c_customer_id             string,
    c_current_cdemo_sk        bigint,
    c_current_hdemo_sk        bigint,
    c_current_addr_sk         bigint,
    c_first_shipto_date_sk    bigint,
    c_first_sales_date_sk     bigint,
    c_salutation              string,
    c_first_name              string,
    c_last_name               string,
    c_preferred_cust_flag     string,
    c_birth_day               int,
    c_birth_month             int,
    c_birth_year              int,
    c_birth_country           string,
    c_login                   string,
    c_email_address           string
) 
row format delimited fields terminated by '|'
location 'alluxio://ip-xxx-xxx-xxx-xxx.ap-southeast-1.compute.internal:19998/s3/30/customer' 
TBLPROPERTIES (
  'streaming-source.enable' = 'false',  
  'lookup.join.cache.ttl' = '12 h'
)

如上圖所示,可以看到該維度表 location 路徑是 alluxio 快取 ufs 路徑的 uri,業務程式讀寫該維度表時,alluxio 會自動更新快取中的 customer 維度表資料,並非同步寫入到 alluxio的backend storage 的 S3表路徑,實現資料湖的表資料同步更新。

Flink Temporal 時態表 join

Flink 時態表(Temporal table)也是動態表的一種,時態表的每條記錄都會有一個或多個時間欄位相關聯,當我們事實表 join 維度表的時候,通常需要獲取實時的維度表資料做 lookup,所以通常需要在事實表 create table 或者 join 時,透過 proctime()函式指定事實表的時間欄位,同時在 join 時,透過 FOR SYSTEM_TIME AS OF 語法,指定維度表 lookup 時對應的事實表時間版本的資料

在本 Demo 示例中,客戶資訊在 hive 離線表作為一個變化的維度表的角色,客戶行為在 kafka 中作為事實表的角色,因此在 flink kafka source table 中,透過 proctime()指定時間欄位,然後在 flink hive table 做 join 時,使用 FOR SYSTEM_TIME AS OF 指定 lookup 的 kafka source table 的時間欄位,從而實現 Flink temporal 時態表 join 業務處理

如下所示,Flink Sql 中透過 Kafka connector 建立使用者行為的事實表,其中 ts 欄位即為時態表 join 時的時間戳:

CREATE TABLE logevent_source (`timestamp`  string, 
`system` string,
 actor STRING,
 action STRING,
 ts as PROCTIME()
) WITH (
'connector' = 'kafka',
'topic' = 'logevent',
'properties.bootstrap.servers' = 'b-6.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092,b-5.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092,b-1.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092 (http://b-6.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092%2Cb-5.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092%2Cb-1.msk06.dr04w4.c3.kafka.ap-southeast-1.amazonaws.com:9092/)',
'properties.group.id' = 'testGroup-01',
'scan.startup.mode'='latest-offset',
'format' = 'json'
);

Flink 離線維度表與流式實時表具體 join 方法如下:

select a.`timestamp`,a.`system`,a.actor,a.action,b.c_login from 
      (select *, proctime() as proctime from user_logevent_source) as a 
left join customer  FOR SYSTEM_TIME AS OF a.proctime as b on a.actor=b.c_last_name;

如上程式碼示例,在事實表 logevent_source join lookup 維度表時,透過 proctime 函式獲取到維度表的瞬時最新的版本資料,保障 join 時的一致性和實時性

同時,該維度表資料已經在 alluxio cache,因此讀取時效能遠高於離線讀取 s3上的表資料

透過 hive 切換 S3和 alluxio 路徑的 customer 資訊 維度表,對比測試 flink join 可以看出 alluxio 快取後效能明顯優勢

透過 alter table 方便切換本地和 cache 的 location路徑:

alter table customer set location "s3://xxxxxx/data/s3/30/customer";
alter table customer  set location "alluxio://ip-xxx-xxx-xxx-xxx.ap-southeast-1.compute.internal:19998/s3/30/customer";

選取某一 split 資料分片的 TaskManager 日誌:

  • cache 前(S3路徑讀取): 5s 載入
2022-06-29 02:54:34,791 INFO  com.amazon.ws.emr.hadoop.fs.s3n.S3NativeFileSystem           [] - Opening 's3://salunchbucket/data/s3/30/customer/data-m-00029' for reading
2022-06-29 02:54:39,971 INFO  org.apache.flink.table.filesystem.FileSystemLookupFunction   [] - Loaded 433000 row(s) into lookup join cache
  • cache 後(alluxio 讀取): 2s 載入
2022-06-29 03:25:14,476 INFO  com.amazon.ws.emr.hadoop.fs.s3n.S3NativeFileSystem           [] - Opening 's3://salunchbucket/data/s3/30/customer/data-m-00029' for reading
2022-06-29 03:25:16,397 INFO  org.apache.flink.table.filesystem.FileSystemLookupFunction   [] - Loaded 433000 row(s) into lookup join cache

在 JobManager 上檢視 Timeline,對比 alluxio 和 s3路徑下 job 的執行時間可以看到更加清楚

可以看到, 單個 task 查詢提升1倍以上,整體 job 效能提升更加明顯

其他需要考慮的問題

持續 Join 每次都需要拉取維度資料做 join,Flink 的 checkpoint state 是否一直膨脹導致 TM 的 RockDB 撐爆或者記憶體溢位?

state 自帶有 ttl 機制,可以設定 ttl 過期策略,觸發 Flink 清理過期 state 資料,Flink Sql 可以透過 Hint 方式設定

insert into logevent_sink
select a.`timestamp`,a.`system`,a.actor,a.action,b.c_login from 
(select *, proctime() as proctime from logevent_source) as a 
  left join 
customer/*+ OPTIONS('lookup.join.cache.ttl' = '5 min')*/  FOR SYSTEM_TIME AS OF a.proctime as b 
on a.actor=b.c_last_name;

Flink Table/Streaming API 類似:

StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.days(7))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .cleanupInRocksdbCompactFilter() 
    .build();
ValueStateDescriptor<Long> lastUserLogin = 
    new ValueStateDescriptor<>("lastUserLogin", Long.class);
lastUserLogin.enableTimeToLive(ttlConfig);
StreamTableEnvironment.getConfig().setIdleStateRetentionTime(min, max);

設定後重新啟動 lookup join,從 Flink TM 日誌中可以看到,ttl 到期後,會觸發清理並重新拉取 hive 維表資料:

2022-06-29 04:17:09,161 INFO  org.apache.flink.table.filesystem.FileSystemLookupFunction   
[] - Lookup join cache has expired after 5 minute(s), reloading

此外,可以透過配置 flink state retain,減少 checkpoint 時候快照數量,從而減少快照時候 state 的佔用空間

Flink job中配置:
-D state.checkpoints.num-retained=5

設定後,可以看到 s3 checkpoint 路徑上,Flink Job 會自動清理歷史快照,只保留最近的5次快照資料,從而確保 checkpoint 快照資料不會堆積

[hadoop@ip-172-31-41-131 ~]$ aws s3 ls s3://salunchbucket/data/checkpoints/7b9f2f9becbf3c879cd1e5f38c6239f8/
                           PRE chk-3/
                           PRE chk-4/
                           PRE chk-5/
                           PRE chk-6/
                           PRE chk-7/

附錄

Alluxio整體架構

Alluxio on EMR 快速部署

在 Amazon EMR 中利用 Alluxio 的分層儲存架構

EMR Alluxio整合detail

Flink Temporal Join 詳細

本篇作者

唐清原

Amazon 資料分析解決方案架構師,負責 Amazon Data Analytic 服務方案架構設計以及效能最佳化,遷移,治理等 Deep Dive 支援。10+資料領域研發及架構設計經驗,歷任 Oracle 高階諮詢顧問,咪咕文化資料集市高階架構師,澳新銀行資料分析領域架構師職務。在大資料,資料湖,智慧湖倉,及相關推薦系統 /MLOps 平臺等專案有豐富實戰經驗

陳昊

Amazon 合作伙伴解決方案架構師,有將近 20 年的 IT 從業經驗,在企業應用開發、架構設計及建設方面具有豐富的實踐經驗。目前主要負責 Amazon (中國)合作伙伴的方案架構諮詢和設計工作,致力於 Amazon 雲服務在國內的應用推廣以及幫助合作伙伴構建更高效的 Amazon 雲服務解決方案。
文章來源:https://dev.amazoncloud.cn/column/article/6309af45d4155422a46...

相關文章