文章收藏站的首頁反覆顯示同一篇文章標題。即使查詢已經有合適索引,每次開頁仍要向資料庫取一次。有人提議:「把標題放進 Redis。」
先別急著安裝。加快取,就是多保存一份可重用的答案,也多承擔一份可能過期的資料。 你要先知道原本在哪裡、誰會更新,以及副本何時不該再用。
以下用公開文章 post_id=17 的標題示範。所有時間與流量數字都是教學假設,流程並未做效能實測;不快取使用者私人的收藏內容。
一個 key,只回答一個明確問題
假設 MySQL 的 posts 表保存正式文章資料,Redis 只保存讀取用副本:
資料庫:posts.id = 17,title = "理解 HTTP"
快取 key:post:17:public-title:v1
快取 value:"理解 HTTP"
保存期限:60 秒
這裡的 key 是我們自己設計的名稱。17 表示哪一篇,public-title 限定這份資料的用途,v1 用來區分資料格式;它不是自動偵測內容更新的版本號。若答案還會隨語言改變,語言也要成為 key 的一部分,否則不同頁面會拿到同一份不適用的答案。
MySQL 在這個設計中是權威來源:標題有爭議時,以它為準。Redis 是產品名稱,快取則是用途;不能因為資料放在 Redis,就認定遺失後一定可以重建。本例刻意只放能從資料庫重建的副本。
命中省掉查詢,未命中才補一份
最小讀取流程是:先問 Redis;取得值就回傳,稱為命中(hit)。找不到稱為未命中(miss),接著讀資料庫,把結果存進 Redis,再回傳給讀者。這是常見的 cache-aside 流程。
同一個標題,兩條讀取路徑
1 先問 Redis
key:post:17:public-title:v1
應用程式要求文章 17 的公開標題
2A 命中 hit
Redis 有值 → 回傳副本
不查 MySQL;仍需接受副本可能落後
2B 未命中 miss
查 MySQL → 存 Redis → 回傳
MySQL 保持權威;副本可以重建
命中只代表「找到副本」,沒有順便證明「副本仍與資料庫相同」。相反地,未命中也不等於系統壞了:第一次讀取、到期或被清除,都可能正常走向資料庫。
在應用程式裡,快取查詢要能區分「沒有這個 key」與「值剛好是空字串」,不要用一般真假值判斷代替命中判斷。至於文章已刪除時要不要快取「查無資料」,是另一項產品決策,這個最小方案先不保存。
TTL 是副本的倒數,不是資料同步時鐘
Redis 的 SET ... EX可以同時寫入值與以秒為單位的期限。以下是指令語意示意,沒有在本篇執行:
SET post:17:public-title:v1 "理解 HTTP" EX 60
TTL post:17:public-title:v1
TTL讀取剩餘秒數;-1 表示 key 沒有期限,-2 表示不存在。到期後,下次讀取會走 miss 路徑,不會因為時間到了就自動去 MySQL 抓新標題。
TTL 倒數與內容更新,是不同事件
第 0 秒
MySQL:A | Redis:A
寫入副本,設定 TTL 60 秒
第 10 秒
MySQL:B | Redis:A
原本改名;副本仍命中,但內容已落後
第 61 秒
MySQL:B | Redis:無可讀值
下一次請求才 miss、讀取 B 並回填
假設第 0 秒放入舊標題,第 10 秒資料庫改名。若這段時間沒有刪除或重新填入快取,讀者在到期前仍可能讀到舊標題。這份副本有值、未到期,卻已經 stale:落後於原本。
因此,60 秒是這份副本從寫入起計算的存活期限,不是每次資料庫更新後都能保證 60 秒內同步的完整契約。讀取來源落後、並行回填等情況,還會改變你能承諾的範圍。
更新原本,再讓副本失效
對這個公開標題,最小更新流程可以是:資料庫更新成功,再刪除對應 key,讓後續讀取重新取得資料。刪除只讓副本不存在,沒有負責製造新版副本。
仍有兩個失敗點。資料庫成功後,刪除可能因 Redis 斷線失敗;另一個已在進行中的讀取,也可能把先前讀到的舊值放回快取。「有呼叫刪除」不能當成所有讀者都已看到新版的證據。
這篇先把要求限在「公開標題允許短暫落後,編輯完成畫面直接使用更新結果」。若需求是退款、權限或庫存這類不能隨意接受舊值的資料,就該另訂讀取契約,而不是照抄相同 TTL。進一步的並行情境可接著讀快取與讀取契約。
值不值得加,要連同 miss 成本一起算
假設每秒有 100 次標題查詢,實際命中率能達到 90%,則平均每秒約有 100 × (1 − 0.9) = 10 次查詢需要回源。這只是穩定流量下的估算,沒有包含冷啟動、同時到期與 Redis 故障。
與直接讀資料庫相比,這個方案多了一次快取網路往返,也多了連線、逾時與失效處理。若標題查詢原本已很便宜,流量又低,維持有索引的資料庫查詢可能更划算。不能把命中率估算直接寫成延遲改善百分比。
驗證時分開觀察 hit 與 miss 的端到端延遲、資料庫每秒讀取量,以及 Redis 不可用時的行為。故障時若全部回源,必須確認資料庫承受得住;承受不了,就得限制流量或明確回報暫時無法服務,不能無上限地等待或重試。
最小驗收也不複雜:第一次讀取是否填入、第二次是否重用、到期後是否重新查詢、改名後是否失效。再刻意讓刪除失敗,檢查實際畫面與你允許的過期程度是否一致。
當尖峰回源壓垮資料庫、失效規則越來越多,或產品要求更強的新鮮度,才需要進一步設計合併回源、版本檢查或不同的更新方式。先掌握副本生命週期,再比較不同快取寫入模式;容量不足導致的清除,則是快取淘汰策略要處理的另一件事。
練習:找得到,為什麼還是舊的?
第 0 秒把標題 A 存進 Redis,TTL 為 60 秒。第 20 秒資料庫改成 B,但刪除快取失敗。第 25 秒有人讀取,看到 A。這次是 hit 還是 miss?第 61 秒沒有任何人讀取時,Redis 是否應該已經存著 B?
參考思路
第 25 秒是 hit,因為找到尚可讀取的副本;它同時也是 stale,因為原本已更新。第 61 秒到期不會主動生成 B,下一次讀取才會 miss、查資料庫並回填。這就是快取最重要的區分:存在、有效期限與內容新鮮度,是三件不同的事。
