簡介
Redis 是使用非常廣泛的 Key-Value 記憶體資料庫。因為資料都存放在記憶體中,所以存取速度非常快。不過,很多情況下我們需要將 Redis 中的資料儲存到硬碟中以便做備份。Redis 提供了兩種資料持久化方式,分別是 RDB 和 AOP,本文分析這兩種方式的使用以及過期鍵對持久化的影響。
RDB
RDB 指的是將 Redis 資料庫在某個時間點的快照儲存到磁碟,所生成的 RDB 檔案是一個經過壓縮的二進位制檔案,通過這個檔案可以還原出 Redis 的資料狀態。
建立快照的方式有以下幾種:
- 客戶端向 Redis 傳送
BGSAVE
命令,Redis 會呼叫 fork 建立一個子程式,然後子程式負責將快照寫入硬碟,而父程式繼續處理命令請求。 - 客戶端向 Redis 傳送
SAVE
命令,此時 Redis 將開始建立快照,並且在完成之前不再響應其它命令。 - 使用者設定 save 配置選項,比如
save 60 10000
,那麼從 Redis 最近一次建立快照算起,當 “60 秒內有 10000 次寫入” 這個條件被滿足時, Redis 就會自動觸發BGSAVE
命令。如果使用者設定了多個 save 配置選項,那麼當任意一個 save 配置滿足時,Redis 就會觸發一次BGSAVE
命令。save 配置的格式如下所示:
save 60 10000
stop-writes-on-bgsave-error no
rdbcompression yes // 使用壓縮
dbfilename dump.rdb // RBD 檔案的名字
dir ./
- 當 Redis 通過
SHUTDOWN
命令接收到關閉伺服器的請求時,或者接收到 TERM命令時,會執行一個SAVE
命令,並且阻塞所有的客戶端,不再執行任何請求。在SAVE
命令執行結束後關閉伺服器。 - 當一個 Redis 伺服器連線另一個 Redis 伺服器,並向對方傳送
SYNC
命令來開始一次複製操作的時候,如果主伺服器沒有在執行BGSAVE
操作,或者主伺服器並非剛執行完BGSAVE
,那麼主伺服器會執行BGSAVE
命令。
RDB 的主要問題是,如果系統發生崩潰,那麼最近一次執行完快照後修改的資料將被丟失。因此,RDB 適合用於即使丟失一部分資料也不會造成影響的應用程式。
AOF
AOF 指的是將所有執行的寫命令寫到 AOF 檔案的末尾,以此來記錄資料發生的變化。如果 Redis 想要恢復 AOF 中的資料,只要重新執行一次 AOF 檔案中所包含的寫命令就可以。
AOF 的配置如下所示:
appendonly yes // 開啟 aof
appendfsync everysec // aof 同步的頻率
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100 // 檔案大小增長比超過這個值開始自動重寫 aof
auto-aof-rewrite-min-size 64mb // 檔案大小超過這個值才可以有可能自動重寫 aof
上面的配置中,appendfsync everysec
設定的是同步的頻率。應用程式在向硬碟寫入資料的時候有 3 個步驟:
- 呼叫
file.write()
向檔案寫入,此時需要寫入的內容被儲存到了緩衝區,並不是真正寫到硬碟上了。 - 作業系統在某個時候將緩衝區的內容寫入硬碟,這時資料才真正被持久化了。
作業系統使用以上的檔案寫入方式是為了提高效能,畢竟硬碟 I/O 操作是比較耗時的。但是,這種方式的缺點在於如果機器崩潰了那麼緩衝區的內容將丟失。程式可以使用 file.flush()
來請求作業系統儘快地將緩衝區的內容重新整理到硬碟上,不過何時開始執行仍然由作業系統決定。程式也可以命令作業系統將檔案同步 ( sync ) 到硬碟,同步操作會阻塞應用程式直到資料被寫入硬碟。當同步操作完成後,即使系統出現故障,也不會對被同步的檔案造成影響。
對於 Redis 來講,可以指定 appendfsync
以何種方式讓資料完全同步到硬碟,這個配置有 3 個選項:
- always: 每個 Redis 寫命令都立即同步到硬碟,這是比較消耗效能的
- everysec: 每秒執行一次同步,兼顧效能與資料安全,是比較常用的選項
- no: 讓作業系統決定何時進行同步
always
可以使得在 Redis 發生崩潰時丟失的資料最少,但是也是最消耗效能的,導致 Redis 的處理速度變慢。ererysec
是一種兼顧效能與資料安全的方式,在這種情況下,如果系統崩潰,使用者最多會丟失一秒內的資料。no
選項完全將同步交給作業系統被決定,效能也不比 everysec
高多少,是不推薦的方式。
AOF 的缺點是隨著 Redis 的不斷執行,AOF 檔案可能會非常大,甚至用完硬碟的空間。解決這個問題的辦法是 AOF 重寫。
重寫
客戶端可以傳送 BGREWRITEAOF
命令讓 Redis 重寫 AOF 檔案,Redis 會移除冗餘的 AOF 命令進行重寫,使得 AOF 檔案的體積儘可能地小。
除了客戶端主動傳送 BGREWRITEAOF
命令,也可以使用配置讓 Redis 在滿足一定條件地情況下自動開始重寫 AOF 檔案。例如上一小節設定了 auto-aof-rewrite-percentage 100
和 auto-aof-rewrite-min-size 64mb
。這兩個配置的含義是,如果 AOF 檔案大於 64MB 並且比上一次重寫之後的大小增加了一倍的時候,Redis 將執行 BGREWERITEAOF
命令。
過期鍵刪除
使用者往往為 Redis 中的鍵設定過期時間,因此需要一定的策略來刪除過期鍵,可以有三種策略:
- 定時刪除,即通過定時器在過期時間到達的時候刪除過期的鍵。這種方式的優點是節省記憶體,不會因為大量的過期鍵佔用記憶體資源,而缺點則是消耗 CPU 資源,尤其是過期鍵數量較多的時候,刪除操作消耗太長時間,降低了 Redis 的響應時間。
- 惰性刪除,即在每次獲取某個鍵的時候判斷是否過期,如果未過期,則正常返回其值,否則刪除這個鍵,返回空。這種方式的優點是節省 CPU 資源,但是消耗了記憶體。尤其是過期鍵數量較多的時候,大量記憶體被無效的鍵佔用,相當於記憶體洩露。
- 定期刪除,即每隔一段時間週期對資料庫中的鍵進行掃描,但是隻掃描其中一部分,力求在記憶體和 CPU 之間達到一個平衡。
從上面 3 種策略可以看出,單用第一個肯定是不行的,Redis 的響應時間至關重要。第二個則是比較好的方式,在獲取鍵的時候判斷是否過期並決定是否刪除,它的缺點是很多鍵無法及時刪除。如果一個過期鍵再也沒有被訪問,那麼它將永遠留在記憶體中,而第三種方式正好可以彌補。
Redis 中過期鍵的刪除策略正是惰性刪除與定期刪除的結合。
過期鍵與持久化
瞭解了過期鍵的刪除策略後,下面看下鍵的過期時間對持久化的影響。
在生成 RDB 檔案的過程中,如果一個鍵已經過期,那麼其不會被儲存到 RDB 檔案中。在載入 RDB 的時候,要分兩種情況:
- 如果 Redis 以主伺服器的模式執行,那麼會對 RDB 中的鍵進行時間檢查,過期的鍵不會被恢復到 Redis 中。
- 如果 Redis 以從伺服器的模式執行,那麼 RDB 中所有的鍵都會被載入,忽略時間檢查。在從伺服器與主伺服器進行資料同步的時候,從伺服器的資料會先被清空,所以載入過期鍵不會有問題。
對於 AOF 來說,如果一個鍵過期了,那麼不會立刻對 AOF 檔案造成影響。因為 Redis 使用的是惰性刪除和定期刪除,只有這個鍵被刪除了,才會往 AOF 檔案中追加一條 DEL 命令。在重寫 AOF 的過程中,程式會檢查資料庫中的鍵,已經過期的鍵不會被儲存到 AOF 檔案中。
在執行過程中,對於主從複製的 Redis,主伺服器和從伺服器對於過期鍵的處理也不相同:
- 對於主伺服器,一個過期的鍵被刪除了後,會向從伺服器傳送 DEL 命令,通知從伺服器刪除對應的鍵
- 從伺服器接收到讀取一個鍵的命令時,即使這個鍵已經過期,也不會刪除,而是照常處理這個命令。
- 從伺服器接收到主伺服器的 DEL 命令後,才會刪除對應的過期鍵。
這麼做的主要目的是保證資料一致性,所以當一個過期鍵存在於主伺服器時,也必然存在於從伺服器。
總結
本文對 Redis 的兩種持久化方式進行了簡要的梳理,分析了 Redis 刪除過期鍵的策略以及對持久化的影響。理解了這部分內容不僅可以讓我們對 Redis 的使用更加得心應手,對於學習 Redis 的其它內容如複製的過程也會很有幫助。
參考
- 《Redis 實戰》
- 《Redis 設計與實現》
如果我的文章對您有幫助,不妨點個贊支援一下(^_^)