本文实现的Godis代码版本为:v0.1git
BGSAVE和SAVE命令生成RDB文件,存储数据库信息。当服务器启动,RDB文件也会做为原始数据,加载近服务内存。这里存在一个优先级问题——当AOF持久化是打开状态,优先从AOF文件加载数据、还原数据库状态。github
SAVE命令会阻塞服务,而BGSAVE派生独立进程,不会阻塞。同时能够经过选项配置自动执行RDB持久化的周期。数据库
Redis服务端经过记录几个参数(如第一篇提到的server.dirty字段记录了上一次SAVE后经历了多少次数据库修改)维护数据库的修改状况。当周期性的后台操做serverCon执行时,会检查数据库的更新状态是否知足RDB持久化条件,依此保存数据库状态。
注意:RDB的文件对数据库数据的存储,采用的方式是存储键值对。segmentfault
前文提到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持久化,而且不对命令进行重写归并操做,全部修改操做都会记录进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
:
已经成功在文件中写入了命令协议。
持久化数据从文件加载进内存的方式是模拟客户端执行命令,逐条将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的值:
查验AOF文件,没有读命令get相关的记录。
伪客户端执行时不要执行持久化操做,对Client结构增长伪终端标志位,用于持久化判断:
func LoadData() { c := godis.CreateClient() c.FakeFlag = true ··· }
没有下集预告了。
这五篇短文的初衷是记录在学习GO时写的小demo,结果花在其余语言以前的精力超出了预算,因此在那之后也没有再继续开发Godis的新feature。
明天在公司的工做迎来忙碌的项目改造期,趁着今天端午小长假最后一天,了解了V0.1版本。不过,在不久的未来,也许下个小长假,会将前文提到过的key过时、网络优化、API开发、Stream等新的feature和优化公之于众。欢迎讨论。