有效载荷是整洁的,但是Solr的完整例子在哪里?


每当我在Solr中讨论有效载荷时,我都有点沮丧,因为我找不到一个例子,可以在一个地方给出所有的片段。所以我决定为Solr 4.0+创建一个(实际上,在撰写本文时是4.8.1,但这应该适用于所有4x代码行)。我们自己的格兰特·英格索尔展示了许多有用的片段how to use payloads in 2009在Lucene级别。

从那以后,有效载荷被添加到Solr…算是吧。在schema.xml中构造分析链时,可以使用delimited PayLoadTokenFilterFactory,它将获取带分隔符的有效负载标记,并将有效负载与术语一起存储。此字段类型和字段在标准分布中是均匀的。

然而,问题是你如何使用在Solr中查询时的有效负载?这篇文章提供了一个端到端的例子。

首先简单回顾一下

有效负载是将数值与术语相关联的一种方式。因此,每当查询中的一个术语与文档中的一个相匹配时,您也可以使用数值进行评分。有效载荷有多种用途,以下是其中几种:
  1. 词类。假设你想比形容词更重地衡量名词。抛开识别名词和形容词的问题,让我们假设你能。您可以关联一个权重,然后将其合并到评分中,对与搜索中的术语匹配的名词使用更大的权重。
  2. 试探性地发现了相关性,也就是“秘密酱汁”。您已经分析了使用模式,发现如果初始搜索短语包含单词“钓鱼”,用户很有可能会购买诱饵而不是深度探测器。在摄取的时候,每当你在描述你归类为“诱饵”的东西时发现“钓鱼”这个词,你就给这个词增加了一些分量。
  3. 每当您可以将任何行为与某些术语相关联时,您就可以在Solr文档的分数计算中更加重视这些术语。这种处理的计算量非常大,因此在搜索时无法执行。如果您可以通过添加有效负载来减轻索引时间的处理负担,那么您就可以使用计算这些相关性的结果,并且仍然拥有高性能的搜索。

步骤概述

记住,我要离开怎么你在这里做关联。
  1. 将有效负载添加到文档中的术语。
  2. 请更改您的schema.xml文件,以允许您使用该负载。
  3. 更改您的solrconfig.xml以识别您将要编写的新查询解析器。
  4. 为payloaded字段编写一个新的相似性类。
  5. 在查询中使用新的查询解析器。
如果这些步骤都那么难,那就没有了,但是在没有指导的情况下将所有部分连接起来可能会很痛苦。这是烹饪书。

将已加载付款的条款添加到文档。

这实际上是最简单的部分,只需使用管道分隔符。你的术语看起来像“钓鱼|5.0”。

更改您的schema.xml文件。

您的schema.xml文件将有两处更改。默认模式带有一个“有效负载”字段类型。这对于接收数据很好,但是我们需要做一个改变来使用它得分;向字段类型添加新的自定义相似性。您的<字段类型>将如下所示:

<fieldtype name="payloads" stored="false" indexed="true" class="solr.TextField" >
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="float"/>
</analyzer>
<similarity class="payloadexample.PayloadSimilarityFactory" />
</fieldtype>

相似性类将是您稍后将看到的一些自定义代码。它的工作是获取有效负载并使用它来影响文档的分数。Solr 4.x的一个特性是,您可以在单个字段上定义自定义相似性,这就是我们在这里所做的。

你还需要对模式做另外一个改变。在底部,你可能会看到一个关于习俗相似性的评论。添加此行:

<similarity class="solr.SchemaSimilarityFactory"/>

这将允许您在<字段类型>中找到相似性。

请更改solrconfig.xml文件。

到目前为止,一切顺利。但是你实际上是怎么做到的使用这个吗?事实证明,如果您不创建自己的解析器,默认的有效负载评分只是返回1.0f。因此,您需要创建一个实际使用该值的解析器。你需要做两个改变;定义jar的lib路径,并定义一个新的查询解析器。这看起来像:

<lib dir="path_to_jar_file_containing_custom_code" regex=".*\.jar">

and then:  

<queryParser name="myqp" class="payloadexample.PayloadQParserPlugin" />

为payloaded字段编写一个新的相似性类和查询解析器。

好的,这是代码。这是帖子最长的部分,请见谅。或者跳到最后,稍后复制/粘贴。在我的示例代码中,有两个文件放在同一个jar中。首先是PayloadQParserPlugin(请参见对solrconfig.xml的更改)。

package payloadexample;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.payloads.AveragePayloadFunction;
import org.apache.lucene.search.payloads.PayloadTermQuery;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.parser.QueryParser;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.QParser;
import org.apache.solr.search.QParserPlugin;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.search.SyntaxError;

// Just the factory class that doesn't do very much in this 
// case but is necessary for registration in solrconfig.xml. public class PayloadQParserPlugin extends QParserPlugin { @Override public void init(NamedList args) { // Might want to do something here if you want to preserve information for subsequent calls! } @Override public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { return new PayloadQParser(qstr, localParams, params, req); } } // The actual parser. Note that it relies heavily on the superclass class PayloadQParser extends QParser { PayloadQueryParser pqParser; public PayloadQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { super(qstr, localParams, params, req); } // This is kind of tricky. The deal here is that you do NOT
// want to get into all the process of parsing parentheses, // operators like AND/OR/NOT/+/- etc, it's difficult. So we'll
// let the default parsing do all this for us. // Eventually the complex logic will resolve to asking for
// fielded query, which we define in the PayloadQueryParser // below. @Override public Query parse() throws SyntaxError { String qstr = getString(); if (qstr == null || qstr.length() == 0) return null; String defaultField = getParam(CommonParams.DF); if (defaultField == null) { defaultField = getReq().getSchema().getDefaultSearchFieldName(); } pqParser = new PayloadQueryParser(this, defaultField); pqParser.setDefaultOperator (QueryParsing.getQueryParserDefaultOperator(getReq().getSchema(), getParam(QueryParsing.OP))); return pqParser.parse(qstr); } @Override public String[] getDefaultHighlightFields() { return pqParser == null ? new String[]{} : new String[] {pqParser.getDefaultField()}; } } // Here's the tricky bit. You let the methods defined in the
// superclass do the heavy lifting, parsing all the // parentheses/AND/OR/NOT/+/- whatever. Then, eventually, when
// all that's resolved down to a field and a term, and // BOOM, you're here at the simple "getFieldQuery" call. // NOTE: this is not suitable for phrase queries, the limitation
// here is that we're only evaluating payloads for // queries that can resolve to combinations of single word
// fielded queries. class PayloadQueryParser extends QueryParser { PayloadQueryParser(QParser parser, String defaultField) { super(parser.getReq().getCore().getSolrConfig().luceneMatchVersion, defaultField, parser); } @Override protected Query getFieldQuery(String field, String queryText, boolean quoted) throws SyntaxError { SchemaField sf = this.schema.getFieldOrNull(field); // Note that this will work for any field defined with the // <fieldType> of "payloads", not just the field "payloads". // One could easily parameterize this in the config files to // avoid hard-coding the values. if (sf != null && sf.getType().getTypeName().equalsIgnoreCase("payloads")) { return new PayloadTermQuery(new Term(field, queryText), new AveragePayloadFunction(), true); } return super.getFieldQuery(field, queryText, quoted); } }

AveragePayloadFunction()有什么用?想象一下,在同一个文档中有几个术语,每个术语都有不同的有效载荷。如果这些值的平均值是“正确的事情”。有一些预定义的有效负载函数(都源自PayloadFunction),在其他情况下,例如最小、最大,您也可以使用有效负载来“做正确的事情”。或者,如果你的需求不同,你可以自己写。

现在是PayloadSimilarityFactory

package payloadexample;

import org.apache.lucene.analysis.payloads.PayloadHelper;
import org.apache.lucene.search.similarities.DefaultSimilarity;
import org.apache.lucene.search.similarities.Similarity;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.schema.SimilarityFactory;

public class PayloadSimilarityFactory extends SimilarityFactory {
  @Override
  public void init(SolrParams params) {
    super.init(params);
  }

  @Override
  public Similarity getSimilarity() {
    return new PayloadSimilarity();
  }
}

class PayloadSimilarity extends DefaultSimilarity {

  //Here's where we actually decode the payload and return it.
  @Override
  public float scorePayload(int doc, int start, int end, BytesRef payload) {
    if (payload == null) return 1.0F;
    return PayloadHelper.decodeFloat(payload.bytes, payload.offset);
  }
}

记住,电脑是愚蠢的。你必须去的地方告诉计算机从术语中获取有效负载并使用它。这就是你在这里所做的一切。默认的有效载荷只返回1.0,所以如果你不这样做,你会想为什么你的有效载荷没有任何效果。

在查询中使用新的查询解析器。

首先,简要回顾一下我们迄今为止所做的工作:

  • 使用管道(|)分隔符将有效负载添加到输入文件中。
  • 更改配置文件,将有效负载放入索引中。
  • 为一个新的相似性类和查询解析器添加了自定义代码,以便对负载做些事情。
在这一点上,虽然,就像在索尔的其他地方,实际上使用这个信息是一个查询解析器实际调用它的问题。有效负载查询解析器就像任何其他查询解析器一样,edismax、标准、术语、短语、原始、嵌套等等CWiki docs)。它仍然必须被调用。

这其实很简单。这里的例子使用了defType,但是您也可以很容易地在一个请求处理程序中指定一个defType,在solrconfig.xml中使用这个查询解析器,在嵌套查询中使用它,等等。这是一个查询解析器,您可以像上面链接中提到的任何一个一样调用它。

http://localhost:8983/solr/collection1/query?defType=myqp&q=payloads:(electronics memory)

结论

我希望这个完整的例子能让其他人更容易地连接所有的部分,并使用来自Solr的有效载荷,而不必挖掘太多。但是,这种特定的代码有一些限制:
  • 它不能很好地处理短语。它会对整个短语进行“术语查询”,这不是你想要的。
  • 它需要一些代码投资,在Solr中有本地支持会更好。
  • 它并不适用于所有不同的查询解析器,它仅限于新定义的qparser。
    • 我和克里斯·霍斯特聊过天(他是我所有查询解析器相关事务的主管),这种方法有这样的优点,引用如下:
    • ..您可以将该qparser用于任何字段类型-添加有效负载的自定义类型、预分析字段、使用定界的PayloadTokenFilterFactory的文本字段,等等...
    • 另一种方法是创建一个自定义字段类型。它的优点(再次引用)是
    • 如果您采用自定义的字段类型方法,那么您将自动从大多数现有的查询解析器中获得基于有效负载的查询——但是您必须决定(结果是:约束)何时/如何/为什么有效负载被添加到字段类型逻辑的术语中
所以每个都有优点仅仅有时在索尔实现它们。嘘。还有另外50件我想做的事情。