Solr reRankQuery加自定义函数实现搜索二次排序

最近用到solr排序的复杂排序,系统最开始的排序时重写了文本相关分计算部分,增加新的排序逻辑后性能下降十分明显,考虑到用reRank和自定义函数的方法来解决,实际操作中碰到一些问题,自定义函数参考了http://blog.sina.com.cn/s/blog_65ae97720102vujw.html

然后再rerank测试通过,确实只会对头部指定的docs进行重新计算分数。

首先创建一个maven项目,pom依赖项如下,solr core使用6.6版本

<dependencies>
        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-core</artifactId>
            <version>6.6.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.restlet.jee</groupId>
                    <artifactId>org.restlet.ext.servlet</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.restlet.jee</groupId>
                    <artifactId>org.restlet</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>6.6.0</version>

        </dependency>
    </dependencies>

项目中增加自己的package,增加两个类文件,分别实现 ValueSourceParser和ValueSource

项目文件结构如下图

首先是继承ValueSourceParser的MyValueSourceParser 类,重写parse方法,return MyScoreSource类的实例。

MyValueSourceParser 类代码如下:

package com.wmf.customfunc;
/**
 * Created by wangmf on 2019/7/19.
 */
import org.apache.lucene.queries.function.ValueSource;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.FunctionQParser;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.search.ValueSourceParser;
//需要继承ValueSourceParser,重写parse方法
public class MyValueSourceParser extends ValueSourceParser {
    public MyValueSourceParser() {
        super();
    }
    @Override
    public ValueSource parse(FunctionQParser fp) throws SyntaxError {
//        String sortType = fp.parseArg(); //这里是从FunctionQParser取到第一个参数,也就是我们自定义函数里的第一个参数 sortType
//        String latitudeStr = fp.parseArg();//第二个参数latitude
//        String longitudeStr = fp.parseArg();//第三个参数longitude
        //本例没用到参数
     //获取三个字段的信息
ValueSource intval1 = getValueSource(fp,"intval1"); ValueSource intval2 = getValueSource(fp,"intval2"); ValueSource floatval1 = getValueSource(fp,"floatval1"); //将参数及需要的文档的值传给自定义的ValueSource方法,打分规则在自定义的ValueSource中定制 MyScoreSource stringFieldSource = new MyScoreSource(intval1,intval2,floatval1); return stringFieldSource; } //该方法是根据字段名,从FunctionQParser得到文档该字段的相关信息 public ValueSource getValueSource(FunctionQParser fp, String arg) { if (arg == null) return null; SchemaField f = fp.getReq().getSchema().getField(arg); return f.getType().getValueSource(f, fp); } }

MyScoreSource类实现评分计算,代码如下

package com.wmf.customfunc;

import java.io.IOException;
import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.FloatDocValues;
/**
 * Created by wangmf on 2019/7/19.
 */
public class MyScoreSource extends ValueSource {//需要继承ValueSource ,重写getValues方法

    private ValueSource intval1;
    private ValueSource intval2;
    private ValueSource floatval1;
    //通过构造方法的参数传递取得filed中的值
    public MyScoreSource(ValueSource intVal1, ValueSource intVal2, ValueSource floatVal1) {
        this.intval1 = intVal1;
        this.intval2 = intVal2;
        this.floatval1 = floatVal1;
    }
    @Override
    public FunctionValues getValues(Map context,
                                    LeafReaderContext readerContext) throws IOException {
        final FunctionValues IntValue1 = intval1.getValues(context,readerContext);
        final FunctionValues IntValue2 = intval2.getValues(context,readerContext);
        final FunctionValues FloatValue1 = floatval1.getValues(context,readerContext);
        return new FloatDocValues(this) {
            //重写floatVal方法,此处为打分排序规则
            @Override
            public float floatVal(int doc) {
                String strInt1 = IntValue1.strVal(doc);
                String strInt2 = IntValue2.strVal(doc);
                String strFloat1 = FloatValue1.strVal(doc);
                float sc1 = Float.parseFloat(strInt1);
                float sc2 = Float.parseFloat(strInt2);
                float sc3 = Float.parseFloat(strFloat1);

                System.out.println("Int1:" + strInt1 + "," + "Int2:" + strInt2 + "," + "Float1:" + strFloat1);
                return sc1 + sc2 + sc3; //返回自己计算出的得分值
            }

            @Override
            public String toString(int doc) {
                return name() + '(' + IntValue1.strVal(doc) + ',' + IntValue2.strVal(doc) + ',' + FloatValue1.strVal(doc) + ')';
            }
        };
    }
    @Override
    public boolean equals(Object o) {
        return true;
    }
    @Override
    public int hashCode() {
        return 0;
    }
    @Override
    public String description() {
        return name();
    }
    public String name() {
        return "Myfunction";
    }
}

测试用的逻辑,实际很简单,将三个字段:intval1,intval2,floatval1的值相加作为新的分数,实际业务中逻辑会更复杂,但逻辑都可以在实例new FloatDocValues(this)写,根据不同的返回,重写floatVal方法,或者用其他继承了FloatDocValues类或者DoubleDocValues、StrDocValues等类实现逻辑。

代码完成后打包,将生成的包复制到solr中,如果是在使用solr自带的jetty作为web容器,则将打好的jar包复制到 server/solr-webapp/webapp/WEB-INF/lib/目录下,然后修改对应对应core的solrconfig.xml

修改内容如下:

<valueSourceParser name="myfunc"  class="com.wmf.customfunc.MyValueSourceParser" />

    name就是我们再调用时需要使用的函数名,通常我们在查询时想要得到函数计算值,可以直接在fl中增加该函数

http://solrserver/solr/core_demo/select?fl=*,_val_:myfunc()&indent=on&q=*:*&wt=json

    得到结果如下

    结合solr的reRankQuery可以实现在文本相关的头部docs中进行排序,注意,在rerank查询时{}中的内容大小写敏感

http://solrserver/solr/core_demo/select?q=*:*&wt=json&sort=intval1 desc&rq={!rerank reRankQuery=$rqq reRankDocs=2 reRankWeight=1.0}&rqq={!func}myfunc()&
fl=*,score,_val_:myfunc()&indent=on

该查询实现:查询所有文档,并按照intval1字段进行倒序排序,再在前面的两个doc,按照我们自定义的函数myfunc计算的分数,重新进行年排序,返回结果如下图

    这里的逻辑是在reRankQuery中计算的分数按照reRankWeight的权重和第一次查询的score相加后计算新的分数,计算式只针对reRankDocs的指定的头部数量的doc进行,得到新的分数score。从上图可以看到前面两个doc的score是函数计算的分数*1(权重是1.0)+1(原有的分数)。从第三条开始score只有第一次查询的1.0。从而在进行第二次查询是实现自定义的逻辑。

    另外如果要按照计算的分数排序,使用sort={!func}myfunc() desc,进行过滤,应该使用solr的frange命令{!frange l=13}myfunc() 过滤小于13分的doc。

注:除了在reRank中使用外,其他查询、排序、过滤都会在召回的所有doc中进行计算操作,可能对性能影响比较大。

 

获取三个字段的信息

posted on 2019-07-19 18:21  nobody  阅读(1588)  评论(0编辑  收藏  举报

导航