用Go實現Redis之五持久化

寫在前面

本文實現的Godis代碼版本爲:v0.1git

Redis持久化方式

RDB持久化

BGSAVE和SAVE命令生成RDB文件,存儲數據庫信息。當服務器啓動,RDB文件也會做爲原始數據,加載近服務內存。這裏存在一個優先級問題——當AOF持久化是打開狀態,優先從AOF文件加載數據、還原數據庫狀態。github

SAVE命令會阻塞服務,而BGSAVE派生獨立進程,不會阻塞。同時能夠經過選項配置自動執行RDB持久化的週期。數據庫

Redis服務端經過記錄幾個參數(如第一篇提到的server.dirty字段記錄了上一次SAVE後經歷了多少次數據庫修改)維護數據庫的修改狀況。當週期性的後臺操做serverCon執行時,會檢查數據庫的更新狀態是否知足RDB持久化條件,依此保存數據庫狀態。
注意:RDB的文件對數據庫數據的存儲,採用的方式是存儲鍵值對。segmentfault

AOF持久化

前文提到RDB文件保存的是數據自己,而AOF文件存儲的是執行的命令轉成的協議。能夠經過開啓Redis的AOF持久化,操做若干命令後,查看appendonly.aof文件瞭解。
由於是對數據的備份操做,讀命令無需記錄,只需記錄修改型操做。服務器

若是AOF持久化對每次修改命令都計入文件,會多記錄一些無效命令。如:網絡

set alpha 123
set alpha 1
set alpha 321
set alpha 123

四條命令是過程,數據庫記錄的最終值123纔是過程的最終結果。
爲了不對同一個key的操做的「無效命令」的記錄,Redis有AOF重寫機制——讀取當前數據狀態做爲AOF文件要追加的命令記錄。app

Godis實現AOF持久化

Godis只實現AOF持久化,而且不對命令進行重寫歸併操做,全部修改操做都會記錄進AOF文件。這也意味着,在數據保存階段,會有不少無效I/O操做;加載階段,會有不少無效的命令被執行。函數

數據持久化到磁盤

在Godis的編碼中沒有使用Redis相似的事件循環,咱們在此依賴server.dirty字段做爲標識。dirty變化即爲持久化的時機。學習

首先,在命令調用處添加AOF持久化判斷,若是dirty變化,則進行持久化:測試

func call(c *Client, s *Server) {
    dirty := s.Dirty
    c.Cmd.Proc(c, s)
    dirty = s.Dirty - dirty
    if dirty > 0 {//dirty變化 進行持久化
        AppendToFile(s.AofFilename, c.QueryBuf)
    }

}

執行持久化操做的函數AppendToFile也很簡單,對文件追加寫,而且即刻關閉:

func AppendToFile(fileName string, content string) error {
    // 以只寫的模式,打開文件
    f, err := os.OpenFile(fileName, os.O_WRONLY|syscall.O_CREAT, 0644)
    if err != nil {
        log.Println("log file open failed" + err.Error())
    } else {
        n, _ := f.Seek(0, os.SEEK_END)
        _, err = f.WriteAt([]byte(content), n)
    }
    defer f.Close()
    return err
}

最後,在修改型命令(如set命令)的實現處,添加對server.dirty的更新。

func SetCommand(c *Client, s *Server) {
    ···
    s.Dirty++
    ···
}

咱們來測試下效果,從新編譯godis-server.go,並執行set alpha 123

clipboard.png

已經成功在文件中寫入了命令協議。

服務啓動加載數據

持久化數據從文件加載進內存的方式是模擬客戶端執行命令,逐條將AOF文件命令發送給服務端。

func LoadData() {
    c := godis.CreateClient()
    pros := core.ReadAof(godis.AofFilename)
    for _, v := range pros {
        c.QueryBuf = string(v)
        err := c.ProcessInputBuffer()
        if err != nil {
            log.Println("ProcessInputBuffer err", err)
        }
        godis.ProcessCommand(c)
    }
}

core.ReadAof將AOF文件讀入內存,分條存儲。然後的ProcessCommand在set/get命令實現處有介紹,再也不說明。

集成測試

關閉服務端,從新啓動服務端,直接在客戶端執行get alpha,查看是否能獲取以前set的值:

clipboard.png

查驗AOF文件,沒有讀命令get相關的記錄。

本篇問題

僞客戶端執行時不要執行持久化操做,對Client結構增長僞終端標誌位,用於持久化判斷:

func LoadData() {
    c := godis.CreateClient()
    c.FakeFlag = true
    ···
}

小結

沒有下集預告了。
這五篇短文的初衷是記錄在學習GO時寫的小demo,結果花在其餘語言以前的精力超出了預算,因此在那之後也沒有再繼續開發Godis的新feature。

明天在公司的工做迎來忙碌的項目改造期,趁着今天端午小長假最後一天,瞭解了V0.1版本。不過,在不久的未來,也許下個小長假,會將前文提到過的key過時、網絡優化、API開發、Stream等新的feature和優化公之於衆。歡迎討論。

相關文章
相關標籤/搜索