iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Build on AWS

AWS架構師的自我修養:30天雲端系統思維實戰指南系列 第 11

Day 10-1 | 快取策略領域驅動哲學:空間、時間與監控的權衡藝術(一) - 快取的三重存在論:本地、雲端、數據端

  • 分享至 

  • xImage
  •  

經過前面九天的建模與分析,我們已經確定了系統的本質、邊界和交互模式。但現在面臨一個更實際的問題:如何在真實世界的約束下,讓這些精心設計的領域概念快速響應用戶需求?

當投資交易系統需要在毫秒內完成風險計算、家庭財務系統要支撐多人同時操作、健康監控要處理海量 IoT 數據時,純粹的業務邏輯計算已經無法滿足性能要求。

這就是快取設計的本體論意義:在保持領域純粹性的同時,為系統注入時間和空間的優化策略。

每個快取決策都會影響用戶體驗、系統成本和數據正確性。所以快取的架構設計在 Domain 的驅動開發中無疑也是重中之重的議題。

我們在每次的需求確認中,需要且必須追問的是

這個業務場景對延遲和數據不一致的容忍度是多少?我們願意為此付出多少開發和維運成本?

我們接下來將逐步一同討論在快取中我們最常遇見的情境與議題。這裡簡單列下主題:

  • 快取的三重存在論:本地、雲端、數據端
  • 資料一致性的三個層次:強一致、最終一致、因果一致
  • 資安暴露風險與效能的辯證關係
  • 快取失效策略:時間的哲學
  • 成本建模:ROI 驅動的快取決策
  • 快取監控:可觀測性的三個維度

記住這個框架:熱度決定位置容忍度決定模型耦合度決定實現。當我們面對任何一個需要快取的場景時,先用這三個原則去剖析它,技術選型自然會水到渠成。

快取的三重存在論:本地、雲端、數據端

在什麼時機更新快取?如何在成本控制與系統性能之間找到最佳平衡點?如何處理快取帶來的資料一致性問題?

快取不只是技術優化,而是對數據存在狀態的深層思考。所有數據在不同的位置、不同的時間,都有其特定的存在意義和價值。

快取(Cache)的本質,就是在我們身邊放一個書架。

這個書架可以放在我們的書桌上(本地端快取)、樓下的閱覽室(雲端快取),甚至是圖書館門口的快速取書櫃(資料庫快取)。

但問題來了:

  1. 空間有限:我們的書架放不下所有書。我們放了 A 書,就可能得把 B 書拿掉。這就是 替換策略(Eviction Policy),比如 LRU(把最久沒看的書丟掉)。

  2. 資訊會過期:圖書館的書更新了,我們書架上的還是舊版。這就是 一致性(Consistency) 問題。

  3. 管理成本:我們得花心力去整理書架,決定哪些書要放、哪些要更新。這就是 快取策略(Caching Strategy) 的成本。

所以,快取設計的全部技術,都是圍繞著「如何用最小的管理成本,在有限的書架空間上,最大化地存放最有價值且不過時的資訊」這個核心問題展開的。

常見的區分方式有:熱數據 (Hot Data)、溫數據 (Warm Data)、冷數據 (Cold Data)

熱數據 (Hot Data):例如,一個交易系統的即時股價、社群媒體的熱門貼文。

  • 特徵:讀取頻率極高,時效性極短(秒級甚至毫秒級)。
  • 選型依據:速度就是一切。我們需要最快的書架,而且要離我們最近。
  • 技術選型:In-Memory Cache,如 Redis 或 Memcached。它們把數據直接放在記憶體裡,讀取速度是微秒級的,就像我們大腦裡的記憶一樣快。
  • 優劣:
    • 優:極致的速度。
    • 劣:昂貴(記憶體比硬碟貴得多),且數據易失(斷電就沒了,除非有額外持久化機制)。

溫數據 (Warm Data):例如,我們的個人資料頁面、不常變動的產品目錄。

  • 特徵:讀取頻率高,但可以容忍幾分鐘甚至幾小時的延遲。
  • 選型依據:平衡成本與效能。可以放在離用戶近、但又不用自己維護的地方。
  • 技術選型:CDN (Content Delivery Network),如 CloudFront 或 Cloudflare。它們在全球各地都有節點(閱覽室),能把我們的數據快取到離用戶最近的地方。
  • 優劣:
    • 優:大幅降低伺服器負載,提升全球用戶的訪問速度。
    • 劣:主要適用於公開或半公開數據,對於個人化動態內容的快取配置複雜,且快取失效(更新)不夠即時。

冷數據 (Cold Data):例如,幾年前的交易紀錄、不活躍用戶的資料。

  • 特徵:很少讀取,但需要時仍要能取得。
  • 選型依據:成本最低化。直接去圖書館(資料庫)拿就好,甚至可以考慮把這些書放到更便宜的地下書庫(歸檔儲存)。這類數據通常不值得快取。
graph TB
    subgraph "數據時效性矩陣"
        A1[實時價格數據<br/>秒級更新]
        A2[用戶持倉數據<br/>交易觸發更新]
        A3[風險指標數據<br/>分鐘級重算]
        A4[歷史報表數據<br/>日級更新]

        B1[家庭預算狀態<br/>支出觸發更新]
        B2[月度統計數據<br/>天級更新]
        B3[用戶偏好設定<br/>手動更新]
        B4[歷史支出數據<br/>不變數據]

        C1[設備即時數據<br/>分鐘級更新]
        C2[健康趨勢分析<br/>小時級更新]
        C3[告警閾值設定<br/>手動更新]
        C4[醫療歷史記錄<br/>追加型數據]
    end

    subgraph "快取策略映射"
        S1[Write-Through<br/>即寫即快取<br/>強一致性]
        S2[Write-Behind<br/>延遲寫入<br/>最終一致性]
        S3[Cache-Aside<br/>手動管理<br/>因果一致性]
        S4[Read-Through<br/>穿透讀取<br/>可配置一致性]
    end

    subgraph "技術實現"
        T1[Redis Cluster<br/>+ ElastiCache]
        T2[DynamoDB DAX<br/>+ Lambda 異步]
        T3[Application Cache<br/>+ EventBridge]
        T4[CloudFront<br/>+ S3 Origin]
    end

    %% 投資交易系統映射
    A1 --> S1
    A1 --> T1
    A2 --> S2
    A2 --> T2
    A3 --> S3
    A3 --> T1
    A4 --> S4
    A4 --> T4

    %% 家庭財務系統映射
    B1 --> S3
    B1 --> T3
    B2 --> S4
    B2 --> T2
    B3 --> S3
    B3 --> T3
    B4 --> S4
    B4 --> T4

    %% 健康監控系統映射
    C1 --> S1
    C1 --> T1
    C2 --> S2
    C2 --> T2
    C3 --> S3
    C3 --> T3
    C4 --> S4
    C4 --> T4

    %% 策略到技術的關聯
    S1 --> T1
    S2 --> T2
    S3 --> T3
    S4 --> T4

    classDef hotData fill:#ff6b6b,stroke:#333,stroke-width:2px,color:#fff
    classDef warmData fill:#ffd93d,stroke:#333,stroke-width:2px,color:#333
    classDef coldData fill:#6bcf7f,stroke:#333,stroke-width:2px,color:#333
    classDef strategy fill:#4ecdc4,stroke:#333,stroke-width:2px,color:#fff
    classDef tech fill:#a8e6cf,stroke:#333,stroke-width:2px,color:#333

    class A1,C1 hotData
    class A2,A3,B1,B2,C2 warmData
    class A4,B3,B4,C3,C4 coldData
    class S1,S2,S3,S4 strategy
    class T1,T2,T3,T4 tech

結合到快取位置遠近的關係,我們可以整理出常見的幾重關卡: 本地、雲端、數據端

第一重存在:本地端快取 - 最接近用戶意識的數據

哲學定位:本地端快取是數據與用戶認知最直接的接觸點,它回答了「什麼數據應該立即可得」的問題。

投資交易系統的本地端快取設計

// 前端本地快取的領域抽象
class PortfolioLocalCache {
  constructor() {
    this.realtimeData = new Map(); // 實時數據:價格、持倉
    this.calculatedMetrics = new Map(); // 計算結果:風險值、損益
    this.userPreferences = new Map(); // 用戶偏好:介面設定、警告閾值
  }

  // 核心洞察:不同數據的失效策略反映了其業務重要性
  getDataWithStrategy(key, dataType) {
    switch (dataType) {
      case "REAL_TIME_PRICE":
        return this.getOrFetch(key, 100); // 100ms 失效
      case "RISK_CALCULATION":
        return this.getOrFetch(key, 1000); // 1s 失效,允許短暫延遲
      case "USER_PREFERENCES":
        return this.getOrFetch(key, 300000); // 5分鐘失效,穩定性優先
    }
  }
}

本地端快取的設計哲學

  1. 認知負荷最小化:最常用的功能應該零延遲
  2. 離線容錯能力:網路中斷時系統仍可部分運作
  3. 隱私優先策略:敏感數據優先在本地處理

AWS 實現策略

# CloudFront Edge Locations 作為本地端延伸
CloudFrontDistribution:
  PriceClass: PriceClass_All
  CacheBehaviors:
    - PathPattern: "/api/realtime/*"
      CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # Caching Disabled
    - PathPattern: "/api/historical/*"
      CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # Caching Optimized
      TTL: 3600 # 1小時快取,歷史數據相對穩定

第二重存在:雲端快取 - 分散式數據的協調中心

哲學定位:雲端快取是多個服務間數據共享的協調機制,它解決了「如何在分散式環境中維持數據的一致性視圖」的問題。

多層次雲端快取架構的設計

# 雲端快取的層次化設計
class DistributedCacheStrategy:
    def __init__(self):
        self.l1_cache = ElastiCacheRedis(node_type="cache.r6g.large")
        self.l2_cache = ElastiCacheMemcached(node_type="cache.r6g.xlarge")
        self.l3_cache = DynamoDBDAX()  # 數據庫快取層

    async def get_portfolio_risk(self, portfolio_id):
        # L1: 熱數據,毫秒級存取
        risk_data = await self.l1_cache.get(f"risk:{portfolio_id}")
        if risk_data:
            return risk_data

        # L2: 溫數據,支援複雜計算結果
        risk_data = await self.l2_cache.get(f"risk:{portfolio_id}")
        if risk_data:
            await self.l1_cache.set(f"risk:{portfolio_id}", risk_data, ttl=60)
            return risk_data

        # L3: 冷數據,從數據庫快取層獲取
        risk_data = await self.calculate_and_cache_risk(portfolio_id)
        return risk_data

雲端快取的領域映射

graph TD
    A[用戶請求] --> B{L1 Redis}
    B -->|Hit| C[返回結果]
    B -->|Miss| D{L2 Memcached}
    D -->|Hit| E[回填L1] --> C
    D -->|Miss| F{L3 DynamoDB DAX}
    F -->|Hit| G[回填L2] --> E
    F -->|Miss| H[領域服務計算] --> I[多層回填] --> C

成本考量的策略決策

# ElastiCache 成本優化配置
RedisCluster:
  NodeType: cache.r6g.large # 記憶體優化型
  NumCacheNodes: 3
  AutomaticFailoverEnabled: true
  MultiAZEnabled: true
  # 成本分析:$0.2016/小時 * 3 節點 * 24 * 30 = $435.5/月

MemcachedCluster:
  NodeType: cache.r6g.xlarge # 更大容量,更低單位成本
  NumCacheNodes: 2
  # 成本分析:$0.4032/小時 * 2 節點 * 24 * 30 = $580.6/月
# 總快取成本:$1016.1/月
# 效益:減少 RDS 查詢 80%,節省資料庫執行成本 $2000/月
# ROI:(2000 - 1016) / 1016 = 96.8% 正向投資報酬率

第三重存在:數據端快取 - 最接近數據本源的優化

哲學定位:數據端快取是數據在其原生環境中的預處理和優化,它回答了「如何讓數據自身更高效」的問題。

DynamoDB DAX 的領域價值

# DynamoDB DAX 作為數據端快取的抽象
class DomainDataCache:
    def __init__(self):
        self.dax_client = boto3.client('dax',
                                     endpoint_url='daxs://portfolio-cluster.abcdef.dax-clusters.us-east-1.amazonaws.com')
        self.dynamodb = boto3.resource('dynamodb')

    async def get_portfolio_holdings(self, portfolio_id):
        # DAX 自動處理快取邏輯,對應用透明
        response = await self.dax_client.get_item(
            TableName='Portfolios',
            Key={'portfolio_id': {'S': portfolio_id}},
            ConsistentRead=False  # 接受最終一致性換取效能
        )
        return response['Item']

數據端快取的設計原則

  1. 透明性:應用代碼無需感知快取存在
  2. 一致性等級:根據業務需求選擇合適的一致性
  3. 成本效益:計算快取成本 vs 查詢成本的平衡

資料一致性的三個層次:強一致、最終一致、因果一致

一致性模型的領域語義

我們必須去思考我們所面對的業務能容忍多大程度的「數據不一致」?這是快取設計中最具挑戰性的哲學問題。例如,銀行帳戶餘額、交易下單,數據不一致會導致直接的金融損失或法律問題,在這個情境中 正確性壓倒一切。寧可慢,不可錯。這種就要求強一致性。

強一致性、最終一致性與因果一致性

在設計分散式系統時,「一致性」是一個核心議題。特別是在涉及快取策略時,理解不同的一致性模型對於系統的行為和性能有著直接的影響。以下是三種主要的一致性模型及其特點:

1. 強一致性 (Strong Consistency)

概念與比喻:
就像在使用 Google Docs 時,我們打下一個字,我們的同事(理論上)會立即看到這個字。任何後續的操作都是基於這個最新的版本。所有人都看到完全相同的、唯一的「真相」。

特性:

  • 線性化 (Linearizability):一旦寫入操作完成,任何後續的讀取操作(無論來自哪個用戶或節點)都必須返回該寫入的值或更新的值。
  • 原子性 (Atomicity):操作要麼完全成功,要麼完全失敗,不存在中間狀態。
  • 全局順序 (Global Order):系統中的所有操作看起來就像是按照一個唯一的、全局的時間線順序執行的。

優點:

  • 開發心智負擔低:開發者無需處理數據可能過期的複雜情況,邏輯最直觀。
  • 數據絕對正確:對於金融交易、庫存管理、帳戶餘額等零容忍場景,這是唯一選擇。

缺點:

  • 高延遲 (High Latency):系統需要確保所有副本都同步完成數據更新後,才能向客戶端確認寫入成功,這會增加寫入操作的耗時。
  • 低可用性 (Lower Availability):在網路分區(Partition)的情況下,為了保證一致性,系統可能會拒絕服務(寫入或讀取),犧牲了可用性(違反了 CAP 定理中的 A)。
  • 擴展性差:在地理上分散的系統中,跨區域同步的延遲會成為巨大瓶頸。

AWS 應用服務:

  • Amazon RDS (單一主節點):傳統的關聯式資料庫是強一致性的典型代表。所有寫入都發生在主節點上。
  • Amazon DynamoDB (搭配 ConsistentRead=True):當我們進行讀取時,可以明確要求 DynamoDB 執行「強一致性讀取」。這會直接從主分區讀取最新數據,但會消耗更多讀取容量單位(RCU)且延遲較高。
  • Amazon ElastiCache for Redis (搭配交易或鎖):通過使用 Redis 的 MULTI/EXEC 事務或分散式鎖(如 RedLock),可以在特定操作上強制實現原子性和強一致性。

2. 最終一致性 (Eventual Consistency)

概念與比喻:
我們在 Facebook 或 Instagram 上發布了一張照片。我們的朋友可能不會在同一毫秒看到它。美國的朋友可能比台灣的朋友晚幾秒鐘才看到,但系統最終會保證,在一段時間後,所有人都能看到這張照片。

特性:

  • 最終同步:如果沒有新的更新操作,最終所有副本的數據都會達到一致的狀態。
  • 高可用性:即使部分節點或網路出現問題,系統仍然可以接受讀寫操作,優先保證服務不中斷。
  • 無特定順序:不保證不同副本更新的順序。

優點:

  • 低延遲 (Low Latency):寫入操作可以快速在本地副本完成並返回,無需等待所有副本同步,用戶體驗極佳。
  • 高可用性 (High Availability):系統容錯能力強,是為大規模、全球分佈的系統設計的。
  • 高吞吐量:讀寫操作可以分散到多個節點,極大地提高了系統的吞吐能力。

缺點:

  • 開發心智負擔高:開發者必須設計能處理「讀到舊數據」情況的應用邏輯。
  • 數據短暫不一致:在同步完成前的時間窗口內,讀取操作可能拿到過期的數據。

AWS 應用服務:

  • Amazon DynamoDB (預設讀取):DynamoDB 的預設讀取模式就是「最終一致性讀取」,它速度快且成本只有強一致性讀取的一半。
  • Amazon S3:S3 在覆蓋寫(PUT)和刪除(DELETE)操作上提供最終一致性。(注意:S3 現在對新對象的 PUT 操作提供強一致性)。
  • Amazon CloudFront:CDN 是最終一致性的典型範例。當我們更新源站內容後,全球各地的邊緣節點需要時間來同步最新的內容。

3. 因果一致性 (Causal Consistency)

概念與比喻:
這是一致性模型中的一個精妙平衡。想像一個論壇的留言串:

  1. 我們發表了一篇主題文章 A。
  2. 有人接著對文章 A 發表了一條評論 B。

因果一致性保證:任何看到評論 B 的人,必須也能看到文章 A。它不關心其他無關的操作順序,但它嚴格保護有「因果關係」的操作順序。一個用戶不可能看到「回覆」,卻看不到「原文」。

特性:

  • 保留因果順序:如果操作 A 在因果上發生在操作 B 之前(例如,B 是對 A 的回應),那麼系統保證任何進程讀取到 B 時,也必須能讀取到 A。
  • 並行操作無序:對於沒有因果關係的並行操作(例如,兩個用戶同時評論文章 A),它們的順序則不被保證。

優點:

  • 兼顧效能與邏輯正確性:比強一致性有更好的效能和可用性,同時又比最終一致性提供了更強的邏輯保證,避免了許多用戶體驗上的混亂。
  • 符合人類直覺:非常適合對話、評論、協作編輯等場景。

缺點:

  • 實現複雜:需要系統追蹤操作之間的依賴關係(通常使用向量時鐘等技術),比最終一致性更難實現。
  • 仍非強一致:對於無因果關係的數據,仍然可能存在不一致。

AWS 應用服務:

  • Amazon DynamoDB Streams:這是一個非常巧妙的應用。我們可以利用 Streams 來捕捉一個表的所有修改事件(A、B、C...),並確保它們是按順序處理的。這允許在下游系統中重建因果關係。例如,在我們的家庭財務系統範例中,使用 Streams 來處理支出記錄,就能保證按順序更新預算,這就是一種因果一致性的實現。
  • Amazon QLDB (Quantum Ledger Database):QLDB 是一個可驗證的帳本資料庫,它記錄了數據的完整變更歷史。通過查詢這個日誌,可以明確地重建所有操作的因果鏈。

總結對照表

特性 強一致性 (Strong) 因果一致性 (Causal) 最終一致性 (Eventual)
核心保證 讀取最新寫入 讀取操作必先於其因 最終會讀到最新寫入
延遲
可用性
開發複雜度
適用場景 銀行交易、庫存 留言系統、協作文檔 社群動態、點讚數
AWS 範例 RDS, DynamoDB(ConsistentRead) DynamoDB Streams, QLDB S3, DynamoDB(Default), CloudFront

不同的業務場景對數據一致性有不同的容忍度,這直接影響快取策略的設計,接下來我們用我們一直以來的財務情境:投資財務與家庭財務,來討論在不同情境中對於一致性的需求。

投資交易系統的一致性權衡

場景分析:當用戶同時在手機和電腦上操作投資組合時:

class PortfolioConsistencyManager:
    def __init__(self):
        self.strong_consistency_cache = RedisCluster(consistency='strong')
        self.eventual_consistency_cache = CloudFrontCache(ttl=300)

    async def handle_trade_order(self, user_id, order):
        # 交易執行:必須強一致
        async with self.strong_consistency_cache.lock(f"portfolio:{user_id}"):
            current_balance = await self.get_account_balance(user_id)
            if current_balance >= order.amount:
                await self.execute_trade(order)
                await self.invalidate_all_portfolio_caches(user_id)

    async def get_portfolio_summary(self, user_id):
        # 組合總覽:可接受最終一致性
        cached_summary = await self.eventual_consistency_cache.get(f"summary:{user_id}")
        if cached_summary and self.is_acceptable_staleness(cached_summary.timestamp):
            return cached_summary
        return await self.calculate_fresh_summary(user_id)

家庭財務系統的協作一致性

多用戶協作的一致性挑戰

class FamilyFinanceConsistency:
    """家庭成員間的數據一致性管理"""

    async def record_expense(self, family_id, member_id, expense):
        # 支出記錄:需要因果一致性,保證操作順序
        sequence_id = await self.get_next_sequence(family_id)

        # 使用 DynamoDB Streams 確保順序
        await self.expenses_table.put_item(
            Item={
                'family_id': family_id,
                'sequence_id': sequence_id,
                'member_id': member_id,
                'expense': expense,
                'timestamp': datetime.utcnow().isoformat()
            }
        )

        # 觸發其他家庭成員的快取更新
        await self.notify_family_members(family_id, member_id, expense)

快取策略選擇的業務邏輯簡要歸納

數據類型 更新頻率 一致性需求 快取策略 技術選型 業務理由
實時價格 秒級 強一致性 Write-Through Redis Cluster 交易決策基礎,零容忍錯誤
用戶持倉 事件觸發 最終一致性 Write-Behind DynamoDB DAX 寫入頻繁,允許短暫延遲
風險指標 分鐘級 因果一致性 Cache-Aside Application Cache 計算密集,需控制更新時機
歷史報表 日級 最終一致性 Read-Through CloudFront + S3 讀多寫少,全球分發需求
預算狀態 支出觸發 因果一致性 Cache-Aside EventBridge 家庭成員協作,需序列更新
設備數據 分鐘級 強一致性 Write-Through Redis Cluster 告警基礎,即時響應需求

上一篇
Day 9 | 高併發與限流設計:如何避免資源瓶頸
下一篇
Day 10-2 | 快取策略領域驅動哲學:空間、時間與監控的權衡藝術(二) - 資安暴露風險與生命週期效能的辯證關係
系列文
AWS架構師的自我修養:30天雲端系統思維實戰指南26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言