从开始阅读boltdb项目到现在也过去了大概1个月,对这个项目多多少少有了一个全局的认识,知道这个项目的使用场景在读多写少,底层实现是B+树,实现了事务ACID特性并且是用锁来实现的,读事务是读的操作系统的内存,写事务是直接写的磁盘,因为是分离的,所以保证了读读事务和单读写事务的并发特性。
项目整体都是用go来实现的,没有任何cgo的代码,里面大量使用了指针操作,是golang学指针不可多得的学习资料。boltdb在非常多的项目里被使用,比如最著名的etcd项目。
我从boltdb里学到了蛮多知识点的 ,下面我一个个来分析下。
架构分层、抽象
boltdb是数据库设计里的典范之作,里面的分层设计做的很好,因为我是第一次读数据库项目,我觉得这个项目抽象的很好。架构上分了db实现,bucket实现,cursor游标遍历实现,tx事务实现,page 与磁盘交互的实现,node B+ 树节点实现和B+树的操作实现等。 每个模块都抽象的非常好,互相之间解耦了。
golang
unsafe 指针操作
boltdb里大量使用了指针操作,比如page模块。page 模块是与磁盘最接近的模块,因为page是指针到磁盘offset的转换。其实操作系统里,数据结构在内存的实现是指针,在磁盘的实现是offset,这2个东西本质上是等价的,都是标记数据的位置。
锁操作
tx 事务的实现大量用了锁,我一开始特别好奇,数据库的事务到底是怎么实现的,直到我看了boltdb的源码。我发现数据库的事务实现其实就是用锁。在boltdb里,读写事务是拿到一把互斥锁,写事务的时候是直接刷数据到文件;读事务是获取的读锁,而读锁的获取直观上来说只是增加一条S锁增加一条记录而已,而数据的获取是直接从操作系统的 page cache 内存里读的。 一个是读内存,一个是写磁盘,保证了并发事务的完成。
IO 操作,page,磁盘等
page 模块有大量的指针操作,而指针到磁盘的操作,会涉及到文件的io。在boltdb里,经常会分配一块 byte array空间,然后获取指针地址并让page指向这块地址,然后写page就是写这块buffer,最后把这个buffer write到文件io里。这个实现其实有点突破我个人的认知,原来go还可以向c一样去写,特别优雅。
单元测试
boltdb里有大量的单元测试代码,甚至可以占到所有代码的一半。不过可能是作者不维护的原因吧,目前单元测试是跑不通的,不知道为什么。不过我去跟了bboltd,也就是etcd的fork项目,单元测试是可以跑通的。
一个项目的单元测试丰富,可以大致确定它的函数抽象是非常好的,可测试的,可验证的。boltdb里有很多benchmark代码,有很多对单函数的测试,也有很多fake函数,比如对db的fake。我学习到的是,怎么把一个模块写的可测试,怎么mock一个模块又达到测试的目的。
总结
boltdb是非常优秀的go项目,非常值得多读几遍,我经常有时间就翻开它的源码,常读常新。如果我推荐golang新手去学一个开源项目,我会推荐boltdb。