很多人對 Redis 的印象停在「一個很快的快取」。它確實是,但在我做的高併發系統裡,Redis 扮演的角色遠不只如此。這篇講我實際用 Redis 的幾種方式、各自要注意什麼,以及那些「用錯會出大事」的細節。
一、快取:最常見,但魔鬼在失效
快取是 Redis 最基本的用途:把昂貴的查詢結果存起來,下次直接拿,省掉資料庫的負擔。簡單,但真正難的從來不是「存」,是「什麼時候讓它失效」。
我踩過的坑:
- 快取與資料庫不一致:資料更新了,但快取還是舊的。我的原則是更新資料時主動讓對應的快取失效,而不是傻等它過期
- 快取雪崩:大量 key 同時過期,請求瞬間全打到資料庫把它壓垮。解法是讓過期時間帶一點隨機,錯開失效時間
- 快取穿透:一直查一個根本不存在的 key,每次都繞過快取打到資料庫。解法是連「不存在」這件事也快取起來(存一個空值短時間)
快取講起來簡單,但這幾個失效情境沒處理好,它反而會變成壓垮系統的那根稻草。
二、分散式鎖:跨服務的互斥
當你有多台機器、多個程序,要確保「同一件事同一時間只有一個人在做」(例如同一張訂單不要被兩個 worker 同時處理),就需要分散式鎖。Redis 常被拿來做這個。
關鍵細節,每一個都很重要:
- 鎖一定要設過期時間:拿到鎖的程序如果當掉了卻沒釋放,這把鎖就永遠鎖死。設過期時間是保命的
- 解鎖要驗身分:你只能釋放「自己拿的」那把鎖。每次上鎖帶一個唯一識別,解鎖時先確認是自己的才放,否則可能誤放別人的鎖
- 過期時間要抓準:設太短,工作還沒做完鎖就過期,別人就進來了,等於沒鎖;設太長,當掉之後要等很久才釋放
分散式鎖看起來簡單,但這些細節錯一個,鎖就形同虛設。在金流場景,「鎖沒鎖住」可能就是重複扣款。
三、原子計數與庫存扣減
Redis 的單執行緒特性讓它的單一指令天然是原子的,這在搶購系統裡超好用。前面我寫搶購系統時提過:用 Lua script 把「檢查庫存大於 0 就遞減」包成一個原子操作,就能在記憶體裡安全地扣庫存,扛住瞬間高併發,完全不碰資料庫。
重點是原子性:判斷和扣減必須是不可分割的一步,否則併發下一定超賣。
四、排行榜:sorted set 的主場
即時排行榜(積分榜、熱門排序)用關聯式資料庫做會很吃力——每次都要排序一大堆資料。Redis 的 sorted set 天生就是為這個設計的:每個成員帶一個分數,自動依分數排序,查「前 N 名」「某人的排名」都是高效操作。更新分數、即時反映排名,順手得很。
五、限流
Redis 也常拿來做限流的計數器——用它的原子遞增配上過期時間,記錄「某使用者在這段時間內請求了幾次」,超過就擋。因為是集中式的,多台機器共用同一份計數,限流才準。限流我之後有專門一篇細講。
別忘了:Redis 是記憶體,資料會掉
最後一個心態上的提醒:Redis 主要活在記憶體裡。雖然有持久化機制,但它的定位不是「絕對不能掉資料的主資料庫」。我的原則是:真正的事實來源放在資料庫,Redis 放的是可以重建的東西(快取、可重算的計數、即時排名)。萬一 Redis 掉了,系統要能從資料庫重建,而不是直接損失關鍵資料。
小結
Redis 在我的系統裡的角色:
- 快取:重點是失效策略,小心雪崩與穿透
- 分散式鎖:一定要過期、解鎖要驗身分、過期時間抓準
- 原子扣庫存:用 Lua script 保證原子性,扛搶購
- 排行榜:sorted set 的主場
- 限流:集中式計數器
- 心態:Redis 放可重建的東西,事實來源在資料庫
Redis 很強,但它的每一種用法都有「用錯會出事」的細節。把這些細節摸熟,它就是你高併發系統裡最可靠的夥伴之一。