Redis
一個基於記憶體的鍵值對 (key-value pair) 資料庫,資料庫存在記憶體,讀寫速度極快。
為何這麼快? - 記憶體操作。- 單執行緒模型:避免多執行緒頻繁切換上下文 (Context switch) 的開銷,也解決併發競爭問題。 (切換執行緒,CPU 必須儲存目前執行緒的狀態,並載入下一個執行緒狀態,Redis 這種每秒處理十萬次請求的系統,若頻繁切換執行緒,會讓 CPU 花大量時間在切換執行緒上,浪費效能。)
非阻塞 IO 多路復用:可同時處理大量客戶端請求。 (多路:多條連線;復用:只有一個執行緒在線上服務) ==epoll==機制:Redis 將監控連線狀態這項工作,交給 Linux 的 epoll,此時Redis 執行緒會進入休眠狀態 (不消耗 CPU ),當連線中有動靜 (有資料傳輸進來),epoll 會通知 Redis 執行緒,並給他一張清單,告訴它是哪幾條連線有動靜,馬上過去處理,處理完後,再把這些連線丟還給 epoll 那邊繼續監控。
主要功能:
1. 快取
可以將資料庫查詢結果,先存在 Redis 內,速度快很多
- 流程:1. 命中 (Hit):程式先去 Redis 找資料,有就直接回傳。2. 失效 (Miss):如果 Redis 沒資料,程式再去資料庫拿。3. 回填 (Write back):從資料庫拿到資料後,順手存進 Redis 一份,設定好 TTL,方便下次使用。
註:若修改了資料庫的資料,記得刪除 Redis 裡的舊資料,確保下次讀取時會去資料庫拿最新的。
- 重要參數:
- TTL (Time To Live):快取可以存活的時間。
- Maxmemory (最大記憶體限制):可限制 Redis 只能用多少 RAM。
- Eviction Policy (淘汰策略):若記憶體滿了,新資料要進來,舊資料怎麼處理?
- LRU (Least Recently Used): 踢掉最久沒人用的。
- LFU (Least Frequently Used): 踢掉使用頻率最低的。
- 重要參數:
2. 分散式鎖
解決多台伺服器爭取同一資源的解法。
以 NSH 專案來說,有三種方式會產出單一新聞摘要,第一種是爬蟲結束後,會批次上傳 gemini 獲得摘要;第二種是 WEB 使用者點擊新聞,若尚未有摘要,會上傳並生成;第三種則是 Line 用戶點擊新聞,若尚未有摘要,會上傳並生成。
若這三種方式在同一時間觸發,會造成同一篇新聞上傳三次,生成摘要三次,浪費 token ,所以需要使用一個共用的鎖來避免這種情形發生。
- 實現鎖的三要素:
互斥性 (Mutual Exclusion)–
SETNX最基本的搶鎖動作 - 指令:SET lock_key uniqle_id NX - 原理:NX代表 “Not eXists”。只有當 lock_key 不存在時,Redis 才會設定成功並回傳 OK。如果已經有人搶走了,你會收到回傳空值,代表搶鎖失敗。防死結 (Anti-Deadlock)–
PX(TTL)- 風險:如果程式搶到鎖後突然當機,沒去解鎖,這把鎖就會永遠留在那。
- 解法:SET lock_key unique_id NX PX 30000(設定 30 秒後自動過期)。即便程式掛了,30 秒後 Redis 會自動把鎖打開。
安全釋放 (Safe Release)–
Unique ID問題: 假設 A 搶了鎖,但任務跑太久(超過 30 秒),鎖自動過期了。這時 B 搶到了新鎖。結果 A 跑完後執行「刪除鎖」,卻把 B 的鎖給刪掉了!
解法: 每個程式搶鎖時都要帶一個隨機的 unique_id(如 UUID)。解鎖時,程式會先問 Redis:「這把鎖的 ID 是我的嗎?」是的話才刪除。
3. Streams
設計靈感來自 Apache Kafka,專門為「分散式訊息隊列」與「異步任務處理」而生。
主要結構為一條訊息流(Stream)包含多筆訊息,每筆訊息由一個唯一的 ID 和多個 Field-Value(鍵值對) 組成。
特性:
- 持久化(Persistence):訊息發布後會存在 Redis 記憶體中,並可同步到硬碟。即便沒有消費者在線,訊息也不會遺失。
- 唯一 ID 系統:預設 ID 格式為 時間戳記-序號(例如 1678901234567-0),保證了訊息的順序性。
- 消費者群組(Consumer Group):這是 Streams 最強大的功能,支援多個消費者分工處理同一個 Stream,確保任務不重複、不漏掉。
- 確認機制(ACK):消費者處理完訊息後必須回報「ACK」,Redis 才會認為該任務已完成。
- 待處理清單(PEL):Redis 會紀錄「已發出但尚未收到 ACK」的訊息。如果某個消費者當機,可以透過此清單將任務重新分配給別人。
以 NSH 專案為例 (未來擴充): 若日後爬蟲新聞量擴充,可以加入 Redis streams,加入後,爬蟲取得的新聞 (生產者),會依序排入這個隊列中,並且每一條新聞都會有自己的專屬 ID,新聞會依序進到消費者群組內 (此群組會將隊伍中的新聞分配給群組內的消費者,消費者分工去生成摘要),這個機制可以確保任務不會重複也不會漏掉,唯有當消費者生成新聞摘要後並存入資料庫後,消費者才會回報 ACK,Redis 才會認為該任務已完成,此外,Redis 也會記錄那些「已發出但尚未收到 ACK」的訊息,如果某個消費者當機,可以透過此清單將任務重新分配給別人。