【從一台 Server 到分散式架構】第 05 篇:常被問的菜單寫在小黑板——Redis 與快取

上一篇我們聊到,當小明他們的課程平台上線人數變多,資料庫查詢變慢時,最有效也最省錢的第一步是「優化資料庫體質」,加上適當的索引並改寫沒有效率的查詢語句。

經過那波優化,伺服器確實穩定了好一陣子。但隨著小明平台的名氣越來越大,甚至開始有知名講師進駐開課,他們遇到了一個新問題:首頁的「熱賣排行」跟「精選講師資訊」被瘋狂地查詢。

想像一下,每秒鐘有幾千個訪客進到首頁,後端程式就會乖乖地去問資料庫:「請問現在賣得最好的是哪五堂課?」資料庫哪怕已經加了索引,還是要在一大堆訂單與課程表之中做計算、做關聯,然後回傳結果。

但這五堂「熱賣課程」,每一秒鐘的結果根本就一模一樣啊!為了這幾乎不變的結果,卻讓最核心的資料庫每秒鐘重複計算幾千次,這不僅是資源的浪費,也是讓系統陷入危機的導火線。


快取的直覺:常被問的菜單寫在小黑板

如果把資料庫比喻成餐廳裡那本厚厚的大帳本與菜單總匯,每次客人進來問「今天有什麼特價菜?」,店員都要翻開大帳本查一次,不僅店員累,客人也排隊排得很長。

這時候,聰明的店長會在門口擺一塊小黑板。 店長把今天最熱門的「特價菜單」直接抄在小黑板上。當客人進來問特價菜,店員連翻帳本都不用,直接指著小黑板說:「都在上面,自己看。」

在系統設計裡,這個「小黑板」就是快取(Cache),而在實務上,最常被拿來當這塊小黑板的工具之一,就是 Redis

Redis 是一個跑在記憶體(Memory)裡的資料儲存系統。相比於寫在硬碟裡的資料庫(如 MySQL 或 PostgreSQL),讀取記憶體的速度快了不只一個數量級。我們把後端程式查出來的「熱賣排行」存進 Redis,接下來的幾千個查詢,後端不用再去煩資料庫,直接去 Redis 拿資料就回傳給前端。

有了這塊小黑板,首頁載入速度瞬間從幾百毫秒掉到了幾毫秒,資料庫的 CPU 也瞬間獲得了解放。


什麼資料適合寫在小黑板上?

既然小黑板這麼好用,那我們把「所有的」資料都抄上黑板不就好了? 當然不行。因為記憶體很貴,小黑板的空間很有限;而且黑板上的資料如果一直變,店員整天在那邊擦黑板、重寫黑板,反而更沒效率。

所以,通常只有這兩類資料適合放進快取:

  1. 讀多寫少,且變動不頻繁的資料:例如課程介紹、熱門文章清單、平台共同設定。這些資料可能幾天才改一次,但每秒都被瘋狂讀取。
  2. 計算極度耗時的結果:例如經過複雜聚合運算出來的「年度銷售總排行榜」。這種只要算好一次,存進快取讓大家直接看結果就好,不用每次重新算。

什麼不適合放? 像使用者的「私人對話紀錄」或是「剛下單的付款狀態」,這種每秒都在變、而且只有特定一個人會看的資料,你把它寫上黑板既沒有共用效益,還會為了維持資料正確性而搞死自己。


黑板何時要擦掉?—— 快取的過期與失效策略

小黑板最大的致命傷在於:如果帳本(資料庫)已經改了,但小黑板沒改,客人就會看到舊菜單。

這在系統設計裡叫做「資料一致性問題」。實務上,小明他們有幾種策略來決定什麼時候把黑板擦掉換新:

1. 給資料一個保存期限(TTL, Time to Live)

最簡單暴力的做法。小明在把熱門排行寫進 Redis 時,設定這筆資料「存活 10 分鐘」。 10 分鐘一到,Redis 自動把這筆資料刪掉。下一個使用者連進來,發現黑板空了(Cache Miss),後端就會跑去問真正的資料庫,算完後回傳給使用者,同時把「最新的」熱門排行再寫進 Redis,並重新倒數 10 分鐘。 就算這 10 分鐘內資料庫的排名變了,訪客看到舊的排行也無傷大雅。這種**「允許短時間內資料不一致,一段時間後會自我修正」的策略,在業內稱為最終一致性(Eventual Consistency)**的一種體現。

2. 主動刷新(Cache Invalidation)

如果某個資料非常重要,不能有 10 分鐘的落差(例如講師立刻把課程下架,首頁就絕不能再賣),那就在後端程式裡寫一段邏輯: 「當資料庫的課程狀態被更新時,同時去把 Redis 裡的那筆資料刪除或覆寫。」 這能確保最高的一致性,但也代表後端的程式碼會變得比較複雜,幾乎每個寫入的動作都要考慮到「要去哪裡刪快取」。


快取世界的三大危機:打穿、雪崩、擊透

把系統架上 Redis 看似解決了效能問題,但如果不了解它的副作用,反而會引發更災難性的後果。很多面試愛考名詞,其實都是因為這幾張小黑板出事了:

1. 快取穿透(Cache Penetration) 這是指有人一直來查一個「根本不存在」的東⻄(例如亂猜課程 ID = 99999999)。因為黑板上當然沒有查不到的東西(Cache Miss),所以後端就跑去查大帳本(資料庫),結果大帳本裡也沒有。 如果駭客一秒鐘發出一萬個這種惡意請求,所有請求就會「穿透」小黑板而直接重擊資料庫。 解法:對於資料庫查不到的東⻄,也把它寫上黑板並標記為 null 或「空值」。你心裡可能會有個疑問:那如果明天這堂課真的被建出來了,快取不就一直以為「沒有這堂課」嗎?沒錯,所以這個「空值」快取通常會設定一個非常短的存活時間(例如 30 秒)。駭客通常是一秒鐘打幾千次,我們只要讓這個 null 存活 30 秒,就能擋下這段時間內的幾萬次攻擊,保護資料庫。30 秒後快取消失,大不了再查一次 DB,發現還是沒有,就再擋 30 秒。而如果是正常的系統流程新增了這筆資料,後端寫入資料庫成功的同時,就會由程式主動去把這個空值快取殺掉,所以使用者下一秒來查,就會正常去資料庫撈出熱騰騰的新資料了。除了存空值,有些規模更大的系統會用布隆過濾器(Bloom Filter)來快速判斷資料存不存在,這裡先不展開。

2. 快取雪崩(Cache Avalanche) 記得剛剛提到的 TTL 嗎?假設小明很懶,把所有熱門課程、講師名單的 TTL 都設定為每天半夜 12 點過期。 在 12:00:00 那一瞬間,黑板上「所有」的重要資訊同時被擦光。這時候進來的龐大流量發現黑板全空,全部一起轉頭湧向資料庫去查帳本。這座平時都靠小黑板保護的資料庫,瞬間承受不了暴增的壓力而崩潰,這就是雪崩。 解法:不要讓大家的過期時間一樣。在設定 TTL 時,加上一個隨機的亂數(例如加上 1~5 分鐘),讓黑板上的資料「分批」過期,錯開查 DB 的高峰。

3. 快取擊穿(Cache Breakdown / Hotspot Invalid) 這和雪崩有點像,但是是針對單一一個極度熱門的資料。 假設晚上 8 點有超級名師直播開賣課程,幾萬人同時在點這堂課。偏偏在 8:00:05 的時候,這個課程的快取「剛好過期」了。 在快取過期、但新的快取又還沒寫進去的那短短幾十毫秒內,這幾萬個請求發現黑板沒字,全都衝向 DB 要查這堂課,瞬間把 DB 打趴。 解法:針對這種極度熱門節點,除了不要隨便讓它過期外,實務上常在程式層面加上「互斥鎖(Mutex / Lock)」。當發現快取過期時,只放行「第一個」請求去查庫跟寫黑板,其他人在外面稍微排隊等一下,等第一個寫好黑板了,其他人再來看現成的。


小結與預告

當資料庫的查詢已經無法再靠索引優化時,我們引入了 Redis 這塊小黑板(快取)。把讀多寫少、耗時計算的熱門資料放在記憶體裡,可以擋掉 90% 以上的資料庫壓力。但同時我們也必須面對資料一致性的取捨,以及防範駭客或大流量帶來的打穿與雪崩危機。

有了 Redis 擋在前面,小明他們的課程平台又順利度過了一次危機。不過,隨著課程與訂單每天持續產生,帳本裡的資料越來越龐大;而且,就算讀取的壓力被 Redis 擋掉了大半,**「每個人買課新增訂單、寫評論、更新狀態」**這些需要真正寫入帳本的動作,依然全擠在同一台資料庫上。

大家都要寫同一本帳本,開始要大排長龍了。 既然一本帳本來不及寫,我們能不能多買幾本?但多買幾本,要怎麼確保大家的帳目不打架?這就是我們下一篇要談的:共用一本帳本太慢——讀寫分離與主從複製。我們下一篇見。

相關推薦

2026-04-07
【從一台 Server 到分散式架構】第 31 篇:從面試題反推架構——用本系列思維拆解系統設計考題
「設計一個 URL 短網址服務」「設計一個訊息系統」——系統設計面試問的不是背答案,而是思考方式。這篇整理了一套可以重複使用的面試框架,並用兩道例題示範如何從需求出發,一步步推導出架構、說清楚取捨。
2026-04-06
【從一台 Server 到分散式架構】第 30 篇:限流、排隊與降級實戰——開賣與直播場景
平常流量是 1000 QPS,但「開賣」的那一刻,瞬間湧入 50 萬個請求——系統要怎麼活下來?這篇用課程平台的限量課程開賣和直播開播場景,把第 13 篇學到的限流、降級、熔斷,落地到一個真實的高流量設計,走過每個防護層是怎麼工作的。
2026-04-05
【從一台 Server 到分散式架構】第 29 篇:用同樣的思維看 ChatGPT——AI 聊天系統架構
ChatGPT 看起來像一個聊天視窗,背後卻有幾個特殊的設計挑戰:回應是串流的、推理非常耗資源、每輪對話要記住上下文、系統要支援幾千萬用戶同時使用。這篇用熟悉的架構思維,拆解 AI 聊天系統的關鍵設計。
2026-04-04
【從一台 Server 到分散式架構】第 28 篇:用同樣的思維看 Twitter——社群動態與時序設計
Twitter 的核心功能看起來很簡單:發推文、看動態、按讚留言。但「動態牆」背後藏著一個棘手的設計問題:你追蹤 200 個人,每個人都可能隨時發文——你打開 App 時,那條時序動態要怎麼快速組出來?這篇來看社群動態系統的兩種策略:推(Fan-out on Write)與拉(Fan-out on Read)。