多 Worker 下的定期 Job:以同步 External API 資料為例
Problem
FastAPI 有多個 replica / worker,而每個 worker 都會啟動相同的定期 Job。
如果沒有額外協調,同一個 Job 可能會被多個 worker 重複執行。
以下以「定期從 External API 取得資料並寫入 Redis」為例, 目標是: 不論有多少 worker,每個 INTERVAL 最多只執行一次 Job。
Core Method
INTERVAL = 15
POLL = 1
MAX_STALE = 300INTERVAL:最短 refresh 間隔POLL:worker 多久嘗試搶一次 lockMAX_STALE:舊值最多保留多久
核心:
if await r.set(LOCK, owner, nx=True, ex=INTERVAL):
resp = await client.get(EXT_URL)
resp.raise_for_status()
await r.set(KEY, json.dumps(resp.json()["value"]), ex=MAX_STALE)
await asyncio.sleep(POLL)其中:
SET lock owner NX EX 15NX:lock 不存在時才成功 → 同一時間只有一個 winnerEX 15:lock 15 秒後自動過期 → 控制下一次 refresh 最早何時發生
因此這個 lock 比較像 distributed cooldown,不是一般做完就立即 release 的 mutex。
sequenceDiagram
participant A as Worker A
participant B as Worker B
participant R as Redis
participant E as External API
A->>R: SET lock A NX EX INTERVAL
R-->>A: OK
A->>E: Refresh
A->>R: Update cached value
Note over A,R: INTERVAL<br/>lock remains active
B->>R: SET lock B NX EX INTERVAL
R-->>B: nil
Note over A,B: Every POLL, workers retry
Note over R: INTERVAL elapsed → lock expires
B->>R: SET lock B NX EX INTERVAL
R-->>B: OK
B->>E: Next refresh
實際 refresh 間隔約為:
INTERVAL ~ INTERVAL + POLLStale Value
成功 refresh 後,cache key 設定 MAX_STALE TTL。
API 正常
→ 持續更新 cache
API 暫時失敗
→ 繼續提供舊值
失敗超過 MAX_STALE
→ key 過期
→ 回 503Limitation
SET NX EX 可以讓共用同一個 Redis / lock key 的 workers 在正常情況下,每個 INTERVAL 最多只有一個取得執行權,但不保證 exactly-once execution。
若 Job 可能超過 lock TTL,或操作具有副作用,應搭配 idempotency 或更嚴格的 coordination 機制。
References
Last updated on