lucene中的Filter

来源:互联网 发布:国际网络购物平台 编辑:程序博客网 时间:2024/05/21 01:57

前言

用Lucene一定不能不知道Filter,Filter在合适的场景下能大大提升搜索性能

背景

最近在折腾solr,这个3年前“玩过”的东西,现在又来玩了,3年前是瞎比玩,只知道最上层的一些接口,却不知其所以然,而现在的目标就是要把solr以及lucene底层的核心代码都分析一遍,并成功的部署一套电商搜索解决方案。

Filter逻辑

Filter的构造逻辑其实本身和Query的构造逻辑差距不大,唯一一点不一样的就是Query之后会在collector中进行打分,并使用堆来进行一个结果的取舍。
那么在lucene中Filter是怎么用的呢?首先需要知道Filter的功能,那就是通过某个条件把所有符合这个条件的docid拿到, 然后在构造scorer的时候把这些符合条件docid传过来,然后遍历这些符合条件的docid,通过query对应的相关信息,例如similarity等对这个doc打分。 所以说,如果你的filter能够把结果限制在很少的范围内的话,那么即使你的similarity或者是customScoreProvider稍微复杂点,那也是可以接受的。
调用层次:
1. Filter和Query都传给searcher
2. 把Filter和Query通过wrapFilter构造出FilteredQuery,通过filter条件过滤docid的逻辑就在FilteredQuery中:

    public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder, boolean topScorer, final Bits acceptDocs) throws IOException {        assert filter != null;        final DocIdSet filterDocIdSet = filter.getDocIdSet(context, acceptDocs);        if (filterDocIdSet == null) {          // this means the filter does not accept any documents.          return null;        }        return strategy.filteredScorer(context, scoreDocsInOrder, topScorer, weight, filterDocIdSet);      }
  1. Scorer在collector文档的时候,只要扫一遍通过filter query筛选出来的候选集即可
 public void score(Collector collector) throws IOException {    collector.setScorer(this);    int doc;    while ((doc = nextDoc()) != NO_MORE_DOCS) {      collector.collect(doc);    }  }

SOLR

那么在solr里的filter query是怎么玩的呢?

  1. solr里的fq参数会被searchHandler中的QueryComponent.prepare方法解析,并且构造这些field:value对应的query存到QueryComponent的rb(ResponseBuilder)中,并把rb存放到见下代码:
String[] fqs = req.getParams().getParams(CommonParams.FQ);if (fqs!=null && fqs.length!=0) {  List<Query> filters = rb.getFilters();  if (filters==null) {    filters = new ArrayList<Query>(fqs.length);  }  for (String fq : fqs) {    if (fq != null && fq.trim().length()!=0) {      QParser fqp = QParser.getParser(fq, null, req);      filters.add(fqp.getQuery());    }  }  // only set the filters if they are not empty otherwise  // fq=&someotherParam= will trigger all docs filter for every request   // if filter cache is disabled  if (!filters.isEmpty()) {    rb.setFilters( filters );  }}
  1. QueryComponent的prepare进行完后,执行process,进行搜索,这里把rb中的filter,query等一系列参数传给SolrIndexSearcher.QueryCommand,然后执行SolrIndexSearcher的search方法
SolrIndexSearcher.QueryCommand cmd = rb.getQueryCommand();。。。。searcher.search(result,cmd);
  1. 然后在SolrIndexSearcher中解析cmd中的filter,其中所有filter都存放在一个以Query类为模板的List中。然后在getDocListNC中调用getProcessedFilter,这个函数是干什么的呢,就是把每个filter对应的Query解析成一个个DocSet,然后根据这些filter的AND或者OR的关系去对这些DocSet取交集或者并集,这样就生成了一个候选的DocSet,大大减小了待查询打分的候选集。见getProcessedFilter的部分代码:
for (Query q : queries) {    if (q instanceof ExtendedQuery) {        ExtendedQuery eq = (ExtendedQuery) q;        if (!eq.getCache()) {            if (eq.getCost() >= 100 && eq instanceof PostFilter) {                if (postFilters == null)                    postFilters = new ArrayList<Query>(sets.length - end);                postFilters.add(q);            } else {                if (notCached == null)                    notCached = new ArrayList<Query>(sets.length - end);                notCached.add(q);            }            continue;        }    }    Query posQuery = QueryUtils.getAbs(q);    sets[end] = getPositiveDocSet(posQuery);    // Negative query if absolute value different from original    if (q == posQuery) {        neg[end] = false;        // keep track of the smallest positive set.        // This optimization is only worth it if size() is cached, which        // it would        // be if we don't do any set operations.        int sz = sets[end].size();        if (sz < smallestCount) {            smallestCount = sz;            smallestIndex = end;            answer = sets[end];        }    } else {        neg[end] = true;    }    end++;}

这里还有个比较有意思的地方,对于每个Filter,要先看看它是不是负的,即”-brand_name:宝马”就是所有品牌不是宝马的内容,这里的方法是先取这个Filter的“绝对值”,即“brand_name:宝马”得到DocSet A,然后通过matchAllDocsQuery得到包含所有文档的DocSet B,然后B.andNot(A)就可以把B中的A的内容排除掉,实现了取反的逻辑。

至此,Filter Query的任务也就告一段落了,不过还有一些Cache相关的东西

Filter Cache

0 0
原创粉丝点击