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中進行計算操作,可能對性能影響比較大。

 

獲取三個字段的信息


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM