天天看點

解密Redis持久化

寫操作的流程

首先我們來看一下資料庫在進行寫操作時到底做了哪些事,主要有下面五個過程。

  1. 用戶端向服務端發送寫操作(資料在用戶端的記憶體中)
  2. 資料庫服務端接收到寫請求的資料(資料在服務端的記憶體中)
  3. 服務端調用write(2) 這個系統調用,将資料往磁盤上寫(資料在系統記憶體的緩沖區中)
  4. 作業系統将緩沖區中的資料轉移到磁盤控制器上(資料在磁盤緩存中)
  5. 磁盤控制器将資料寫到磁盤的實體媒體中(資料真正落到磁盤上)

故障分析上面的5個流程看一下各種級别的故障。

  • 當資料庫系統故障時,這時候系統核心還是OK的,那麼此時隻要我們執行完了第3步,那麼資料就是安全的,因為後續作業系統會來完成後面幾步,保證資料最終會落到磁盤上。
  • 當系統斷電,這時候上面5項中提到的所有緩存都
  • 寫操作大緻有上面5個流程,下面我們結合會失效,并且資料庫和作業系統都會停止工作。是以隻有當資料在完成第5步後,機器斷電才能保證資料不丢失,在上述四步中的資料都會丢失。

通過上面5步的了解,可能我們會希望搞清下面一些問題:

  • 資料庫多長時間調用一次write(2),将資料寫到核心緩沖區
  • 核心多長時間會将系統緩沖區中的資料寫到磁盤控制器
  • 磁盤控制器又在什麼時候把緩存中的資料寫到實體媒體上

對于第一個問題,通常資料庫層面會進行全面控制。而對第二個問題,作業系統有其預設的政策,但是我們也可以通過POSIX API提供的fsync系列指令強制作業系統将資料從核心區寫到磁盤控制器上。對于第三個問題,好像資料庫已經無法觸及,但實際上,大多數情況下磁盤緩存是被設定關閉的。或者是隻開啟為讀緩存,也就是寫操作不會進行緩存,直接寫到磁盤。建議的做法是僅僅當你的磁盤裝置有備用電池時才開啟寫緩存。

資料損壞

所謂資料損壞,就是資料無法恢複,上面我們講的都是如何保證資料是确實寫到磁盤上去,但是寫到磁盤上可能并不意味着資料不會損壞。比如我們可能一次寫請求會進行兩次不同的寫操作,當意外發生時,可能會導緻一次寫操作安全完成,但是另一次還沒有進行。如果資料庫的資料檔案結構組織不合理,可能就會導緻資料完全不能恢複的狀況出現。

這裡通常也有三種政策來組織資料,以防止資料檔案損壞到無法恢複的情況:

  • 第一種是最粗糙的處理,就是不通過資料的組織形式保證資料的可恢複性。而是通過配置資料同步備份的方式,在資料檔案損壞後通過資料備份來進行恢複。實際上MongoDB在不開啟journaling日志,通過配置Replica Sets時就是這種情況。
  • 另一種是在上面基礎上添加一個記錄檔,每次操作時記一下操作的行為,這樣我們可以通過記錄檔來進行資料恢複。因為記錄檔是順序追加的方式寫的,是以不會出現記錄檔也無法恢複的情況。這也類似于MongoDB開啟了journaling日志的情況。
  • 更保險的做法是資料庫不進行老資料的修改,隻是以追加方式去完成寫操作,這樣資料本身就是一份日志,這樣就永遠不會出現資料無法恢複的情況了。實際上CouchDB就是此做法的優秀範例。

RDB快照

下面我們說一下Redis的第一個持久化政策,RDB快照。Redis支援将目前資料的快照存成一個資料檔案的持久化機制。而一個持續寫入的資料庫如何生成快照呢。Redis借助了fork指令的copy on write機制。在生成快照時,将目前程序fork出一個子程序,然後在子程序中循環所有的資料,将資料寫成為RDB檔案。

我們可以通過Redis的save指令來配置RDB快照生成的時機,比如你可以配置當10分鐘以内有100次寫入就生成快照,也可以配置當1小時内有1000次寫入就生成快照,也可以多個規則一起實施。這些規則的定義就在Redis的配置檔案中,你也可以通過Redis的CONFIG SET指令在Redis運作時設定規則,不需要重新開機Redis。

Redis的RDB檔案不會壞掉,因為其寫操作是在一個新程序中進行的,當生成一個新的RDB檔案時,Redis生成的子程序會先将資料寫到一個臨時檔案中,然後通過原子性rename系統調用将臨時檔案重命名為RDB檔案,這樣在任何時候出現故障,Redis的RDB檔案都總是可用的。

同時,Redis的RDB檔案也是Redis主從同步内部實作中的一環。

但是,我們可以很明顯的看到,RDB有他的不足,就是一旦資料庫出現問題,那麼我們的RDB檔案中儲存的資料并不是全新的,從上次RDB檔案生成到Redis停機這段時間的資料全部丢掉了。在某些業務下,這是可以忍受的,我們也推薦這些業務使用RDB的方式進行持久化,因為開啟RDB的代價并不高。但是對于另外一些對資料安全性要求極高的應用,無法容忍資料丢失的應用,RDB就無能為力了,是以Redis引入了另一個重要的持久化機制:AOF日志。

AOF日志

aof日志的全稱是append only file,從名字上我們就能看出來,它是一個追加寫入的日志檔案。與一般資料庫的binlog不同的是,AOF檔案是可識别的純文字,它的内容就是一個個的Redis标準指令。比如我們進行如下實驗,使用Redis2.6版本,在啟動指令參數中設定開啟aof功能:

./redis-server --appendonly yes
      

然後我們執行如下的指令:

127.0.0.1:7791> SET name yayun
OK
127.0.0.1:7791> APPEND name good
(integer) 9
127.0.0.1:7791> DEL name
(integer) 1
127.0.0.1:7791> DEL non_existing_key
(integer) 0
127.0.0.1:7791>       

這時我們檢視AOF日志檔案,就會得到如下内容:

[root@localhost redis_7791]# tail -n 20 appendonly.aof  
yayun
*3
$3
SET
$4
name
$5
yayun
*3
$6
APPEND
$4
name
$4
good
*2
$3
DEL
$4
name
[root@localhost redis_7791]#       

可以看到,寫操作都生成了一條相應的指令作為日志。其中值得注意的是最後一個del指令,它并沒有被記錄在AOF日志中,這是因為Redis判斷出這個指令不會對目前資料集做出修改。是以不需要記錄這個無用的寫指令。另外AOF日志也不是完全按用戶端的請求來生成日志的,比如指令INCRBYFLOAT在記AOF日志時就被記成一條SET記錄,因為浮點數操作可能在不同的系統上會不同,是以為了避免同一份日志在不同的系統上生成不同的資料集,是以這裡隻将操作後的結果通過SET來記錄。

AOF重寫

你可以會想,每一條寫指令都生成一條日志,那麼AOF檔案是不是會很大?答案是肯定的,AOF檔案會越來越大,是以Redis又提供了一個功能,叫做AOF rewrite。其功能就是重新生成一份AOF檔案,新的AOF檔案中一條記錄的操作隻會有一次,而不像一份老檔案那樣,可能記錄了對同一個值的多次操作。其生成過程和RDB類似,也是fork一個程序,直接周遊資料,寫入新的AOF臨時檔案。在寫入新檔案的過程中,所有的寫記錄檔還是會寫到原來老的AOF檔案中,同時還會記錄在記憶體緩沖區中。當重完操作完成後,會将所有緩沖區中的日志一次性寫入到臨時檔案中。然後調用原子性的rename指令用新的AOF檔案取代老的AOF檔案。

從上面的流程我們能夠看到,RDB和AOF操作都是順序IO操作,性能都很高。而同時在通過RDB檔案或者AOF日志進行資料庫恢複的時候,也是順序的讀取資料加載到記憶體中。是以也不會造成磁盤的随機讀。

AOF可靠性設定

AOF是一個寫檔案操作,其目的是将記錄檔寫到磁盤上,是以它也同樣會遇到我們上面說的寫操作的5個流程。那麼寫AOF的操作安全性又有多高呢。實際上這是可以設定的,在Redis中對AOF調用write(2)寫入後,何時再調用fsync将其寫到磁盤上,通過appendfsync選項來控制,下面appendfsync的三個設定項,安全強度逐漸變強。

appendfsync no

當設定appendfsync為no的時候,Redis不會主動調用fsync去将AOF日志内容同步到磁盤,是以這一切就完全依賴于作業系統的調試了。對大多數Linux作業系統,是每30秒進行一次fsync,将緩沖區中的資料寫到磁盤上。

appendfsync everysec

當設定appendfsync為everysec的時候,Redis會預設每隔一秒進行一次fsync調用,将緩沖區中的資料寫到磁盤。但是當這一次的fsync調用時長超過1秒時。Redis會采取延遲fsync的政策,再等一秒鐘。也就是在兩秒後再進行fsync,這一次的fsync就不管會執行多長時間都會進行。這時候由于在fsync時檔案描述符會被阻塞,是以目前的寫操作就會阻塞。

是以,結論就是,在絕大多數情況下,Redis會每隔一秒進行一次fsync。在最壞的情況下,兩秒鐘會進行一次fsync操作。

這一操作在大多數資料庫系統中被稱為group commit,就是組合多次寫操作的資料,一次性将日志寫到磁盤。

appednfsync always

當設定appendfsync為always時,每一次寫操作都會調用一次fsync,這時資料是最安全的,當然,由于每次都會執行fsync,是以其性能也會受到影響。

對于pipelining有什麼不同

對于pipelining的操作,其具體過程是用戶端一次性發送N個指令,然後等待這N個指令的傳回結果被一起傳回。通過采用pipilining就意味着放棄了對每一個指令的傳回值确認。由于在這種情況下,N個指令是在同一個執行過程中執行的。是以當設定appendfsync為everysec時,可能會有一些偏差,因為這N個指令可能執行時間超過1秒甚至2秒。但是可以保證的是,最長時間不會超過這N個指令的執行時間和。

與postgreSQL和MySQL的比較

這一塊就不多說了,由于上面作業系統層面的資料安全已經講了很多,是以其實不同的資料庫在實作上都大同小異。總之最後的結論就是,在Redis開啟AOF的情況下,其單機資料安全性并不比這些成熟的SQL資料庫弱。

資料導入

這些持久化的資料有什麼用,當然是用于重新開機後的資料恢複。Redis是一個記憶體資料庫,無論是RDB還是AOF,都隻是其保證資料恢複的措施。是以Redis在利用RDB和AOF進行恢複的時候,都會讀取RDB或AOF檔案,重新加載到記憶體中。相對于MySQL等資料庫的啟動時間來說,會長很多,因為MySQL本來是不需要将資料加載到記憶體中的。

但是相對來說,MySQL啟動後提供服務時,其被通路的熱資料也會慢慢加載到記憶體中,通常我們稱之為預熱,而在預熱完成前,其性能都不會太高。而Redis的好處是一次性将資料加載到記憶體中,一次性預熱。這樣隻要Redis啟動完成,那麼其提供服務的速度都是非常快的。

而在利用RDB和利用AOF啟動上,其啟動時間有一些差别。RDB的啟動時間會更短,原因有兩個,一是RDB檔案中每一條資料隻有一條記錄,不會像AOF日志那樣可能有一條資料的多次操作記錄。是以每條資料隻需要寫一次就行了。另一個原因是RDB檔案的存儲格式和Redis資料在記憶體中的編碼格式是一緻的,不需要再進行資料編碼工作。在CPU消耗上要遠小于AOF日志的加載。

參考資料:

http://oldblog.antirez.com/post/redis-persistence-demystified.html

作者:Atlas

出處:Atlas的部落格 http://www.cnblogs.com/gomysql

您的支援是對部落客最大的鼓勵,感謝您的認真閱讀。本文版權歸作者所有,歡迎轉載,但請保留該聲明。如果您需要技術支援,本人亦提供有償服務。

繼續閱讀