倒排索引服务实现

131 阅读2分钟

服务由正排索引和倒排索引组成

正排索引

结构如下

type ForwardIndex struct {
    map[int]interface{}
    
}

正排索引比较简单,map类型,key是广告id,value 是ad

倒排索引

首先服务会维护一个全局的定向列表,如下所示

type FieldConfig struct {
    Name string
    Field string
    empty bool

}
FieldsConfigs = []FieldConfig{
{Name: "gender", Field:"Gender",emptyNone: true},
{Name: "location", Field:"Location",emptyNone: true},
{Name:"includePackage", Feild:"IncludePackage", emptyNone: true},
{Name: "excludePackage", Field:"ExcludePackage", emptyNone:false}

}

FieldConfig 里的field字段是用来反射结构体的字段 这个服务速度非常快,支持高并发,尽可能降低内存的使用,实现

  1. 数据全部维护在内存里,
  2. 写时复制,尽量避免使用锁
  3. bitmap ,降低内存使用
  4. sync.Pool

倒排索引的数据结构

type Field struct {
    name      string
    none      bitmap
    int64Map  bitmap
    stringMap bitmap
}

func(f *Field) add(key int, value interface()) bool
func(f *Feild) get(value interface{}) bitmap

func(f *Feild) get(value interface{}) bitmap {
    switch value.(type) {
        case int64:
            return f.getSingleForInt64(value.(int64))
    
    }

}

func (f *Field) getSingleForInt64(value int64) bitmap {
        // none 与int64 求并集
        b1 := f.int64Data[value]
        bnone := f.none
        return bitmapOr(b1, bnone)
}

func (f *Field) Add(key uint32, value interface{}) bool {
        // key是广告id
        // 如果值为空,表示,该广告未在找个field上做定向,如果该定向emptyNone =true, 则放到 none上,
        // 如果不为空,根据value 的type 映射,放到 int64Map, 或stringMap 里
	switch value.(type) {
	case int64:
		return f.addSingleForInt64(key, value.(int64))
	case string:
		return f.addSingleForString(key, value.(string))
	case []int64:
		return f.addArrayForInt64(key, value.([]int64))
	case []string:
		return f.addArrayForString(key, value.([]string))
	case nil && emptyNone:
		return f.addNone(key)
	}
	return false
}

type InvertedIndex struct {

    fields map[string]*Feild

}


将一个广告加入到倒排索引里

func (i *Inverted) Add(value interface{}) bool {
        ad := value.(*Ad)
	for _, conf := range AdInvertedFields {
		if !i.addField(conf, ad.AdId, ad) {
			return false
		}
	}
	return true
}

func (i *Inverted) addField(conf FieldConf, id uint32, data interface{}) bool {
        // 通过反射拿到广告对应filed的值,
        // 如果value不为空,就调用field.Add(id, value)
        // 如果value为空且empty=true, 就调用field.Add(id, nil)
	field := i.indexer.Field(conf.Name)
	if field == nil {
		return false
	}

	rValue := reflect.ValueOf(data).Elem().FieldByName(conf.Field)
	if !rValue.IsValid() {
		if conf.EmptyNone {
			return field.Add(id, nil)
		}
		return true
	}

	value := rValue.Interface()
	if conf.EmptyNone {
		return field.Add(id, value)
	}
	switch value.(type) {
	case []int64:
		if len(value.([]int64)) > 0 {
			return field.Add(id, value)
		}
	case []string:
		if len(value.([]string)) > 0 {
			return field.Add(id, value)
		}
	}
	return true
}

// 根据定向去倒排索引查找,
如果exclude为空,则各字段求交集
否则,对include 求交集得到A集合,对exclude求并集得到B集合,最后结果为A-B
func (d *DarwinMatch) searchInverted(indexer *advertisement.Inverted,
	include, exclude map[string]interface{}) []uint32 {

	if len(exclude) > 0 {
		d.searcher = indexer.GetSearchBox(indexing.RelationDifference,
			indexer.GetSearchDo(indexing.RelationIntersection, include),
			indexer.GetSearchDo(indexing.RelationUnion, exclude))
	} else {
		d.searcher = indexer.GetSearchDo(indexing.RelationIntersection, include)
	}
	return d.searcher.Do()
}

// 广告加入到倒排索引

func (i *Inverted) Add(a *db.Ad) bool {
	for _, conf := range FieldsConfigs {
		if !i.addField(conf, a.AdId, a) {
			return false
		}
	}
	return true
}