z

zhaozhenhang

V1

2022/09/24阅读:14主题:默认主题

从零实现一个TSDB(四)

实现刷盘操作

之前我们实现了将chan中的rows封装成segment然后存储进入内存List结构,现在来学习下如何进行异步刷盘操作。

方法 writeCodeSegment()

  1. 判断是否是冷数据
  2. 创建一个dirname,然后将当前segment刷入磁盘
  3. 刷盘后打开该写入磁盘文件的fd句柄,通过mmap的方式打开
  4. 构造出一个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()

步骤:

  1. 将segment编码成为两个部分,第一部分就是我们要存储的data数据,第二部分就是meta数据,也就是开头我们看到的meta.json数据,可以告知我们该segment文件中存储的时间区间,row条数等等
  2. 进行写文件,分别将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文件的描述信息,而非一个完整的元信息)

分类:

后端

标签:

后端

作者介绍

z
zhaozhenhang
V1