从零实现一个TSDB(六)

QuerySeries 方法实现

本篇就讲QuerySeries方法的实现,这儿会用到一些额外知识~

  • Roaring Bitmap
  • 来自 Prometheus优化的正则匹配器

tsdb.go

func (db *TSDB) QuerySeries(matcherList LabelMatcherList, start, end int64) ([]map[string]string, error) {
    labelList := make([]LabelList, 0)
  // 从 内存的有序List中获取segment
    for _, segment := range db.segments.Get(start, end) {
  // 将其加载成为segment(磁盘或者内存)
        segment = segment.Load()
    // 根据传入的正则匹配器查询Series,匹配器可以为nil
        data, err := segment.QuerySeries(matcherList)
        if err != nil {
            return nil, err
        }
        labelList = append(labelList, data...)
    }
  // 合并series的所有label返回
    return db.mergeQuerySeries(labelList...), nil
}

从内存中查询一个segment之前我们已经实现过,就不过多介绍了,Load()函数也上一篇写过,也不去叙述细节,直接看核心方法QuerySeries()

同样,QuerySeries()分为从内存查询和从磁盘查询,具体从哪儿查询,主要看之前的Load()方法获取的segment是磁盘的还是内存的

memtable 内存

memtable.go

func (m *memtable) QuerySeries(matcherList LabelMatcherList) ([]LabelList, error) {
    matchSeriesIds := m.indexMap.MatchSids(m.labelVs, matcherList)
    ret := make([]LabelList, 0)
    for _, seriesID := range matchSeriesIds {
        s, _ := m.segment.Load(seriesID)
        series := s.(*memSeries)
        ret = append(ret, series.labels)
    }
    return ret, nil
}

indexMap保存的就是key=labelName,value = seriesList的数据,所以上述代码,核心就是MatchSids,查询出当前segment中所有我需要的labelName对应的series。

index.go

func (mim *memtableIndexMap) MatchSids(valueList *labelValueList, matcherList LabelMatcherList) []string {
    mim.mutex.Lock()
    defer mim.mutex.Unlock()
    seriesIdList := newMemtableSidList()
    var got bool
    for i := len(matcherList) - 1; i >= 0; i-- {
        temp := newMemtableSidList()
        vs := valueList.Match(matcherList[i])
        for _, value := range vs {
            mimIndex := mim.index[joinSeprator(matcherList[i].Name, value)]
            if mimIndex == nil || mimIndex.Size() <= 0 {
                continue
            }
            temp.Union(mimIndex.Copy())
        }
        if temp == nil || temp.Size() <= 0 {
            return nil
        }
        if !got {
            seriesIdList = temp
            got = true
            continue
        }
        seriesIdList.DeleteSids(temp.Copy())
    }
    return seriesIdList.List()
}

这儿两个点

  1. 通过优化的正则优化器去查询符合条件的label

     
     这儿差一点,Prometheus算是tsdb的
     鼻祖了,推荐阅读一下它的论文,很优秀
  2. 对于相同的labelName,求并集获取所有的sid,对于不同的labelName,求交集去获取符合条件的sid,这样就可以获取我们所需要的series了
正则优化器实现

label.gofastMatcher

注意,我们传入的是labelMatcher,而去进行正则优化的是fastMatcher,所以需要定义两个结构体

// LabelMatcher 标签匹配器
type LabelMatcher struct {
    Name      string
    Value     string
    IsRegular bool
}

type fastMatcher struct {
    regular  *regexp.Regexp
    prefix   string
    suffix   string
    contains string
}

查找过程 Match()方法

func (lvl *labelValueList) Match(matcher LabelMatcher) []string {
    ret := make([]string, 0)
    if matcher.IsRegular {
        pattern, err := newFastRegularMatcher(matcher.Value)
        if err != nil {
            return []string{matcher.Value}
        }
        for _, value := range lvl.Get(matcher.Name) {
            if pattern.MatchStr(value) {
                ret = append(ret, value)
            }
        }
        return ret
    }
    return []string{matcher.Value}
}

核心步骤

  1. 构造fastMatcher

    func newFastRegularMatcher(value string) (*fastMatcher, error) {
     ret, err := regexp.Compile("^(?:" + value + ")$")
     if err != nil {
         return nil, err
     }
     parse, err := syntax.Parse(value, syntax.Perl)
     if err != nil {
         return nil, err
     }
     frm := &fastMatcher{
         regular: ret,
     }
     if parse.Op == syntax.OpConcat {
         frm.prefix, frm.suffix, frm.contains = optimizeRegular(parse)
     }
     return frm, nil
    }
    
    func optimizeRegular(regular *syntax.Regexp) (prefix, sufiix, contains string) {
     sub := regular.Sub
     if len(sub) > 0 && sub[0].Op == syntax.OpBeginText {
         sub = sub[1:]
     }
     if len(sub) > 0 && sub[len(sub)-1].Op == syntax.OpEndText {
         sub = sub[:len(sub)-1]
     }
     if len(sub) <= 0 {
         return
     }
    
     if sub[0].Op == syntax.OpLiteral && (sub[0].Flags&syntax.FoldCase) == 0 {
         prefix = string(sub[0].Rune)
     }
     if last := len(sub) - 1; sub[last].Op == syntax.OpLiteral && (sub[last].Flags&syntax.FoldCase) == 0 {
         sufiix = string(sub[last].Rune)
     }
    
     for i := 1; i < len(sub)-1; i++ {
         if sub[i].Op == syntax.OpLiteral && (sub[i].Flags&syntax.FoldCase) == 0 {
             contains = string(sub[i].Rune)
             break
         }
     }
     return
    }
  2. 获取当前的lableList的所有labelName去通过fastMatcher匹配值

    func (fm *fastMatcher) MatchStr(s string) bool {
     if !strings.EqualFold(fm.prefix, "") && !strings.HasPrefix(s, fm.prefix) {
         return false
     }
     if !strings.EqualFold(fm.suffix, "") && !strings.HasSuffix(s, fm.suffix) {
         return false
     }
    
     if !strings.EqualFold(fm.contains, "") && !strings.Contains(s, fm.contains) {
         return false
     }
     return fm.regular.MatchString(s)
    }

这样就可以拿到当前的segment中的所有的labelName标签,然后就可以通过index索引获取所有的seriesID,然后将其全部保存起来,就是求并集的过程

func (msl *memtableSidList) Union(other *memtableSidList) {
    msl.mutex.Lock()
    defer msl.mutex.Unlock()
    for key := range msl.container {
        msl.container[key] = struct{}{}
    }
}

然后对于不同的labelName的seriesID,求交集

func (msl *memtableSidList) DeleteSids(other *memtableSidList) {
    msl.mutex.Lock()
    defer msl.mutex.Unlock()
    for key := range msl.container {
        _, ok := other.container[key]
        if !ok {
            delete(msl.container, key)
        }
    }
}

注意,container中保存的key=sid, value = struct{}

至此,我们就把内存的MatchSids方法实现了,接下来实现磁盘的

disk 磁盘

disk.go

func (ds *diskSegment) QuerySeries(matcherList LabelMatcherList) ([]LabelList, error) {
    seriesIDList := ds.indexMap.MatchSids(ds.labelVs, matcherList)
    ret := make([]LabelList, 0)
    for _, seriesID := range seriesIDList {
        ret = append(ret, ds.indexMap.MatchLabels(ds.series[seriesID].Labels...))
    }
    return ret, nil
}

MatchSids()

func (dim *diskIndexMap) MatchSids(lvl *labelValueList, matcherList LabelMatcherList) []uint32 {
    dim.mutex.Lock()
    defer dim.mutex.Unlock()
    bitmapList := make([]*roaring.Bitmap, 0)
    for i := len(matcherList) - 1; i >= 0; i-- {
        bitmapTemp := make([]*roaring.Bitmap, 0)
        values := lvl.Match(matcherList[i])
        for _, value := range values {
            didIndex := dim.label2sids[joinSeprator(matcherList[i].Name, value)]
            if didIndex == nil || didIndex.list.IsEmpty() {
                continue
            }
            bitmapTemp = append(bitmapTemp, didIndex.list)
        }
        union := roaring.ParOr(4, bitmapTemp...)
        if union.IsEmpty() {
            return nil
        }
        bitmapList = append(bitmapList, union)
    }
    return roaring.ParAnd(4, bitmapList...).ToArray()
}

逻辑和上面memtable.go的一样,但是这儿通过roaing.bitmap进行存储,通过union := roaring.ParOr(4, bitmapTemp...) 执行并集,通过roaring.ParAnd(4, bitmapList...).ToArray()求交集

这样获取所有的sids后,memtable就是基于sids找到所有的series然后返回
memtable.go

func (m *memtable) QuerySeries(matcherList LabelMatcherList) ([]LabelList, error) {
    matchSeriesIds := m.indexMap.MatchSids(m.labelVs, matcherList)
    ret := make([]LabelList, 0)
    for _, seriesID := range matchSeriesIds {
        s, _ := m.segment.Load(seriesID)
        series := s.(*memSeries)
        ret = append(ret, series.labels)
    }
    return ret, nil
}

磁盘则是通过seriesID拿到labels索引,然后通过索引获取label的值,将其拼接成为Label返回,可能磁盘这儿说着有点点难懂,看下数据的流转流程就清晰了。

当我们在执行Load()方法将数据从磁盘中解码出来时,可以拿到meta.Labels, 然后让label2sids保存labelName和sids,labelOrdered保存索引和labelName

所以上面我们是通过seriesID获取labelOrdered的索引,然后通过labelOrdered获取labelName,其实这个labelName之前存储时就是 labelName+Value存储的,所以就可以获取label的值了

github:https://github.com/azhsmesos/...

本文由mdnice多平台发布

你可能感兴趣的:(后端)