經過前面九天的建模與分析,我們已經確定了系統的本質、邊界和交互模式。但現在面臨一個更實際的問題:如何在真實世界的約束下,讓這些精心設計的領域概念快速響應用戶需求?
當投資交易系統需要在毫秒內完成風險計算、家庭財務系統要支撐多人同時操作、健康監控要處理海量 IoT 數據時,純粹的業務邏輯計算已經無法滿足性能要求。
這就是快取設計的本體論意義:在保持領域純粹性的同時,為系統注入時間和空間的優化策略。
每個快取決策都會影響用戶體驗、系統成本和數據正確性。所以快取的架構設計在 Domain 的驅動開發中無疑也是重中之重的議題。
我們在每次的需求確認中,需要且必須追問的是
這個業務場景對延遲和數據不一致的容忍度是多少?我們願意為此付出多少開發和維運成本?
我們接下來將逐步一同討論在快取中我們最常遇見的情境與議題。這裡簡單列下主題:
記住這個框架:熱度決定位置,容忍度決定模型,耦合度決定實現。當我們面對任何一個需要快取的場景時,先用這三個原則去剖析它,技術選型自然會水到渠成。
在什麼時機更新快取?如何在成本控制與系統性能之間找到最佳平衡點?如何處理快取帶來的資料一致性問題?
快取不只是技術優化,而是對數據存在狀態的深層思考。所有數據在不同的位置、不同的時間,都有其特定的存在意義和價值。
快取(Cache)的本質,就是在我們身邊放一個書架。
這個書架可以放在我們的書桌上(本地端快取)、樓下的閱覽室(雲端快取),甚至是圖書館門口的快速取書櫃(資料庫快取)。
但問題來了:
空間有限:我們的書架放不下所有書。我們放了 A 書,就可能得把 B 書拿掉。這就是 替換策略(Eviction Policy),比如 LRU(把最久沒看的書丟掉)。
資訊會過期:圖書館的書更新了,我們書架上的還是舊版。這就是 一致性(Consistency) 問題。
管理成本:我們得花心力去整理書架,決定哪些書要放、哪些要更新。這就是 快取策略(Caching Strategy) 的成本。
所以,快取設計的全部技術,都是圍繞著「如何用最小的管理成本,在有限的書架空間上,最大化地存放最有價值且不過時的資訊」這個核心問題展開的。
常見的區分方式有:熱數據 (Hot Data)、溫數據 (Warm Data)、冷數據 (Cold Data)
熱數據 (Hot Data):例如,一個交易系統的即時股價、社群媒體的熱門貼文。
溫數據 (Warm Data):例如,我們的個人資料頁面、不常變動的產品目錄。
冷數據 (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分鐘失效,穩定性優先
}
}
}
本地端快取的設計哲學:
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 作為數據端快取的抽象
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']
數據端快取的設計原則:
我們必須去思考我們所面對的業務能容忍多大程度的「數據不一致」?這是快取設計中最具挑戰性的哲學問題。例如,銀行帳戶餘額、交易下單,數據不一致會導致直接的金融損失或法律問題,在這個情境中 正確性壓倒一切。寧可慢,不可錯。這種就要求強一致性。
在設計分散式系統時,「一致性」是一個核心議題。特別是在涉及快取策略時,理解不同的一致性模型對於系統的行為和性能有著直接的影響。以下是三種主要的一致性模型及其特點:
概念與比喻:
就像在使用 Google Docs 時,我們打下一個字,我們的同事(理論上)會立即看到這個字。任何後續的操作都是基於這個最新的版本。所有人都看到完全相同的、唯一的「真相」。
特性:
優點:
缺點:
AWS 應用服務:
ConsistentRead=True
):當我們進行讀取時,可以明確要求 DynamoDB 執行「強一致性讀取」。這會直接從主分區讀取最新數據,但會消耗更多讀取容量單位(RCU)且延遲較高。MULTI/EXEC
事務或分散式鎖(如 RedLock),可以在特定操作上強制實現原子性和強一致性。概念與比喻:
我們在 Facebook 或 Instagram 上發布了一張照片。我們的朋友可能不會在同一毫秒看到它。美國的朋友可能比台灣的朋友晚幾秒鐘才看到,但系統最終會保證,在一段時間後,所有人都能看到這張照片。
特性:
優點:
缺點:
AWS 應用服務:
概念與比喻:
這是一致性模型中的一個精妙平衡。想像一個論壇的留言串:
因果一致性保證:任何看到評論 B 的人,必須也能看到文章 A。它不關心其他無關的操作順序,但它嚴格保護有「因果關係」的操作順序。一個用戶不可能看到「回覆」,卻看不到「原文」。
特性:
優點:
缺點:
AWS 應用服務:
特性 | 強一致性 (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 | 告警基礎,即時響應需求 |