Redis是內存數據庫,但是一旦服務器宕機,內存中的數據將會全部丟失。
最簡單的恢復方式是從后端數據庫恢復,但這種方式有兩個問題:
- 頻繁訪問數據庫,會給數據庫帶來巨大的壓力;
- 從數據庫中讀取相比從Redis中讀取要慢很多,會導致應用響應變慢
因此,Redis要實現持久化,避免從后端數據庫中進行恢復。
Redis有兩種持久化機制:AOF(Append Only File)日志和RDB快照。今天先來學習AOF日志。
什么是AOF日志?
AOF日志是通過保存Redis寫命令來記錄數據庫數據的。大多數的數據庫采用的是寫前日志(WAL),例如MySQL,通過寫前日志和兩階段提交,實現數據和邏輯的一致性。想了解更多關於兩階段提交的內容,點擊查看。
而AOF日志采用寫后日志,即先寫內存,后寫日志。
為什么采用寫后日志?
Redis要求高性能,采用寫日志有兩方面好處:
- 避免額外的檢查開銷
- 不會阻塞當前的寫操作
但這種方式存在潛在風險:
- 如果命令執行完成,寫日志之前宕機了,會丟失數據。
- 主線程寫磁盤壓力大,導致寫盤慢,阻塞后續操作。
如何實現AOF日志?
AOF日志記錄Redis的每個寫命令,步驟分為:命令追加(append)、文件寫入(write)和文件同步(sync)。
命令追加
當AOF持久化功能打開了,服務器在執行完一個寫命令之后,會以協議格式將被執行的寫命令追加到服務器的 aof_buf
緩沖區。
文件寫入和同步
關於何時將 aof_buf
緩沖區的內容寫入AOF文件中,Redis提供了三種寫回策略:
Always,同步寫回
:每個寫命令執行完,立馬同步地將日志寫回磁盤;Everysec,每秒寫回
:每個寫命令執行完,只是先把日志寫到AOF文件的內存緩沖區,每隔一秒把緩沖區中的內容寫入磁盤;No,操作系統控制的寫回
:每個寫命令執行完,只是先把日志寫到AOF文件的內存緩沖區,由操作系統決定何時將緩沖區內容寫回磁盤。
上面的三種寫回策略體現了一個重要原則:trade-off,取舍,指在性能和可靠性保證之間做取舍。
關於AOF的同步策略是涉及到操作系統的 write
函數和 fsync
函數的,在《Redis設計與實現》中是這樣說明的:
為了提高文件寫入效率,在現代操作系統中,當用戶調用
write
函數,將一些數據寫入文件時,操作系統通常會將數據暫存到一個內存緩沖區里,當緩沖區的空間被填滿或超過了指定時限后,才真正將緩沖區的數據寫入到磁盤里。這樣的操作雖然提高了效率,但也為數據寫入帶來了安全問題:如果計算機停機,內存緩沖區中的數據會丟失。為此,系統提供了
fsync
、fdatasync
同步函數,可以強制操作系統立刻將緩沖區中的數據寫入到硬盤里,從而確保寫入數據的安全性。
如何配置AOF?
默認情況下,Redis是沒有開啟AOF的,可以通過配置redis.conf文件來開啟AOF持久化,關於AOF的配置如下:
# appendonly參數開啟AOF持久化 appendonly no # AOF持久化的文件名,默認是appendonly.aof appendfilename "appendonly.aof" # AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數設置的 dir ./ # 同步策略 # appendfsync always appendfsync everysec # appendfsync no # aof重寫期間是否同步 no-appendfsync-on-rewrite no # 重寫觸發配置 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 加載aof出錯如何處理 aof-load-truncated yes # 文件重寫策略 aof-rewrite-incremental-fsync yes
AOF重寫
AOF會記錄每個寫命令到AOF文件,隨着時間越來越長,AOF文件會變得越來越大。如果不加以控制,會對Redis服務器,甚至對操作系統造成影響,而且AOF文件越大,數據恢復也越慢。
為了解決AOF文件體積膨脹的問題,Redis提供AOF文件重寫功能來對AOF文件進行“瘦身”。Redis通過創建一個新的AOF文件來替換現有的AOF,新舊兩個AOF文件保存的數據相同,但新AOF文件沒有了冗余命令。
簡單來說,AOF重寫就是把舊AOF日志文件的多條命令,在重寫后變成新日志文件的一條命令。
AOF重寫會阻塞嗎?AOF重寫是由后台線程bgrewriteaof來完成的。
AOF重寫過程
用一句話總結:一個拷貝,兩處日志。一個拷貝指一份內存拷貝,兩處日志分別是一處正在使用的AOF日志,另一處是新的AOF重寫日志。
下圖是AOF重寫過程:
拓展
關於AOF重寫過程的潛在阻塞風險
前面提到AOF重寫不會阻塞,指的是在AOF重寫過程不會阻塞主線程,因為是通過后台bgrewriteaof線程來執行的。
但是在fork子進程的時候,fork這個瞬間一定是會阻塞主線程的。
fork采用的是操作系統提供的寫時復制(Copy On Write)機制,避免一次性拷貝造成的阻塞。但fork子進程需要拷貝進程必要的數據結構,其中有一項是拷貝內存頁表(虛擬內存和物理內存的映射索引表),這個拷貝過程會消耗大量的CPU資源,在拷貝完成之前,整個進程是會阻塞的。
拷貝內存頁完成后,子進程與父進程指向相同的內存地址空間,也就是說此時雖然產生了子進程,但是並沒有申請與父進程相同的內存大小。
那什么時候父子進程才會真正內存分離呢?在寫發生時,才真正拷貝內存的數據,這個過程中,父進程也可能會產生阻塞風險。
因為內存分配是以頁為單位進行分配的,默認4K,如果父進程此時操作的是一個bigkey,重新申請大塊內存耗時會變長,可能會產生阻塞風險。
另外,如果操作系統開啟了內存大頁機制(Huge Page,頁面大小2M),那么父進程申請內存時阻塞的概率將會大大提高,所以在Redis機器上需要關閉Huge Page機制。
為什么AOF重寫不復用原AOF日志
有兩方面原因:
- 父子進程寫同一個文件會產生競爭問題,影響父進程的性能。
- 如果AOF重寫過程中失敗了,相當於污染了原本的AOF文件,無法做恢復數據使用。
AOF重寫需要手動觸發嗎?
可以設置自動觸發,通過配置這兩個參數auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
:
auto-aof-rewrite-min-size
:表示運行AOF重寫時文件的最小大小,默認為64MBauto-aof-rewrite-percentage
:當前AOF文件大小和上一次重寫后AOF文件大小的差值,再除以上一次重寫后AOF文件大小
當AOF文件大小同時超出上面兩個配置項,會觸發AOF重寫。