圖解 Redis | AOF 持久化

      網友投稿 906 2025-04-01

      AOF 日志


      試想一下,如果 Redis 每執行一條寫操作命令,就把該命令以追加的方式寫入到一個文件里,然后重啟 Redis 的時候,先去讀取這個文件里的命令,并且執行它,這不就相當于恢復了緩存數據了嗎?

      這種保存寫操作命令到日志的持久化方式,就是 Redis 里的 AOF(Append Only File) 持久化功能,注意只會記錄寫操作命令,讀操作命令是不會被記錄的,因為沒意義。

      在 Redis 中 AOF 持久化功能默認是不開啟的,需要我們修改 redis.conf 配置文件中的以下參數:

      AOF 日志文件其實就是普通的文本,我們可以通過 cat 命令查看里面的內容,不過里面的內容如果不知道一定的規則的話,可能會看不懂。

      我這里以「set name xiaolin」命令作為例子,Redis 執行了這條命令后,記錄在 AOF 日志里的內容如下圖:

      我這里給大家解釋下。

      「*3」表示當前命令有三個部分,每部分都是以「$+數字」開頭,后面緊跟著具體的命令、鍵或值。然后,這里的「數字」表示這部分中的命令、鍵或值一共有多少字節。例如,「 set」表示這部分有 3 個字節,也就是「set」命令這個字符串的長度。

      不知道大家注意到沒有,Redis 是先執行寫操作命令后,才將該命令記錄到 AOF 日志里的,這么做其實有兩個好處。

      第一個好處,避免額外的檢查開銷。

      因為如果先將寫操作命令記錄到 AOF 日志里,再執行該命令的話,如果當前的命令語法有問題,那么如果不進行命令語法檢查,該錯誤的命令記錄到 AOF 日志里后,Redis 在使用日志恢復數據時,就可能會出錯。

      而如果先執行寫操作命令再記錄日志的話,只有在該命令執行成功后,才將命令記錄到 AOF 日志里,這樣就不用額外的檢查開銷,保證記錄在 AOF 日志里的命令都是可執行并且正確的。

      第二個好處,不會阻塞當前寫操作命令的執行,因為當寫操作命令執行成功后,才會將命令記錄到 AOF 日志。

      當然,AOF 持久化功能也不是沒有潛在風險。

      第一個風險,執行寫操作命令和記錄日志是兩個過程,那當 Redis 在還沒來得及將命令寫入到硬盤時,服務器發生宕機了,這個數據就會有丟失的風險。

      第二個風險,前面說道,由于寫操作命令執行成功后才記錄到 AOF 日志,所以不會阻塞當前寫操作命令的執行,但是可能會給「下一個」命令帶來阻塞風險。

      因為將命令寫入到日志的這個操作也是在主進程完成的(執行命令也是在主進程),也就是說這兩個操作是同步的。

      如果在將日志內容寫入到硬盤時,服務器的硬盤的 I/O 壓力太大,就會導致寫硬盤的速度很慢,進而阻塞住了,也就會導致后續的命令無法執行。

      認真分析一下,其實這兩個風險都有一個共性,都跟「 AOF 日志寫回硬盤的時機」有關。

      三種寫回策略

      Redis 寫入 AOF 日志的過程,如下圖:

      我先來具體說說:

      Redis 執行完寫操作命令后,會將命令追加到 server.aof_buf 緩沖區;

      然后通過 write() 系統調用,將 aof_buf 緩沖區的數據寫入到 AOF 文件,此時數據并沒有寫入到硬盤,而是拷貝到了內核緩沖區 page cache,等待內核將數據寫入硬盤;

      具體內核緩沖區的數據什么時候寫入到硬盤,由內核決定。

      Redis 提供了 3 種寫回硬盤的策略,控制的就是上面說的第三步的過程。

      在 redis.conf 配置文件中的 appendfsync 配置項可以有以下 3 種參數可填:

      Always,這個單詞的意思是「總是」,所以它的意思是每次寫操作命令執行完后,同步將 AOF 日志數據寫回硬盤;

      Everysec,這個單詞的意思是「每秒」,所以它的意思是每次寫操作命令執行完后,先將命令寫入到 AOF 文件的內核緩沖區,然后每隔一秒將緩沖區里的內容寫回到硬盤;

      No,意味著不由 Redis 控制寫回硬盤的時機,轉交給操作系統控制寫回的時機,也就是每次寫操作命令執行完后,先將命令寫入到 AOF 文件的內核緩沖區,再由操作系統決定何時將緩沖區內容寫回硬盤。

      這 3 種寫回策略都無法能完美解決「主進程阻塞」和「減少數據丟失」的問題,因為兩個問題是對立的,偏向于一邊的話,就會要犧牲另外一邊,原因如下:

      Always 策略的話,可以最大程度保證數據不丟失,但是由于它每執行一條寫操作命令就同步將 AOF 內容寫回硬盤,所以是不可避免會影響主進程的性能;

      No 策略的話,是交由操作系統來決定何時將 AOF 日志內容寫回硬盤,相比于 Always 策略性能較好,但是操作系統寫回硬盤的時機是不可預知的,如果 AOF 日志內容沒有寫回硬盤,一旦服務器宕機,就會丟失不定數量的數據。

      Everysec 策略的話,是折中的一種方式,避免了 Always 策略的性能開銷,也比 No 策略更能避免數據丟失,當然如果上一秒的寫操作命令日志沒有寫回到硬盤,發生了宕機,這一秒內的數據自然也會丟失。

      大家根據自己的業務場景進行選擇:

      如果要高性能,就選擇 No 策略;

      如果要高可靠,就選擇 Always 策略;

      如果允許數據丟失一點,但又想性能高,就選擇 Everysec 策略。

      我也把這 3 個寫回策略的優缺點總結成了一張表格:

      大家知道這三種策略是怎么實現的嗎?

      深入到源碼后,你就會發現這三種策略只是在控制 fsync() 函數的調用時機。

      當應用程序向文件寫入數據時,內核通常先將數據復制到內核緩沖區中,然后排入隊列,然后由內核決定何時寫入硬盤。

      如果想要應用程序向文件寫入數據后,能立馬將數據同步到硬盤,就可以調用 fsync() 函數,這樣內核就會將內核緩沖區的數據直接寫入到硬盤,等到硬盤寫操作完成后,該函數才會返回。

      Always 策略就是每次寫入 AOF 文件數據后,就執行 fsync() 函數;

      Everysec 策略就會創建一個異步任務來執行 fsync() 函數;

      No 策略就是永不執行 fsync() 函數;

      AOF 重寫機制

      AOF 日志是一個文件,隨著執行的寫操作命令越來越多,文件的大小會越來越大。

      如果當 AOF 日志文件過大就會帶來性能問題,比如重啟 Redis 后,需要讀 AOF 文件的內容以恢復數據,如果文件過大,整個恢復的過程就會很慢。

      所以,Redis 為了避免 AOF 文件越寫越大,提供了 AOF 重寫機制,當 AOF 文件的大小超過所設定的閾值后,Redis 就會啟用 AOF 重寫機制,來壓縮 AOF 文件。

      AOF 重寫機制是在重寫時,讀取當前數據庫中的所有鍵值對,然后將每一個鍵值對用一條命令記錄到「新的 AOF 文件」,等到全部記錄完后,就將新的 AOF 文件替換掉現有的 AOF 文件。

      舉個例子,在沒有使用重寫機制前,假設前后執行了「set name xiaolin」和「set name xiaolincoding」這兩個命令的話,就會將這兩個命令記錄到 AOF 文件。

      但是在使用重寫機制后,就會讀取 name 最新的 value(鍵值對) ,然后用一條 「set name xiaolincoding」命令記錄到新的 AOF 文件,之前的第一個命令就沒有必要記錄了,因為它屬于「歷史」命令,沒有作用了。這樣一來,一個鍵值對在重寫日志中只用一條命令就行了。

      重寫工作完成后,就會將新的 AOF 文件覆蓋現有的 AOF 文件,這就相當于壓縮了 AOF 文件,使得 AOF 文件體積變小了。

      然后,在通過 AOF 日志恢復數據時,只用執行這條命令,就可以直接完成這個鍵值對的寫入了。

      所以,重寫機制的妙處在于,盡管某個鍵值對被多條寫命令反復修改,最終也只需要根據這個「鍵值對」當前的最新狀態,然后用一條命令去記錄鍵值對,代替之前記錄這個鍵值對的多條命令,這樣就減少了 AOF 文件中的命令數量。最后在重寫工作完成后,將新的 AOF 文件覆蓋現有的 AOF 文件。

      這里說一下為什么重寫 AOF 的時候,不直接復用現有的 AOF 文件,而是先寫到新的 AOF 文件再覆蓋過去。

      因為如果 AOF 重寫過程中失敗了,現有的 AOF 文件就會造成污染,可能無法用于恢復使用。

      所以 AOF 重寫過程,先重寫到新的 AOF 文件,重寫失敗的話,就直接刪除這個文件就好,不會對現有的 AOF 文件造成影響。

      AOF 后臺重寫

      寫入 AOF 日志的操作雖然是在主進程完成的,因為它寫入的內容不多,所以一般不太影響命令的操作。

      但是在觸發 AOF 重寫時,比如當 AOF 文件大于 64M 時,就會對 AOF 文件進行重寫,這時是需要讀取所有緩存的鍵值對數據,并為每個鍵值對生成一條命令,然后將其寫入到新的 AOF 文件,重寫完后,就把現在的 AOF 文件替換掉。

      這個過程其實是很耗時的,所以重寫的操作不能放在主進程里。

      所以,Redis 的重寫 AOF 過程是由后臺子進程 bgrewriteaof 來完成的,這么做可以達到兩個好處:

      子進程進行 AOF 重寫期間,主進程可以繼續處理命令請求,從而避免阻塞主進程;

      子進程帶有主進程的數據副本(數據副本怎么產生的后面會說),這里使用子進程而不是線程,因為如果是使用線程,多線程之間會共享內存,那么在修改共享內存數據的時候,需要通過加鎖來保證數據的安全,而這樣就會降低性能。而使用子進程,創建子進程時,父子進程是共享內存數據的,不過這個共享的內存只能以只讀的方式,而當父子進程任意一方修改了該共享內存,就會發生「寫時復制」,于是父子進程就有了獨立的數據副本,就不用加鎖來保證數據安全。

      子進程是怎么擁有主進程一樣的數據副本的呢?

      主進程在通過 fork 系統調用生成 bgrewriteaof 子進程時,操作系統會把主進程的「頁表」復制一份給子進程,這個頁表記錄著虛擬地址和物理地址映射關系,而不會復制物理內存,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。

      這樣一來,子進程就共享了父進程的物理內存數據了,這樣能夠節約物理內存資源,頁表對應的頁表項的屬性會標記該物理內存的權限為只讀。

      不過,當父進程或者子進程在向這個內存發起寫操作時,CPU 就會觸發缺頁中斷,這個缺頁中斷是由于違反權限導致的,然后操作系統會在「缺頁異常處理函數」里進行物理內存的復制,并重新設置其內存映射關系,將父子進程的內存讀寫權限設置為可讀寫,最后才會對內存進行寫操作,這個過程被稱為「寫時復制(Copy On Write)」。

      寫時復制顧名思義,在發生寫操作的時候,操作系統才會去復制物理內存,這樣是為了防止 fork 創建子進程時,由于物理內存數據的復制時間過長而導致父進程長時間阻塞的問題。

      當然,操作系統復制父進程頁表的時候,父進程也是阻塞中的,不過頁表的大小相比實際的物理內存小很多,所以通常復制頁表的過程是比較快的。

      圖解 Redis | AOF 持久化

      不過,如果父進程的內存數據非常大,那自然頁表也會很大,這時父進程在通過 fork 創建子進程的時候,阻塞的時間也越久。

      所以,有兩個階段會導致阻塞父進程:

      創建子進程的途中,由于要復制父進程的頁表等數據結構,阻塞的時間跟頁表的大小有關,頁表越大,阻塞的時間也越長;

      創建完子進程后,如果子進程或者父進程修改了共享數據,就會發生寫時復制,這期間會拷貝物理內存,如果內存越大,自然阻塞的時間也越長;

      觸發重寫機制后,主進程就會創建重寫 AOF 的子進程,此時父子進程共享物理內存,重寫子進程只會對這個內存進行只讀,重寫 AOF 子進程會讀取數據庫里的所有數據,并逐一把內存數據的鍵值對轉換成一條命令,再將命令記錄到重寫日志(新的 AOF 文件)。

      但是子進程重寫過程中,主進程依然可以正常處理命令。

      如果此時主進程修改了已經存在 key-value,就會發生寫時復制,注意這里只會復制主進程修改的物理內存數據,沒修改物理內存還是與子進程共享的。

      所以如果這個階段修改的是一個 bigkey,也就是數據量比較大的 key-value 的時候,這時復制的物理內存數據的過程就會比較耗時,有阻塞主進程的風險。

      還有個問題,重寫 AOF 日志過程中,如果主進程修改了已經存在 key-value,此時這個 key-value 數據在子進程的內存數據就跟主進程的內存數據不一致了,這時要怎么辦呢?

      為了解決這種數據不一致問題,Redis 設置了一個 AOF 重寫緩沖區,這個緩沖區在創建 bgrewriteaof 子進程之后開始使用。

      在重寫 AOF 期間,當 Redis 執行完一個寫命令之后,它會同時將這個寫命令寫入到 「AOF 緩沖區」和 「AOF 重寫緩沖區」。

      也就是說,在 bgrewriteaof 子進程執行 AOF 重寫期間,主進程需要執行以下三個工作:

      執行客戶端發來的命令;

      將執行后的寫命令追加到 「AOF 緩沖區」;

      將執行后的寫命令追加到 「AOF 重寫緩沖區」;

      當子進程完成 AOF 重寫工作(掃描數據庫中所有數據,逐一把內存數據的鍵值對轉換成一條命令,再將命令記錄到重寫日志)后,會向主進程發送一條信號,信號是進程間通訊的一種方式,且是異步的。

      主進程收到該信號后,會調用一個信號處理函數,該函數主要做以下工作:

      將 AOF 重寫緩沖區中的所有內容追加到新的 AOF 的文件中,使得新舊兩個 AOF 文件所保存的數據庫狀態一致;

      新的 AOF 的文件進行改名,覆蓋現有的 AOF 文件。

      信號函數執行完后,主進程就可以繼續像往常一樣處理命令了。

      在整個 AOF 后臺重寫過程中,除了發生寫時復制會對主進程造成阻塞,還有信號處理函數執行時也會對主進程造成阻塞,在其他時候,AOF 后臺重寫都不會阻塞主進程。

      總結

      這次小林給大家介紹了 Redis 持久化技術中的 AOF 方法,這個方法是每執行一條寫操作命令,就將該命令以追加的方式寫入到 AOF 文件,然后在恢復時,以逐一執行命令的方式來進行數據恢復。

      Redis 提供了三種將 AOF 日志寫回硬盤的策略,分別是 Always、Everysec 和 No,這三種策略在可靠性上是從高到低,而在性能上則是從低到高。

      隨著執行的命令越多,AOF 文件的體積自然也會越來越大,為了避免日志文件過大, Redis 提供了 AOF 重寫機制,它會直接掃描數據中所有的鍵值對數據,然后為每一個鍵值對生成一條寫操作命令,接著將該命令寫入到新的 AOF 文件,重寫完成后,就替換掉現有的 AOF 日志。重寫的過程是由后臺子進程完成的,這樣可以使得主進程可以繼續正常處理命令。

      用 AOF 日志的方式來恢復數據其實是很慢的,因為 Redis 執行命令由單線程負責的,而 AOF 日志恢復數據的方式是順序執行日志里的每一條命令,如果 AOF 日志很大,這個「重放」的過程就會很慢了。

      《Redis設計與實現》

      《Redis核心技術與實戰-極客時間》

      《Redis源碼分析》

      Redis

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:表格豎列怎么使用同個公司
      下一篇:wps怎么合并兩個pdf文件?
      相關文章
      日本红怡院亚洲红怡院最新| 日韩欧美亚洲国产精品字幕久久久| 午夜在线亚洲男人午在线| 91午夜精品亚洲一区二区三区| 亚洲成a人片77777kkkk| 亚洲人成色7777在线观看不卡| 亚洲精品美女久久久久久久| 亚洲伦理中文字幕| 亚洲美女人黄网成人女| 亚洲图片一区二区| 久久精品亚洲综合一品| 亚洲AV日韩精品久久久久久久| 亚洲国产AV无码专区亚洲AV| 亚洲乱码国产乱码精品精| 国产成人A人亚洲精品无码| 国产精品国产亚洲精品看不卡| 亚洲爆乳无码一区二区三区| 亚洲国产精华液网站w| 亚洲AV无码乱码国产麻豆穿越| 亚洲av无码精品网站| 色播亚洲视频在线观看| 亚洲高清中文字幕综合网| 亚洲精品影院久久久久久| 亚洲视频一区在线| 亚洲av乱码一区二区三区香蕉| 亚洲丝袜中文字幕| 亚洲一本一道一区二区三区| 亚洲中文字幕一二三四区| 亚洲精品无码国产片| 亚洲中文字幕久久久一区| 亚洲国产一区在线观看| 亚洲国产精品免费在线观看| 亚洲娇小性xxxx色| 亚洲一区二区三区乱码在线欧洲| 亚洲一区二区三区精品视频| tom影院亚洲国产一区二区| 亚洲人成电影网站久久| 亚洲高清在线mv| 亚洲美女视频一区| 亚洲成熟xxxxx电影| 亚洲精品成人片在线播放|