从零实现一个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
}
  1. 获取当前的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/tsdb

本文由 mdnice 多平台发布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值