
zhaozhenhang
V1
2022/09/24阅读:16主题:默认主题
从零实现一个TSDB(四)
实现刷盘操作
之前我们实现了将chan中的rows封装成segment然后存储进入内存List结构,现在来学习下如何进行异步刷盘操作。
方法 writeCodeSegment()
-
判断是否是冷数据 -
创建一个dirname,然后将当前segment刷入磁盘 -
刷盘后打开该写入磁盘文件的fd句柄,通过mmap的方式打开 -
构造出一个diskSegment存储进入内存有序List,方便查询
tsdb.go
func (db *TSDB) writeColdSegment() (Segment, error) {
db.mutex.Lock()
defer db.mutex.Unlock()
if db.segments.head.Frozen() {
head := db.segments.head
go func() {
db.wait.Add(1)
defer db.wait.Done()
db.segments.Add(head)
startTime := time.Now()
dirname := makeDirName(head.MinTs(), head.MaxTs())
if err := writeToDisk(head.(*memtable)); err != nil {
logrus.Errorf("faild to flush data to disk, %v", err)
return
}
filename := path.Join(dirname, "data")
mmapFile, err := OpenMMapFile(filename)
if err != nil {
logrus.Errorf("failed to make a mmap file %s, %v", filename, err)
return
}
// 将diskSegment添加进入tree,方便查询
err = db.segments.Replace(head, newDiskSegment(mmapFile, dirname, head.MinTs(), head.MaxTs()))
if err != nil {
logrus.Errorf("add diskSegment into in list error: %v", err)
return
}
logrus.Infof("write file %s take: %v", filename, time.Since(startTime))
}()
db.segments.head = newMemtable()
}
return db.segments.head, nil
}
核心方法 writeToDisk()
可以看到,核心方法就是刷盘的writeToDisk()
步骤:
-
将segment编码成为两个部分,第一部分就是我们要存储的data数据,第二部分就是meta数据,也就是开头我们看到的meta.json数据,可以告知我们该segment文件中存储的时间区间,row条数等等 -
进行写文件,分别将data和meta.json的数据写入data文件和meta文件
func writeToDisk(segment *memtable) error {
dataBytes, descBytes, err := segment.Marshal()
if err != nil {
return fmt.Errorf("faild to marshal segment: %s", err.Error())
}
writeFile := func(file string, data []byte) error {
if isFileExist(file) {
return fmt.Errorf("%s file is already exist", file)
}
fd, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return nil
}
defer fd.Close()
_, err = fd.Write(data)
return err
}
dirname := makeDirName(segment.MinTs(), segment.MaxTs())
mkdir(dirname)
if err = writeFile(path.Join(dirname, "data"), dataBytes); err != nil {
return err
}
if err = writeFile(path.Join(dirname, "meta"), descBytes); err != nil {
return err
}
return nil
}
其核心就是Marshal()
方法,将segment生成对应的数据按照指定的格式存储在磁盘上面。
Marshal方法
将数据分成两大块:
-
data:生成data文件的数据
data数据大概分成这么几块儿:
-
series数据 -
meta数据
-
-
desc:生成meta.json的数据(这儿叫desc因为该数据更像是该segment文件的描述信息,而非一个完整的元信息)
作者介绍

zhaozhenhang
V1
快手