iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0

昨天我們學會了用 Flink SQL 構建大寬表,一個 INSERT 語句就能 JOIN 所有相關表,看似完美解決了 OLAP 分析的需求。但在實際的生產環境中,你會發現一個關鍵問題:大寬表一旦 sink 到 JDBC,就無法再進行 streaming read 了

什麼是 Streaming Read?

Streaming Read:數據以「持續流動」的方式被讀取,新數據一到達就立刻被處理。

Streaming Read (流式讀取):
Kafka Topic → Flink → 持續監聽新數據 → 即時處理

JDBC Read (批次讀取):
Database → 讀取現有數據 → 處理結束

為什麼 JDBC 無法 Streaming Read?

-- JDBC 只能這樣讀(批次查詢)
SELECT * FROM order_wide_table WHERE order_date > '2024-01-01';
-- 問題:只能讀到「當下存在」的數據,無法持續監聽新數據

-- Kafka 可以這樣讀(流式消費)
CREATE TABLE order_stream (...) WITH ('connector' = 'kafka', ...);
-- 優勢:新數據一寫入 Kafka 就立刻被 Flink 消費處理

實際影響:一旦數據寫入 JDBC,就失去了「實時響應新數據」的能力,只能定期批次查詢。

其實 Day 22 的架構(大寬表 + OLAP)已經可以在資料庫裡直接 GROUP BY 做各種分析:

-- 在 OLAP 資料庫中直接查詢
SELECT city, DATE_TRUNC('hour', order_date), SUM(payment_amount)
FROM order_wide_table 
GROUP BY city, DATE_TRUNC('hour', order_date);

但如果你想要:

  • 更即時的響應
  • 更複雜的流式計算

那就需要在 Flink 層面進行更多計算。這就引出了現代數據架構的進階設計:Kafka 多層架構


Kafka 多層架構:Bronze、Silver、Gold

現代數據分層理念

企業級數據架構遵循 Medallion Architecture(獎章架構)設計模式:

Modern Data Architecture:

Bronze Layer (Kafka Raw Data):
┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│     orders   │  │    customers │  │    payments  │  │   deliveries │
└──────────────┘  └──────────────┘  └──────────────┘  └──────────────┘
        │                 │                     │             │
        └─────────────────┼─────────────────────┼─────────────┘
                          │                     │
                          ▼                     ▼
Silver Layer (Wide Table):
                ┌─────────────────────────────────────────────┐
                │           Kafka: order_wide_table           │
                │              (Enriched Data)                │
                └─────────────────────────────────────────────┘
                                      │
                                      │ Multiple Consumers
                                      ▼
Gold Layer (Aggregated Data):
┌─────────────────┐  ┌─────────────────┐  ┌──────────────────┐  ┌────────────┐
│ JDBC: city_sales│  │Kafka:hourly_topn│  │JDBC:payment_stats│  │Kafka:alerts│
└─────────────────┘  └─────────────────┘  └──────────────────┘  └────────────┘

各層職責劃分

Bronze Layer(銅牌層)

  • 職責:原始數據存儲,CDC 直接寫入
  • 特點:數據不經任何處理,保持最原始狀態
  • 用途:數據來源追溯、重新處理的起點

Silver Layer(銀牌層)

  • 職責:清洗、整合後的業務實體(大寬表)
  • 特點:經過 JOIN、清洗的結構化數據
  • 關鍵存在 Kafka 中,保持 streaming 特性

Gold Layer(金牌層)

  • 職責:針對特定用途的聚合數據
  • 特點:預計算的指標、排行榜、統計結果
  • 靈活性:可以是 Kafka(實時)或 JDBC(分析)

實戰:多層架構的 Flink SQL 實現

重要提醒
本文所有程式碼皆為教學與概念演示用的 Flink SQL,可以幫助你理解並解決實際場景的問題,但這裡的程式碼需要根據實際環境調整,主要目的是用來講解多層架構設計的思路與核心概念。

Step 1: Bronze → Silver(大寬表)

Silver 層的關鍵設計

-- 關鍵:Silver 層寫到 Kafka,不是 JDBC!
CREATE TABLE order_wide_table_silver (
    ...
) WITH (
    'connector' = 'kafka',
    'topic' = 'order_wide_table_silver',
    'value.format' = 'json'
);

-- 大寬表 JOIN(昨天學會的)
INSERT INTO order_wide_table_silver
SELECT ...
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
LEFT JOIN payments p ON o.order_id = p.order_id
LEFT JOIN deliveries d ON o.order_id = d.order_id;

Step 2: Silver → Gold(進階聚合)

現在可以對 Silver 層進行 streaming 聚合

-- Gold 層 1: 城市銷售排行榜
CREATE TABLE city_sales_ranking (
    window_start TIMESTAMP(3),
    window_end TIMESTAMP(3),
    city STRING,
    total_sales DECIMAL(10,2),
    ranking BIGINT
) WITH (
    'connector' = 'kafka',
    'topic' = 'city_sales_ranking'
);

INSERT INTO city_sales_ranking
SELECT
    window_start,
    window_end,
    city,
    total_sales,
    ROW_NUMBER() OVER (PARTITION BY window_start ORDER BY total_sales DESC) as ranking
FROM (
    SELECT
        TUMBLE_START(order_date, INTERVAL '1' HOUR) as window_start,
        TUMBLE_END(order_date, INTERVAL '1' HOUR) as window_end,
        customer_city as city,
        SUM(payment_amount) as total_sales
    FROM order_wide_table_silver  -- 從 Silver 讀取!
    GROUP BY 
        TUMBLE(order_date, INTERVAL '1' HOUR),
        customer_city
)
WHERE ranking <= 10;  -- TOP 10
-- Gold 層 2: 付款方式統計
CREATE TABLE payment_method_stats (
    window_start TIMESTAMP(3),
    payment_method STRING,
    success_count BIGINT,
    total_count BIGINT,
    success_rate DECIMAL(5,2)
) WITH (
    'connector' = 'jdbc',
    'url' = 'jdbc:mysql://localhost:3306/analytics'
);

INSERT INTO payment_method_stats
SELECT
    TUMBLE_START(paid_at, INTERVAL '5' MINUTE) as window_start,
    payment_method,
    SUM(CASE WHEN payment_status = 'success' THEN 1 ELSE 0 END) as success_count,
    COUNT(*) as total_count,
    CAST(SUM(CASE WHEN payment_status = 'success' THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS DECIMAL(5,2)) as success_rate
FROM order_wide_table_silver  -- 同一個 Silver 源可以多次使用
GROUP BY 
    TUMBLE(paid_at, INTERVAL '5' MINUTE),
    payment_method;

核心優勢:為什麼要多層設計?

1. 靈活性:統一標準化,多次使用

單層架構 (Bronze → Gold)

  • 原始數據直接轉換為最終格式
  • 每個用途需要重新從 Bronze 處理
  • 清理邏輯分散在各個 Gold 層

多層架構 (Bronze → Silver → Gold)

  • Silver 作為統一的「標準化」層
  • 統一的清理和標準化邏輯集中在 Silver
  • Gold 層只需專注於特定業務邏輯

2. 性能:分段優化

  • Silver 層:複雜 JOIN,一次處理完成
  • Gold 層:簡單聚合,基於已整合的數據

3. 可擴展性:業務需求快速響應

新的分析需求來了?不用重新 JOIN 多張表,直接從 Silver 層讀取即可:

-- 新需求:產品銷量分析
INSERT INTO product_sales_analysis
SELECT 
    product_id,
    SUM(quantity) as total_sold,
    AVG(unit_price) as avg_price
FROM order_wide_table_silver  -- 直接使用現有 Silver 層
GROUP BY product_id;

從理想到現實:維運層面的嚴峻挑戰

多層 Kafka 架構提供了強大的技術能力 - 從 Day 22 的基礎大寬表到 Day 23 的進階聚合,我們解決了「JDBC 無法 streaming read」的關鍵問題。Silver 層存在 Kafka 中保持流動性,讓同一份大寬表可以支撐多種實時分析需求。

實務上的選擇:目前業界的 Realtime Data 應用主要有兩種路線 - 大寬表 + OLAP 計算(Day 22 的方式)或是全部 Flink 計算(Day 23 的多層架構)。技術實現上,無論是 Flink SQL 的 JOIN、GROUP BY、還是窗口函數,程式碼都不是什麼難事。

但當這些美好的架構真正運行在生產環境時,你會遇到一些讓人頭痛的現實問題。

歷史資料重放:昂貴的時光倒流

場景:業務邏輯變更需要重跑歷史數據

想像你已經穩定運行了半年的大寬表流處理,突然業務方說:「我們發現訂單金額計算有bug,VIP客戶的折扣邏輯錯了,需要修正並重新計算過去 3 個月的所有訂單數據。」

為什麼需要全量重跑?Stream JOIN 的狀態依賴

Stream JOIN 需要維護狀態。這是 Streaming 與 Batch 處理的根本差異:

Batch vs Streaming 重跑對比:

Batch 處理:
SELECT * FROM orders o JOIN payments p ON o.order_id = p.order_id
WHERE order_date >= '2024-01-01'
→ 一次查詢,立即得到所有結果

Streaming 處理:
需要按時間順序逐一重放每個事件:
Event 1: Order(123)  → 建立狀態 {123: waiting}
Event 2: Payment(123) → 匹配狀態,輸出結果
Event 3: Order(456)  → 建立狀態 {456: waiting}
...
(必須按原始順序,一筆一筆重建狀態)
挑戰:
• Kafka retention:歷史數據可能已經過期刪除 -> 需要想辦法重新 produce 歷史數據
• 狀態重建:需要從零開始重建所有 JOIN 狀態
• 資源消耗:重跑歷史數據會消耗大量計算資源

Issue 排查:在流動的數據中尋找問題根源

場景:Silver 層大寬表出現異常數據

客戶反映某筆訂單的金額計算有誤,但這筆數據已經經過了複雜的多表 JOIN:

排查困難:
• 流式數據:Kafka 中的數據無法直接 SQL 查詢
• 時間窗口:需要確定問題發生的確切時間範圍
• 多數據源:錯誤可能來自 orders、customers、payments、deliveries 任一環節
• 狀態追蹤:JOIN 過程中的中間狀態難以重現

實際痛點

-- 傳統資料庫可以這樣查
SELECT * FROM order_wide_table WHERE order_id = '12345';

-- 但 Kafka 中的流式數據無法直接查詢
-- 你需要:
-- 1. 檢查 Kafka Consumer offset
-- 2. 手動追蹤數據在各個 Topic 中的流動

維運工程師的真實感受

  • 「數據有問題,但我不知道是哪個環節出錯」
  • 「想查歷史數據,但 Kafka 已經 rotation 掉了」

常見的解決方案與權衡

長期備份策略

一個常見的解決方案是將中間表(如 Silver 層的大寬表)同時備份到可以長期保存的存儲系統:

架構升級:
Kafka Silver Layer → ┌─ 下游 Gold Layer (Kafka/JDBC)
                     └─ 長期存儲 (S3/HDFS/Data Warehouse)
                        ↓
                    可直接 SQL 查詢歷史數據

優勢

  • 歷史數據查詢:可以直接 SQL 查詢任意時間點的數據
  • 問題追溯:異常數據可以追溯到具體的時間和來源

代價

  • 系統複雜度上升:需要額外維護存儲組件和同步機制
  • 存儲成本增加:相同數據需要雙重存儲
  • 維運負擔:多了一個需要監控和維護的存儲系統

這種方案體現了實務上的經典權衡:用系統複雜度換取維運便利性。對於大型企業來說,這個代價往往是值得的。

這些維運挑戰往往比技術實現更讓人頭疼,也是企業級 Flink 部署必須面對的現實問題。

總結

今天我們從多層 Kafka 架構的設計開始,深入探討了企業級流處理的現實挑戰。多層架構解決了「JDBC 無法 streaming read」的問題,讓 Silver 層保持在 Kafka 中的流動性,支撐多種實時分析需求。

但更重要的是,我們揭露了生產環境中的嚴峻現實:

  • 歷史資料重放的昂貴成本
  • Issue 排查在流式數據中的困難

狀態管理:Streaming 領域的核心議題

從這一系列的學習中可以看出,狀態是 Streaming 領域一個極其重要的概念。無論是窗口聚合或者是 JOIN 處理,所有這些問題的核心都指向同一個關鍵:如何有效管理和優化流處理中的狀態

我們在「歷史資料重放」部分看到了問題,但還沒有深入討論解決方案。事實上,狀態管理如此重要和複雜,值得獨立一個完整章節來深入討論

Day 24 預告:Flink 大狀態管理與維運挑戰

明天我們將專門聚焦於 Flink 的狀態管理難題:

大狀態帶來的挑戰

  • Checkpoint 越來越慢:狀態達到 TB 級別時的性能瓶頸
  • Job 復原緩慢:重啟時重新載入大量狀態的痛苦
  • 記憶體壓力:大狀態導致的 GC 問題

技術解決方案

  • MiniBatch + Interval JOIN + Lookup JOIN:減少狀態使用的架構優化
  • State TTL + RocksDB 調優:狀態生命週期與存儲優化
  • 硬體策略:記憶體配置與磁碟 IO 優化

從問題分析到解決方案,讓我們徹底搞懂 Flink 狀態管理的方方面面!


上一篇
【知其然,更知其所以然】Day 22: 大寬表設計與 OLAP 集成
下一篇
【知其然,更知其所以然】Day 24: Flink 大狀態管理與維運挑戰
系列文
「知其然,更知其所以然:什麼是 Real-time (Streaming) Pipeline?從造輪子到 Flink 與 RisingWave」26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言