Redis支持兩種持久化方式:rdb與aof。rdb將一個節點上的內存數據序列化後存儲到磁盤中,序列化的數據以儘量節約空間的方式存儲,並不是徹底的ascii表示。它的優勢在於節約空間,恢復速度快,缺點在於每一次操做都須要對整個內存數據進行序列化,而且持久化過程當中的修改被丟失。而aof將數據以操做命令的方式進行存儲,從aof恢復數據即從aof文件讀入命令再執行命令。它的優勢是能夠記錄持久化過程當中的產生的命令,而缺點在於徹底以ascii編碼,使用空間更多,且恢復速度更慢。這篇文件章主要對rdb的持久化方式進行大體介紹。redis
Redis中有多種數據類型,如string、set、zset、hash、list等,而且具備多個db(一個db便是一個字典,存儲了string、list、set等類型的數據),爲了完成不一樣類型數據的序列化,Redis設計了相應的持久化格式。數組
序列化string時,首先會存儲string的長度,而後存儲string的真實值。爲了節約空間,對於不一樣長度的string,redis使用盡量少的空間來存儲它的長度,所以設計了以下的字符串長度存儲格式: 緩存
Redis中相應的定義以下:socket
/* Defines related to the dump file format. To store 32 bits lengths for short * keys requires a lot of space, so we check the most significant 2 bits of * the first byte to interpreter the length: * * 00|XXXXXX => if the two MSB are 00 the len is the 6 bits of this byte * 01|XXXXXX XXXXXXXX => 01, the len is 14 byes, 6 bits + 8 bits of next byte * 10|000000 [32 bit integer] => A full 32 bit len in net byte order will follow * 10|000001 [64 bit integer] => A full 64 bit len in net byte order will follow * 11|OBKIND this means: specially encoded object will follow. The six bits * number specify the kind of object that follows. * See the RDB_ENC_* defines. * * Lengths up to 63 are stored using a single byte, most DB keys, and may * values, will fit inside. */
舉例,對string「hello world」編碼結果以下:ide
0xC 「hello, world」
0xC以1個字節表示字符串長度爲12字節,後續12字節即爲字符串的真實值。因爲是非前綴碼,所以根據第1個字節的值徹底能夠進行區分是哪一種格式,反序列化時首先讀1個字節的內容,而後根據該字節的值便可選擇正確的操做。函數
redis中rdb.c源文件中的rdbSaveLen函數完成長度的編碼,而rdbLoadLen完成長度值的解碼。ui
當須要序列化的值爲整形值時,整形值會以二進制的形式直接進行序列化,而不是轉換成ascii碼的字符串形式進行序列化,從而進一步節約空間。此外,根據整形值的大小,redis一樣以儘量少的空間來存儲該值。與字符串相似,首先有一個字節的高2bits設置爲11表明後續的值是一個整形值,同時以低6bits的值區分使用的字節數。this
總體形式以下: 編碼
11000000[8 bit integer] 11000001[16 bit integer] 11000002[32 bit integer]
舉例,序列化整形值0x300的結果以下:spa
0xC1 0x0300
0xC1表示後續是一個以int16形式存儲的整形值,0x0300即爲該整形值。因爲第1個字節的高2bits爲11,與string類型表示長度的格式不相同,所此能夠區分是string仍是int型。
此外,若第1個字節的高2bits爲11,低6bits爲3(RDB_ENC_LZF),那麼後續的數據爲壓縮數據,該字節後跟着兩個長度值,分別表示壓縮後的數據長度與壓縮前的數據長度,再後面纔是真正的數據。
Redis是一個key-value緩存系統,全部的值都以key-value的形式存儲在db字典中。可是redis中value的類型並不只限於string,它還能夠是結構體類型,如set、list、hash等。爲了序列化這些類型,redis中首先會以一個字節存儲數據類型,而後若是是複合類型,會存儲該類型的成員數目,而後遍歷成員值並以基本的int或者string類型進行存儲。
Redis中定義了以下的類型值:
/* Map object types to RDB object types. Macros starting with OBJ_ are for * memory storage and may change. Instead RDB types must be fixed because * we store them on disk. */ #define RDB_TYPE_STRING 0 #define RDB_TYPE_LIST 1 #define RDB_TYPE_SET 2 #define RDB_TYPE_ZSET 3 #define RDB_TYPE_HASH 4 #define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */ #define RDB_TYPE_MODULE 6 #define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without the generating module being loaded. */ /* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */ /* Object types for encoded objects. */ #define RDB_TYPE_HASH_ZIPMAP 9 #define RDB_TYPE_LIST_ZIPLIST 10 #define RDB_TYPE_SET_INTSET 11 #define RDB_TYPE_ZSET_ZIPLIST 12 #define RDB_TYPE_HASH_ZIPLIST 13 #define RDB_TYPE_LIST_QUICKLIST 14 #define RDB_TYPE_STREAM_LISTPACKS 15 /* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
這些類型值表明了被序列化的value的類型,如list、set等,此外還表明了value的具體實現方式,如set可使用hash表實現,相應類型值爲RDB_TYPE_SET,也可使用一個有序的整形數組實現,對應類型值爲RDB_TYPE_SET_INTSET。反序列化時,首先讀取1個字節的值判斷後續數據的類型,而後進行相應的類型重建。
舉例,一個string類型的key-value對 「key1」 「hello, world」的序列化結果爲:
RDB_TYPE_STRING 0x4 「key1」 0xC 「hello, world」
首先1個字節的類型值RDB_TYPE_STRING,而後一個字節的長度值0x4,即後面有4個字節的關鍵字字符串,而後一個字節的長度值0xC,即後面有12個字節的字符串值。
反序列化時:
舉例,一個list類型的key-value對:」key2」 2 3 4 0x300的序列化結果爲:
RDB_TYPE_LIST 0x4 「key2」 0x4 0xc0 0x2 0xc0 0x3 0xc0 0x4 0xC1 0x0300
首先是1個字節的類型值RDB_TYPE_LIST,而後長度值爲0x4,表示有4個字節的關鍵字字符串,而後是0x4表示該list有4個成員,而後4個成員依次以整形的格式序列化。
反序列化時:
除了RDB_TYPE*與redis中存儲的數據類型對應,還有一類RDB_OPCODE*表示一些其它的數據,如:RDB_OPCODE_EXPIRETIME表示後面的數據是該對象的超時時間;RDB_OPCODE_SELECTDB表示接下來的數據是一個db索引,直到遇到下一個RDB_OPCODE_SELECTDB以前,全部反序列化的數據都應該存儲在該索引的db字典中。Redis中定義了以下類型的RDB_OPCODE*值:
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */ #define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */ #define RDB_OPCODE_IDLE 248 /* LRU idle time. */ #define RDB_OPCODE_FREQ 249 /* LFU frequency. */ #define RDB_OPCODE_AUX 250 /* RDB aux field. */ #define RDB_OPCODE_RESIZEDB 251 /* Hash table resize hint. */ #define RDB_OPCODE_EXPIRETIME_MS 252 /* Expire time in milliseconds. */ #define RDB_OPCODE_EXPIRETIME 253 /* Old expire time in seconds. */ #define RDB_OPCODE_SELECTDB 254 /* DB number of the following keys. */ #define RDB_OPCODE_EOF 255 /* End of the RDB file. */
這些特殊類型後續值的長度一般是固定的,如RDB_OPCODE_EXPIRETIME以32bit表示超時時間,單位爲s;RDB_OPCODE_EXPIRETIME_MS後面是64bit表示的超時時間,單位爲ms;RDB_OPCODE_SELECTDB後續是使用的db索引,以len的編碼方式進行存儲,而RDB_OPCODE_AUX表示一些key-value對,而這些key, value都是string與int等基本類型。
除了rdb文件開頭的固定字節的magic碼,全部rdb序列化的數據都有一個前置的RDB_TYPE_*值或者RDB_OPCODE_*值,它表示了後續數據的存儲方式,從而反序列化時採起正確的操做。
1. 序列化前置信息,如magic識別碼,版本號,時間戳
2.遍歷db,序列化每個db
2.1 序列化RDB_OPCODE_SELETCTDB
2.2 序列化RDB_OPCODE_RESIZEDB,存儲該db的大小
2.3 序列化db中的每個key-value對
2.3.1 序列化超時時間RDB_OPCODE_EXPIRETIME
2.3.2 序列化lru值,RDB_OPCODE_IDLE
2.3.3 序列化LFU值,RDB_OPCODE_FREQ
2.3.4 序列化值類型
2.3.5 序列化key(string)
2.3.6 序列化value
在前一篇文件中介紹過redis的rio抽象層,而這些序列化操做正是以rio做爲接口,以rio爲目的地,既能夠將序列化內容輸出到文件,也能夠將序列化內容輸出到多個sockets中。普通的持久化操做使用文件做爲輸出對象,而在master-slave中的數據同步可能會使用到sockets做爲輸出對象,經過rio的抽象,將序列化與底層io進行解藕。
redis中的序列化函數調用棧以下:
左圖是輸出對象爲文件的序列化調用關係,右圖是輸出對象爲sockets的序列化調用關係。
1.讀取9字節magic標誌,並驗證
循環進行2-3步
2. 讀取1字節RDB_TYPE_*或者RDB_OPCODE_*標誌
3. 根據RDB_OPCODE_*或者RDB_TYPE_*的值作相應的處理
上述第3步中,若是須要讀取string或者int這種基本類型,處理過程爲:
若是對應的類型爲複合類型,如list、set等,處理過程爲:
反序列化的輸入爲文件,即便序列化的輸出目的地爲sockets,接收端也會先將數據存儲到一個文件中,而後再從文件反序列化。反序列化的調用棧爲
因爲redis是單線程模式,所以它選擇將持久化操做放在子進程中進行,不然持久化過程當中將中止響應請求。
根據fork函數的特性,子進程建立後與父進程擁有相同的內存內容,所以fork函數調用後子進程即獲得了此時db中的完整內容。而且因爲copy-on-write特性,並不會發生大量的內存copy,僅在write操做發生時,相應的內存頁才進行一個copy生成副本,即該操做也不會特別耗時。
但相應的,父進程繼續接受客戶端的命令,修改的內容並不會反應到子進程的內存中,所以rdb持久化過程當中出現的修改將會丟失。