lucene中的Filter

前言

用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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值