系統設計高頻率元件之 Redis
2025年10月29日
在系統設計中有許多「高頻率會用上」的要件。所謂的高頻率會用上,是指不論哪類系統,都會有使用這些要件的需求。
其中鍵值對資料庫是指由鍵值對 (key-value pair) 組成的存儲機制。這種資料庫在存資料時,會同時存一個獨特的 id,然後要取資料時,再根據這個 id 來取。
常見的鍵值對資料庫有 DynamoDB、MongoDB,或是 etcd、Zookeeper,以及 Redis 等等,上面這些都是鍵值對資料庫,但都有不同的特性。這篇會著重在 Redis 這類把資料存在記憶體的鍵值對資料庫。目前社群中相似解決方案有很多,先前 Redis 開源授權風波後,各大廠都有推出相對應的解決方案。不過由於 Redis 在過去被廣泛使用,以下都仍以 Redis 這個詞來代表相似類型的解決方案。
Redis 的基本介紹與特性
如上面提到,Redis 是一種鍵值對資料庫,是由鍵值對的方式來存資料,而 Redis 的每個鍵可以對應的資料結構很多元,基本上最常見的資料結構都有被支援。舉例來說,字串 (string)、列表 (lists)、集合 (Sets),甚至是 布隆過濾器 (Bloom Filters) 以及地理空間索引 (Geospatial Indexes)
因為是鍵值對的操作,所以可以簡單地用 SET 來為某個鍵設定對應的值,以及用 GET 來取得相對應鍵的值。舉例來說,我們可以這樣
SET explainThis 1
GET explainThis # 拿到 1
當然除了上面做基本的存跟取之外,Redis 還提供許多方法可以直接用。有興趣的人可以參考相關文件 [連結],這邊就不展開。下面我們將會聚焦談幾個 Redis 常在系統設計會被用的地方。
做為鍵值對資料庫,Redis 的一大特性是把資料存在記憶體,這讓 Redis 的速度很快。同時,Redis 的可擴展性很好。Redis 主要透過一種叫 hash slot 的分片方式,把鍵分到集群中的不同節點,讓集群要添加節點變很容易,一個集群每秒處理百萬個讀取請求都很容易 (AWS 的 ElastiCache for Redis 先前號稱做到每個集群能一秒處理五百萬個讀取請求)。
在初步了解完 Redis 與其特性後,接著讓我們來談一些實際可以應用上 Redis 的地方。
Redis 作為快取 (caching)
上面提到,Redis 又快又能處理大量請求,基於這個特性,Redis 很常會被作為快取來用。服務先查看 Redis,如果有快取且沒過期,就直接用快取,就不用跟資料庫拿。如果沒有,就跟資料庫拿,然後再放到快取,並設定快取的存活時間 (TTL)
在上面的流程中,如果快取還沒有過期,直接跟 Redis 拿,會比跟資料庫拿快很多;而有 Redis 做為快取,除了能夠降低延遲外,也可以降低後端資料庫的壓力。如果在系統設計中遇到有低延遲要求的系統,Redis 可能是解決方案之一。
Redis 作為分布式鎖 (distributed locking)
除了快取外,Redis 也很常會被拿來當分布式鎖。在一個分散式系統中,如果要保持節點之間的一致性 (consistency),或是要確保同樣的操作不會被重複做,這時就可以用 Redis 來做為分布式鎖。
讓我們用搶票系統作為例子來進一步說明。一般來說,搶票系統對於一致性要求很高,例如,使用者 A 搶到的票,不能同時被其他使用者搶,不然會出現一張票賣兩個人,這就麻煩了。這時候就需要一個機制來避免這種重複的問題,而鎖 (lock) 是很常會用到的方法。
當提到鎖,你可能會想到用資料庫的悲觀鎖 (pessmistic lock),但在搶票系統的情境下,這不是好的做法。因為通常搶票系統要讓搶到票的人,保留時間來付款 (例如搶到後保留五分鐘,沒付款的話就釋放掉);而悲觀鎖適合短時間上鎖,要鎖五到十分鐘不適合用這種方式。
而分布式鎖在這種情況就會更適合,具體來說,可以用 Redis 搭配存活時間 (TTL) 來實現。 以搶票系統來說,當使用者 A 搶到票,會用票的 id 以及存活時間 (TTL) 拿到 Redis 的鎖。如果 A 有在鎖的存活時間內完成付款,則會更新到資料庫,確保其他人不會重複購票;假如時間到了沒付款,鎖就可以自動釋放,讓票能被其他人搶。
用於「在乎即時」的情境
前面提到 Redis 的特點之一是快,這也讓 Redis 經常會用來處理在乎即時的情境。舉例來說,在一個有全球排行榜的遊戲中,Redis 的快速可以用來確保排名的更新夠即時。具體來說,我們可以存每一位玩家的分數,然後透過 Redis 的 ZREVRANGE 來取分數最高的前幾名 (例如前十名) 顯示在排行榜當中。
甚至目前在這種在乎即時的情境中,會有團隊直接拿 Redis 當作主要資料庫,而不是當作快取。不過相信讀者可能會問,因為前面談到 Redis 把資料存在記憶體中,換句話說假如機器壞了或重啟,這些資料就會丟失,這樣作為主要資料庫,不是很危險嗎?
針對這類問題,目前許多提供類似 Redis 的資料庫,都會有相對應的配套。常見的有兩種,一種叫 RDB,這種做法是用快照的方式,定期 (例如每隔 5 分鐘) 看現在 Redis 中有哪些資料,然後開一個子行程 (child process) 把當下 Redis 有的資料寫入一個 rdb 檔,然後把該檔案存到硬碟中,就不擔心資料丟失的問題。當然,這種做法的潛在問題時,如果在拍下一張快照前,跑 Redis 的機器掛掉,那就沒辦法把資料寫到 rdb 檔案,因此會有該間隔資料丟失的可能。
而另一種做法叫 AOF,這種法會記錄每一個對 Redis 的寫入請求 (write operation),然後把記錄存在硬碟。這種做法就能讓 Redis 如果機器掛掉,開發者能夠透過記錄去還原資料。而這種做法,因為要記錄每一個寫的請求,需要佔的硬碟空間大,同時如果要還原也比較花時間。
要選哪一種做法,需要看面對的情境。假如能容忍些微的資料丟失,那可以考慮選 RDB,但假如不能接受任何丟失,則選 AOF (但要承擔較大的硬碟空間、還原時間等成本)。又或者可以視需求,考慮採用兩種混合搭配的模式。
Redis 其他常見用途與追問的問題
除了上面提到的兩種用途外,Redis 也有許多其他在系統設計中常會用到的地方。除此之外,在系統設計中,當提到 Redis 時,經常會有不同的追問題,例如 Redis 做為快取如何擴展到全球多區域、Redis 存記憶體那該如何做到資料持久 (persistence)、Redis 是鍵值對那該如何處理熱鍵問題 (hot key)
上面這些我們都有在 E+ 的主題文中進一步討論。有興趣一讀的人,歡迎加入 E+ (詳細介紹見此)