[深刻理解Redis]讀取RDB文件

最近在作一個解析rdb文件的功能,途中遇到了一些問題,也解決了一些問題。具體爲何要作這件事情以後再詳談,本次主要想聊聊遇到的開始處理文件時遇到的第一個難題:理解RDB文件的協議、如何讀取二進制文件。html

RDB文件

[Redis源碼閱讀]redis持久化
文章介紹過,Redis的持久化是經過RDB和AOF實現的。Redis的RDB文件是二進制格式的文件,從這個方面再次體現了Redis是基於內存的緩存數據庫,無論對於存儲到硬盤仍是恢復數據都十分快捷。Redis有多種數據類型:string、list、hash、set、zset,不一樣數據類型佔用的內存大小是不同的,解析出天然語言能夠識別的數據就須要使用不一樣的方法,保存到文件的時候也須要一些協議或者規則。這有點相似於編程語言裏面的數據類型,不一樣的數據類型佔用的字節大小不一致,可是保存到計算機都是二進制的編碼,就看是讀取多少個字節,以怎樣的方式解讀。linux

舉個例子,redis的對象類型是特定的幾個字符表示,0表明字符串,讀取到字符串類型後,緊接着就是字符串的長度,保存着接下來須要讀取的字節大小,讀取到的字節最終構成完整字符串對象的值。對於保存了"name" => "hoohack"鍵值對的字符串對象保存到內存能夠用下圖表示:redis

redis字符串存儲

固然,除了字符串,redis還有列表,集合,哈希等各類對象,針對這些類型,在RDB文件裏面都有不一樣的規則定義,只須要按照RDB文件格式的協議來解讀文件,就能完整無誤地把文件解讀成天然語言能描述的字符。數據庫

仔細對比,能夠發現跟計算機的操做方式是相似的,數據保存在計算機都是二進制的,具體的值應該看須要多少個字節,以什麼類型解析,讀取不一樣的字節解析到的值是不同的,一樣的字節大小,可是使用不一樣類型保存,只要作適當的轉換,也是正確的。好比在C語言中的void *指針是4個字節,int也是4個字節,定義一個int整數,將它保存到void *也是沒問題的,讀取的時候只須要作一次類型轉換就能夠了。編程

所以,解讀RDB文件最關鍵的就是理解RDB文件的協議,只要理解完RDB文件格式的協議,根據定義好的協議來解析各類數據類型的數據。更詳細的RDB文件協議能夠參考RDB文件格式的定義文檔:RDB file formatapi

查看RDB文件

先清空redis數據庫,保存一個鍵值對,而後執行save命令將當前數據庫的數據保存的rdb文件,獲得文件dump.rdb。數組

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set name hoohack
OK
127.0.0.1:6379> save
OK

cat查看文件:

cat-rdb-file

能夠看到是一個包含亂碼的文件,由於文件是以二進制的格式保存,要想打印出人類能看出的語言,能夠經過linux的od命令查看。od命令用於輸出文件的八進制、十六進制或其餘格式編碼的字節,一般用於輸出文件中不能直接顯示在終端的字符,注意輸出的是字節,分隔符之間的字符都是保存在一個字節的。緩存

od命令輸出文件八進制、十六進制等格式

經過man手冊能夠看到,打印出16進制格式的參數是x,字符是c,將字符與十六進制的對應關係打印出來:od -A x -t x1c -v dump.rdb編程語言

打印得出結果以下:函數

linux-od

從上圖看到,文件打印出來的都是一些十六進制的數字,轉換成十進制再去ASCII碼錶就能查找到對應的字符。好比第一個字符,52=516+21=82='R’。
在這裏說一句,我的以爲這od命令很是有用,在解析數據出現疑惑的時候,就是經過這個命令排查遇到的問題。把文件內容打印出來,找到當前讀取的字符,對比RDB文件協議,看看究竟要解析的是什麼數據,再對比代碼中的解析邏輯,看看是否有問題,而後再修正代碼。
若是以爲敲命令麻煩,能夠把文件上傳而後在線查看:https://www.onlinehexeditor.com

數據的二進制保存和讀取

咱們知道,計算機的全部數據都是以二進制的格式保存的,咱們看到的字符是經過讀取二進制而後解析出來的數據,程序根據不一樣數據類型作相應的轉換,而後展現出來的就是咱們看到的字符。
計算機容許多種數據類型,好比有:32位整數、64位整數、字符串、浮點數、布爾值等等,不一樣的數據類型是經過讀取不一樣大小的字節,根據類型指定的讀取方式讀取出來,就是想要的數據了。

解析數據的第一步,就是讀取數據。在計算機裏面,你們所知道的數據都是逐個字節地讀取數據。所以,首先要實現的就是按照字節去讀取文件。

本次採用實現的解析RDB文件功能的語言是Golang,在Golang的文件操做的API裏,提供了按字節讀取的函數File.ReadAt

函數原型以下:

func (f *File) ReadAt(b []byte, off int64) (n int, err error)

函數從File指針的指向的位置off開始,讀取len(b)個字節的數據,並保存到b中。
根據對API的理解,本身實現了一個按照字節讀取文件數據的函數,函數接收長度值,表明須要讀取的字節長度。具體實現代碼以下:

type Rdb struct {
    fp *os.File
    ... // other field
}

func (r *Rdb) ReadBuf(length int64) ([]byte, error) {
    // 初始化一個大小爲length的字節數組
    buf := make([]byte, length)

    // 從curIndex開始讀取length個字節
    size, err := r.fp.ReadAt(buf[:length], r.curIndex)

    checkErr(err)

    if size < 0 {
        fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
        return []byte{}, err
    } else {
        // 讀取成功,更新文件操做的偏移量
        r.curIndex += length
        return buf, nil
    }
}

Golang的數據轉換

上面實現的函數返回的是字節數組,當函數返回讀取到的數據後,若是須要保存在不一樣的數據類型就須要作轉換,Golang也提供了比較強大的api。如下是我在解析數據時遇到的數據類型轉換的解決方案,但願對你們有幫助。
字符串

str := string(buf)

int整數,先轉爲二進制的值,而後再用int32類型格式化

intVal := int(binary.BigEndian.Uint32(buf))

int64整數,先轉爲二進制的值,而後再用int64類型格式化

int64Val := int64(int16(binary.LittleEndian.Uint16(valBuf)))

浮點數

floatVal, err := strconv.ParseFloat(string(floatBuf), 64)

float64浮點數,先轉爲二進制的值,再調用math庫的Float64frombits函數轉換二進制的值爲float64類型

floatBit := binary.LittleEndian.Uint64(buf)
floatVal := math.Float64frombits(floatBit)

總結

理論上的理解和實踐上的應用是不同的,雖然你們都知道數據是二進制的,就是怎麼怎麼解析,可是真正實現起來仍是很多問題。經過操做二進制文件的一次實踐,收穫瞭如下幾點:

一、更深入地理解到數據在計算機中的保存方式,一切都是0和1的二進制內容,只是看你要怎麼用而已,適當的相似轉換也能夠獲得你想要的內容

二、在某個系統下操做就要遵循已定義好的協議,否則獲得的都是亂套或者亂碼的東西,好比數據的字節序不一樣也會使數據的解析結果不一致

原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。

若是本文對你有幫助,請點個贊吧,謝謝^_^

更多精彩內容,請關注我的公衆號。

相關文章
相關標籤/搜索