资讯专栏INFORMATION COLUMN

用Go实现Redis之五持久化

ybak / 3457人阅读

摘要:数据持久化到磁盘在的编码中没有使用类似的事件循环,我们在此依赖字段作为标识。变化即为持久化的时机。服务启动加载数据持久化数据从文件加载进内存的方式是模拟客户端执行命令,逐条将文件命令发送给服务端。

写在前面

本文实现的Godis代码版本为:v0.1

Redis持久化方式 RDB持久化

BGSAVE和SAVE命令生成RDB文件,存储数据库信息。当服务器启动,RDB文件也会作为原始数据,加载近服务内存。这里存在一个优先级问题——当AOF持久化是打开状态,优先从AOF文件加载数据、还原数据库状态。

SAVE命令会阻塞服务,而BGSAVE派生独立进程,不会阻塞。同时可以通过选项配置自动执行RDB持久化的周期。

Redis服务端通过记录几个参数(如第一篇提到的server.dirty字段记录了上一次SAVE后经历了多少次数据库修改)维护数据库的修改情况。当周期性的后台操作serverCon执行时,会检查数据库的更新状态是否满足RDB持久化条件,依此保存数据库状态。
注意:RDB的文件对数据库数据的存储,采用的方式是存储键值对。

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文件要追加的命令记录。

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

已经成功在文件中写入了命令协议。

服务启动加载数据

持久化数据从文件加载进内存的方式是模拟客户端执行命令,逐条将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和优化公之于众。
欢迎讨论。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28879.html

相关文章

  • Go实现Redis之一准备工作

    摘要:命令实现命令是最常用的命令之一,也是最能反映缓存发展历史的操作。命令在客户端接收之后,经由协议转换传递给服务端执行。服务端执行命令前先查询是否支持该命令,以决定是否执行。,是的简称,代表的是只存增量的持久化方式。 缘起 最近公司的第一个PHP转GO项目已经在生产环境稳定运行数周,又逢需求小年儿,最近可以得空分享下去年学GO过程中的练手项目Godis——用Golang实现的Redis. ...

    zhangke3016 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<