Solr 迟到的Payloads

– 没有了索引时的Boost,但你还有我啊!

1. What is this?

Payload在Lucene中表述是一个任意 byte[],它可以(只是可以不是必须)为它每个Term存储额外的信息到倒排索引里面。而且这些Payloads可以被用作Query的一部分来过滤,或者生成对应值用于打分,甚至替代文档的返回值。

Payload能干嘛呢

前面提到了,Payloads可以被用作Query的一部分来过滤,或者生成对应值用于打分,甚至替代文档的返回值。这个非常强大的东西,你理解起来非常容易,你直接当她是一个Hive中的JSON类型即可,如果还是有点陌生的话,直接当她是一个Map吧。即是可以keyvalue

到这里,你可能要问了。我们本来就是一个动态文档,原本就是键值对的式子,那要你干嘛呢?
其实,此时你应该把她当成一个nested document。后面会举这个例子,请继续往下阅读。

如果你应该理解上面说的,把她当成一个文档的子文档的话。其实她的功能应该很好理解,显而易见了。

  1. 更人性化的文档组织方式
  2. 用内嵌文档(子文档)的某个字段进行操作
    1. 排序
    2. Faceting
    3. 返回指定key的value
  3. 子文档flatMap

2. Show Cases

这个例子有个名字,它叫Per-Store Pricing,在 lucidworks 多博客文出现过,感觉非常好,先借来改改先用着。原例子用的相对比较稳定的价格作为示例,但我觉得用库存这个动态的属性作为示例会更加容易解释,但不需要过来多说明。

pre-setting :
这是一个在线连锁店的中央仓存系统,它会记录每个商品在每家的库存。由于店铺的位置和大小的原因,商品a不是所有店铺都会销售。
假定这是一个 Snapshot

  • 有了上面这个大前提之后,我们继续来设定一些假定的操作:

    1. 找出A店所有商品,并按库存量排序
    2. 找出A店库存在100-200之间的所有商品
    3. 想某些商品在某些店的库存情况
  • 针对上面设定和问题,我们尝试组织文档和定义操作。

    1. 再商品信息中加入库存信息,每家店铺都是一个独立的字段
      这种作法最大的问题是当然店铺数量很多的时候,一个文档的字段非常多,即是多少店铺就得冗余多少字段。
    2. 在库存信息中加入商品信息,每家店铺都是一个独立的文档
      这种作法即是文档数会很多,但是并不要紧,关键是想查商品的情况时,需要做groupBy的操作,这是比较耗性能的操作。
    3. 采用payload的方式,在商品信息加入店铺的库存信息作为payload
      这就是我们要讲的内容了。

2. Payloads in Solr

a. 如何在Solr上启用Payload

你把Payload说得这么好,那我怎么Solr里把它上用起来呢,让全世界都知道它的好。在Solr6.6之后,我们知道它是只是一个字段类型,那么其实很好理解的,即是在schema.xml多配一个字段类型咯,并把它加对应的字段即可了。

1. Change your schema.xml file

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

<field name="stock_dpi" type="payloads" indexed="true" stored="true"/>
  • 这里similarity默认是SchemaSimilarity,它决定payload_scoreQuery得分的关系。
  • encoder 的取值 ${float,integer}$

2. Add the payloaded term to the document

我们已经加了这么一个字段类型和字段了,接下来我们需要把文档带着payload信息文档提交给Solr进行索引。当然这也很简单,它就是我们配置的一个字段类型嘛,那么我们要求进行组织字段的值即可了。

按我们上面的配置(为简单起见我们就配置三个字段)


{
    "id":"123",
    "stock_dpi":"a|100 b|200",
    "name":"cafe"
}

b. Payload神器,三把斧

正因为这三把神器,从而真正牵引我来写这篇博客。其实非常简单,也非常实用。好了,我们开始吧。

1. payload() function

payload() 函数并不能作为查询条件,只能用于修改显示的结构作为伪字段出现在文档中。即只用在fl的条件,不能用于除fl之外的场景。

payload是对word而言,它是有位置的。即是同一个词在同个字段中的不同位置可以赋不同的payload值的。

payload()函数签名:

payload(field, term [,default[,min|max|average|first]])

函数参数说明
- f 必填,字段名
- v 必填,需要转化的Key
- def 选填, 当对应的key没有对应的value时的默认
- func 选填,当有一个key对应有多个value时,进什么计算,默认是average

Example

payloads = ‘a|1.0 a|2.0 a|3.0 b|0.1’

payload(payloads, a) = 2.0  
payload(payloads, c, -1.0) = -1.0   
payload(payloads, a, min) = 1.0     
payload(payloads, c, -1.0, min) = -1.0  

2. Query Function Parser

这里我们来介绍一下两个迟到Query Parser,PayloadScoreParser和PayloadCheckParser两个查询解释器。按理说它应该很早应该出现,即使不能跟Lucene4.x一起出现,但实在不至于到现在Solr6.6才出生。当然,在此之前有过很多民间的实现。因此这两个解释器的出现,使得Payload变得有意义、完整。

关于payload的索引存储原理这里我们只做简单的解释,不做深入的解析。
这些查询解析器利用term|payload在索引期间最终会翻译成SpanQuery,SpanNearQuery或者SpanTermQuery。也就是Term与payload其实是成对存在于索引表上的,跟坡度查询和短语查询差不多意思。

a. PayloadScoceParser : payload_score

简单的说,如果用某个子文档的某个字段的值来参与评分呢?这就是我们接下来要讨论的payload_score了。

这些与boost有几分相似了。多说一句,索引时boost已经Solr6.6弃用了。为何弃用Boost呢,我想你应该能理解,即是Boost的功能相当单一且不够灵活。当然啦,也是因为Payload的引用,使得Boost更加被弱化了。

我们已经payload字段是按一定的规则组织Term和Payload之间的关系的,如上例中我们是这样的组织:

stock_dpi = a|100 b|200 c|300 a|101 a|102 a|103

  • 其中等号左边为字段名,即是下面参数f需要指定的字段名
  • 等号右边是字段值,它是一系列键值对。即这里的每个键便是term,值为payload

参数说明:
- f 指定使用哪个payloads字段,必填。

  • v 拿哪个 keyterm)来查询,必填。

    相当于说,需要指哪个子文档的哪个字段,它里v即是这的哪个字段

  • func 它的计算结果作为payload_score的分数值,必填。

    payload function必须填,对payload的进行计算的函数。

  • includeSpanScore 是否把QueryFunction的查询拼入一般的查询条件中共同打分

    是把 payload_score 的查询函数作为SpanQuery的条件,参与原查询的打分规则。

    如果是,$score = score(Q_{org} + Q_{pay}) + pay$
    否则的话,$score = score(Q_{org}) + pay$

Example

q={!payload_score f=my_field_dpf v=some_term func=max}

q={!payload_score f=stock_dpi v=a func=average}

对func而言,它仅对f中的v有多值时,才会有意义。
1. {!payload_score f=payloads v=a func=min} = 1
2. {!payload_score f=payloads v=a func=max} = 3
3. {!payload_score f=payloads v=a func=average} = 2

b. PayloadCheckParser : payload_check

PayloadCheckParser提供的是一个针对Term-Payload检索功能,即是通过指定键值,搜索含有的所有文档。这么说可能不好理解,简单的说,指定了子文档的字段名和值,希望找到子文档的该字段名下的值能匹配上的所有文档。

因此,它跟普通的查询一样必须需要指定字段名,和对应的值。

参数说明
- f 指定字段名,必填。

  • payloads 指定payload, 支持多值,之间用空格分开。必填。

  • v 指定key,必填

Example

方式一:
{!payload_check f=words_dps payloads=”VERB NOUN”}searching stuff
方式二:
{!payload_check f=words_dps payloads=”VERB NOUN” v=searching stuff}

3. Faceting on numeric payloads

Faceting出生很早,是Lucene统计分析的基石,大家绝对不会陌生。Faceting在分析方面那么好、那么优秀,那么她能不能延伸到PayloadQuery呢?
其实facet.range只支持实际字段,而不支持伪字段。然而她又那么好,不忍舍弃,所以在现在版本得用一些比较偏门的方法,即是facet.query,对payload()再进行{!frange} query function查询来完成类facet.range查询。

后面版本可能会实现对payload进行facet.range,等着吧。

  • facet.query={!frange key=up_to_400 l=0 u=400}${computed_price} // includes price=400.00
  • facet.query={!frange key=above_400 l=400 incl=false}${computed_price} // excludes price=400.00

3. 回顾例子,Payload On Solr

前面提到了,Payloads可以被用作Query的一部分来过滤,或者生成对应值用于打分,甚至替代文档的返回值。

A. Solr下的原例1 per-store pricing

上面挖了坑,现在我们来把它填上。在Solr6.6之后的Solr如何优雅解决这个问题的呢。前面我们讨论per-store pricing问题时,已经给出结论,但我们并没有说明这个方案应当如何操作,接下来我们就来说说它应该如何操作。

在上面我们提出了这如下几问题,如今是时候把他解决掉了。
1. 找出商品A所有商铺,并按库存量排序
2. 找出A店库存在100-200之间的所有商品
3. 想某些商品在某些店的库存情况

现在我们按商品信息进行组织文档,然后把库存信息作为payloads字段类型加入到文档中。即是

{
    "id":"product00001"
    "name":"product_a",
    "price":100.0,
    "stock_dpi":"stock_a|100 stock_b|200 stock_c|300",
    ...
}

现在我们可以这么做
1. q=stock_dpi:stock_a&sort=payloads(stock_dpi, stock_a, 0, min) desc
2. facet.query={!frange key=up_to_400 l=0 u=400}payload(stock_dpi, stock_a, 0)

发布了30 篇原创文章 · 获赞 43 · 访问量 6万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览