go入门教程- 版本 2 - 添加持久化存储

  1. 版本 2 - 添加持久化存储
  2. 19.5 持久化存储:gob
    1. 链接

版本 2 - 添加持久化存储

第 2 个版本的代码 goto_v2goto_v2

19.5 持久化存储:gob

(本节代码见 goto_v2/store.gogoto_v2/main.go。)

当 goto 进程(监听在 8080 端口的 web 服务器)终止,这迟早会发生,内存 map 中缩短的 URL 就会丢失。要保留这些数据,就得将其保存到磁盘文件中。我们将修改 URLStore,使它可以保存数据到文件,且在 goto 启动时还原这些数据。为此我们使用 Go 标准库的 encoding/gob 包:它用于序列化和反序列化,将数据结构转换为字节数组(确切地说是切片),反之亦然(见 12.11 节)。

通过 gob 包的 NewEncoderNewDecoder 函数,可以指定数据要写入或读取的位置。返回的 EncoderDecoder 对象提供了 EncodeDecode 方法,用于对文件写入和从中读取 Go 数据结构。提示:Encoder 实现了 Writer 接口,同样 Decoder 实现了 Reader 接口。我们在 URLStore 上增加一个新的 file 字段(*os.File 类型),它是用于读写已打开文件的句柄。

1
2
3
4
5
type URLStore struct {
urls map[string]string
mu sync.RWMutex
file *os.File
}

我们把这个文件命名为 store.gob,当初始化 URLStore 时将其作为参数传入:

1
var store = NewURLStore("store.gob")

接着,调整 NewURLStore 函数:

1
2
3
4
5
6
7
8
9
func NewURLStore(filename string) *URLStore {
s := &URLStore{urls: make(map[string]string)}
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("URLStore:", err)
}
s.file = f
return s
}

现在,更新后的 NewURLStore 函数接受一个文件名参数,它会打开该文件(见 12 章),将返回的 *os.File 作为 file 字段的值存储在 URLStore 变量 store 中,即这里的本地变量 s

OpenFile 的调用可能会失败(例如文件可能被删除或改名)。它会返回一个错误 err,注意 Go 是如何处理这种情况的:

1
2
3
4
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("URLStore:", err)
}

当 err 不为 nil,表示确实发生了错误,那么输出一条消息并停止程序执行。这是处理错误的一种方式,大多数情况下错误应该返回给调用函数,但这种检测错误的模式在 Go 代码中也很普遍。在 } 之后可以确定文件被成功打开了。

打开该文件时启用了写入标志,更精确地说是“追加模式”。每当一对新的短/长 URL 在程序中创建后,我们通过 gob 把它存储到文件 “store.gob” 中。

为达到目的,定义一个新的结构体类型 record

1
2
3
type record struct {
Key, URL string
}

以及新的 save 方法,将给定的键和 URL 组成 record ,以 gob 编码的形式写入磁盘。

1
2
3
4
func (s *URLStore) save(key, url string) error {
e := gob.NewEncoder(s.file)
return e.Encode(record{key, url})
}

goto 程序启动时,磁盘上存储的数据必须读取到 URLStore 的 map 中。为此,我们编写 load 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (s *URLStore) load() error {
if _, err := s.file.Seek(0, 0); err != nil {
return err
}
d := gob.NewDecoder(s.file)
var err error
for err == nil {
var r record
if err = d.Decode(&r); err == nil {
s.Set(r.Key, r.URL)
}
}
if err == io.EOF {
return nil
}
return err
}

这个新的 load 方法会寻址(Seek)到文件的起始位置,读取并解码(Decode)每一条记录(record),然后用 Set 方法将数据存储到 map 中。再次注意无处不在的错误处理模式。文件的解码由一个无限循环完成,只要没有错误就会一直继续:

1
2
3
for err == nil {

}

如果得到了一个错误,可能是刚解码了最后一条记录,于是产生了 io.EOF(EndOfFile) 错误。若并非此种错误,表示产生了解码错误,用 return err 来返回它。对该方法的调用必须加入到 NewURLStore 中:

1
2
3
4
5
6
7
8
9
10
11
12
func NewURLStore(filename string) *URLStore {
s := &URLStore{urls: make(map[string]string)}
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal("Error opening URLStore:", err)
}
s.file = f
if err := s.load(); err != nil {
log.Println("Error loading data in URLStore:", err)
}
return s
}

同时在 Put 方法中,当新的 URL 对加入到 map 中,也应该立即将它们保存到数据文件中:

1
2
3
4
5
6
7
8
9
10
11
12
func (s *URLStore) Put(url string) string {
for {
key := genKey(s.Count())
if s.Set(key, url) {
if err := s.save(key, url); err != nil {
log.Println("Error saving to URLStore:", err)
}
return key
}
}
panic("shouldn’t get here")
}

编译并测试这第二个版本的程序,或直接使用现有的可执行程序,验证关闭服务器(在终端窗口可以按 CTRL/C)并重启后,短 URL 仍然有效。goto 程序第一次启动时,文件 store.gob 还不存在,因此当载入数据时会得到错误:

2011/09/11 11:08:11 Error loading URLStore: open store.gob: The system cannot find the file specified.

结束进程并重启后,就能正常工作了。或者,可以在 goto 启动前先创建空的 store.gob 文件。

备注: 当第二次启动 goto 时,可能会产生错误:

Error loading URLStore: extra data in buffer

这是由于 gob 是基于流的协议,它不支持重新开始。在版本 4 中,会用 json 作为存储协议来补救此问题。

链接


免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 [email protected]

推荐阅读:

文章标题:go入门教程- 版本 2 - 添加持久化存储

本文作者:知识铺

发布时间:2019-10-15, 22:30:20

最后更新:2019-10-16, 21:00:39

原始链接:https://blog.zshipu.com/2019/10/15/golang/20191015/19.5/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏