Redis 應用案例:快取、佇列與速率限制
你已經建置了一個可運作的網頁應用,但隨著流量成長,你注意到頁面載入變慢、資料庫瓶頸,以及偶爾的停機。這些都是常見的擴展痛點。Redis 是一種記憶體內資料儲存,能優雅地解決許多這類問題。在本文中,我們將探討三個實用的 Redis 應用案例:快取、佇列與速率限制。你將透過程式碼範例與最佳實務,學習如何實作每一個案例。
為什麼選擇 Redis?
Redis 是一個開源的記憶體內鍵值儲存,以其速度與多功能性著稱。它支援字串、列表、集合、雜湊與有序集合等資料結構,使其適用於各種任務。由於資料保存在記憶體中,讀寫速度極快——通常低於一毫秒。雖然它不是主要資料庫的替代品,但作為效能與協調的輔助工具,它表現出色。
1. 使用 Redis 進行快取
快取是最常見的 Redis 應用案例。將經常存取的資料儲存在 Redis 中,可減輕主要資料庫的負載並加速回應。
如何實作快取
- 先檢查 Redis:當請求進來時,使用唯一鍵在 Redis 中尋找資料。
- 快取未命中時:如果 Redis 中沒有資料,則從資料庫取得。
- 儲存至 Redis:將結果存入 Redis 並設定過期時間(TTL),以避免資料過期。
- 回傳資料:將資料傳送給客戶端。
以下是使用 redis-py 的簡單 Python 範例:
import redis
import json
r = redis.Redis(host='localhost', port=6379, db=0)
def get_user(user_id):
cache_key = f"user:{user_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
# 模擬資料庫查詢
user = fetch_user_from_db(user_id)
r.setex(cache_key, 3600, json.dumps(user)) # 快取 1 小時
return user
最佳實務:
- 使用有意義的鍵名並以冒號分隔,例如
user:123。 - 設定 TTL 以防止資料過期與記憶體膨脹。
- 考慮快取失效策略:寫穿(write-through)、寫回(write-behind)或基於時間的過期。
- 對於複雜物件,使用 JSON 或 MessagePack 進行序列化。
2. 使用 Redis 進行佇列
Redis 列表與串流非常適合建置簡單的訊息佇列。它們讓你能將耗時任務(如寄送電子郵件或處理圖片)與主要請求流程解耦,提升回應速度。
使用列表作為佇列
Redis 列表支援原子性的推入與彈出操作。使用 LPUSH 將工作加入左側,並使用 BRPOP 阻塞並從右側彈出,形成 FIFO 佇列。
# 生產者:加入一項工作
r.lpush('job_queue', json.dumps({'task': 'send_email', 'to': 'user@example.com'}))
# 消費者:處理工作
while True:
_, job_data = r.brpop('job_queue', timeout=30)
job = json.loads(job_data)
process_job(job)
注意事項:
- 可靠性:列表預設不具持久性;如有需要,請啟用 AOF 或 RDB 持久化。
- 至少一次傳遞:使用
BRPOPLPUSH將工作移至處理中列表,完成後再移除。 - 串流:對於更進階的應用案例(消費者群組、確認機制),可考慮 Redis Streams。
3. 使用 Redis 進行速率限制
速率限制可保護你的 API 免於濫用,並確保公平使用。Redis 的原子操作與過期機制使其成為實作速率限制器的理想選擇。
固定視窗速率限制
在固定視窗中,你允許每個時間視窗內有 N 個請求(例如每小時 100 個請求)。使用 INCR 與 EXPIRE 來追蹤計數。
def is_rate_limited(user_id, limit=100, window=3600):
key = f"rate:{user_id}"
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current > limit
使用有序集合的滑動視窗提供更平滑的限制。將請求的時間戳記儲存在有序集合中,並移除舊的項目。
def is_rate_limited_sliding(user_id, limit=100, window=3600):
key = f"rate:{user_id}"
now = time.time()
pipeline = r.pipeline()
pipeline.zremrangebyscore(key, 0, now - window)
pipeline.zadd(key, {now: now})
pipeline.zcard(key)
pipeline.expire(key, window)
_, _, count, _ = pipeline.execute()
return count > limit
最佳實務:
- 為每個使用者或 IP 使用唯一的鍵。
- 與中介軟體結合以實現乾淨的整合。
- 考慮使用像
redis-cell這樣的 Redis 模組來實現進階演算法。
應用案例比較
| 應用案例 | Redis 資料結構 | 主要優勢 |
|---|---|---|
| 快取 | String, Hash | 減少資料庫負載、加快讀取 |
| 佇列 | List, Stream | 解耦任務、提升回應速度 |
| 速率限制 | String, Sorted Set | 保護 API、確保公平使用 |
常見問題
Redis 是持久性的嗎?
Redis 透過 RDB 快照與 AOF 日誌提供持久化,但它主要是一個記憶體內儲存。對於關鍵資料,請務必備份或使用主要資料庫。
我可以使用 Redis 進行工作階段儲存嗎?
可以,Redis 由於其速度與自動過期特性,常被用於工作階段儲存。它讓你能夠在多台伺服器之間共享工作階段。
如果 Redis 當掉會發生什麼事?
如果 Redis 無法使用,你的應用應該優雅地退回資料庫,或從本機快取回傳資料。實作斷路器以避免連鎖故障。
準備好最佳化你的網頁應用了嗎?從整合 Redis 進行快取、佇列或速率限制開始。對於格式化 JSON 或分析日誌等快速任務,請參考我們的 JSON Formatter 與 Nginx Log Analyzer。