加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

solr使用自定义排序函数

(2015-08-27 17:53:33)
标签:

股票

我们在查询的时候,通过设置q_val_来实现自定义排序,_val_后面跟自定义的排序函数。

首先来看一下_val_

举个查询例子为q=title:hadoop _val_:count 这里顺便说下hadoop_val_中间的空格,由于solr默认是OR,所以这里是一个or组合的booleanQuery,如果是q=title:hadoop  AND _val_:count 则是一个有AND组合的BooleanQuery查询。
titlecount都是solr配置文件schema.xml里面的一个fieldtitle:hadoop很明细,搜索文档的field值包含hadoop的文档。假如

索引库里总共有5document 12345,其中doucument 25field包含hadoop值,那title:hadoop搜索到25两个document

_val_:count  
由于_val_代表的是functionQuery,由于我们索引库里的每个document都包含count域。所以solr会把所有库里的5document
ID取到,其实solr是直接读reader.maxDoc(),这里值为5

由于是ORsolr底层luence会对两者坐并集,然后对并集的doc ID 打分。关键就在这个打分,也是_val_:count的体现。
由于文档25在两个集合里都出现,所以,document 25的得分也是两个得分之和。title:hadoop返回的文档25的得分就是luence的评分公式计算出来的,而_val_:count是有FunctionQuery的内部类FunctionWeight来计算的,不过FunctionWeight计算权重时,idf的值为1,由FunctionWeight计算的值再乘以该documentcount的值,所以整个的查询结果是5document,但是25这个两个文档的得分会很高。但跟这个两个文档的count域的值有关。

同样如果是q=title:hadoop  AND _val_:count 逻辑于查询,那结果只有两个文档,因为luence会对其期交集,同样的是也会把相同的文档的得分相加。

顺便再说下,如果_val_:3这也,后面直接跟数据,solr就不要到索引库里去取指定域如上面count域的值了,而是直接为3,如果是跟fieldsolr会用到fieldCache,把该域的值取出来放到fieldCache中,下次取就不要再读索引库了。

以上一段来源 http://ronxin999.blog.163.com/blog/static/4221792020111013131992/


接下来我们自己定义一个函数distfunc(sortType,latitude,longitude)来实现根据排序类型,和距离综合排序的算法。

我们使用的solr lucene版本如下:

<</span>dependency>

            <</span>groupId>org.apache.solr</</span>groupId>

            <</span>artifactId>solr-core</</span>artifactId>

            <</span>version>4.7.2</</span>version>

            <</span>exclusions>

                <</span>exclusion>

                   <</span>groupId>org.restlet.jee</</span>groupId>

                   <</span>artifactId>org.restlet.ext.servlet</</span>artifactId>

                </</span>exclusion>

                <</span>exclusion>

                   <</span>groupId>org.restlet.jee</</span>groupId>

                   <</span>artifactId>org.restlet</</span>artifactId>

                </</span>exclusion>

            </</span>exclusions>

        </</span>dependency>

        <</span>dependency>

            <</span>groupId>org.apache.lucene</</span>groupId>

            <</span>artifactId>lucene-core</</span>artifactId>

            <</span>version>4.7.2</</span>version>

        </</span>dependency>

 

自定义函数需要自己实现两个方法分别继承ValueSourceParserValueSource,其中ValueSourceParser可以对自定义函数的入参进行解析,ValueSource里面可以自定义打分规则。

我们定义的distfunc(sortType,latitude,longitude)的功能是,根据位置信息和得分信息来进行综合排序。

我们建立索引的时候,自定义了一个存储位置信息的类型TeacherLocationlocations字段存储的是list< TeacherLocation >转化成jackson后的字符串,TeacherLocation如下:

 

 

package com.renren.teach.search;

 

 

public class TeacherLocation {

   

    private long id;

    private String location;

    private String latitude;

    private String longitude;

    private long teacherId;

    private int deleteFlag = 0;

   

    public TeacherLocation() {

    }

   

    public TeacherLocation(String location, String latitude, String longitude) {

        this.id = 0;

        this.teacherId = 0;

        this.location = location;

        this.latitude = latitude;

        this.longitude = longitude;

    }

}

 

 

Popularity字段存储的是排序需要考虑的另一个因素,得分信息。

自定义排序规则代码如下:

首先是自定义的TeachValueSourceParser

 

package com.renren.teach.search;

 

import org.apache.commons.lang.StringUtils;

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;

 

 

public class TeachValueSourceParser extends ValueSourceParser {//需要继承ValueSourceParser,重写parse方法

 

    public TeachValueSourceParser() {

        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 location = getValueSource(fp, "locations");//取得文档locations字段

        ValueSource popularity = getValueSource(fp, "popularity");//取得文档popularity字段

 

        Double latitude = null;

        if(!StringUtils.isEmpty(latitudeStr) && !latitudeStr.equals("null")){

            latitude = Double.valueOf(latitudeStr);

        }

        Double longitude = null;

        if(!StringUtils.isEmpty(longitudeStr) && !longitudeStr.equals("null")){

            longitude = Double.valueOf(longitudeStr);

        }

//将参数及需要的文档的值传给自定义的ValueSource方法,打分规则在自定义的ValueSource中定制

        DistanceFieldSource stringFieldSource = new DistanceFieldSource(

                location, popularity, latitude,

                longitude, sortType);

        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);

    }

}

 

 

接下来是自定义的DistanceFieldSource

 

package com.renren.teach.search;

 

import java.io.IOException;

import java.util.List;

import java.util.Map;

 

import org.apache.commons.lang.StringUtils;

import org.apache.lucene.index.AtomicReaderContext;

import org.apache.lucene.queries.function.FunctionValues;

import org.apache.lucene.queries.function.ValueSource;

import org.apache.lucene.queries.function.docvalues.FloatDocValues;

import org.codehaus.jackson.map.ObjectMapper;

import org.codehaus.jackson.type.TypeReference;

 

 

public class DistanceFieldSource extends ValueSource {//需要继承ValueSource ,重写getValues方法

 

    private ValueSource location;//文档的位置信息

 

    private ValueSource popularity; //文档的得分信息

 

    private Double latitude; //当前经度

 

    private Double longitude; //当前纬度

 

    private String sortType; //当前排序类型

 

    private static final Double MAX_DISTANCE = 20010d;

 

//通过构造方法取得经纬度信息排序类型以及文档的经纬度得分等

    public DistanceFieldSource(ValueSource location, ValueSource popularity,

            Double latitude, Double longitude, String sortType) {

        this.location = location;

        this.popularity = popularity;

        this.latitude = latitude;

        this.longitude = longitude;

        this.sortType = sortType;

    }

 

    @Override

    public FunctionValues getValues(Map context,

            AtomicReaderContext readerContext) throws IOException {

        final FunctionValues locationValues = location.getValues(context,

                readerContext);//文档位置信息

        final FunctionValues popularityValues = popularity.getValues(context,

                readerContext);//文档得分信息

        return new FloatDocValues(this) {

//重写floatVal方法,此处为打分排序规则

            @Override

            public float floatVal(int doc) {

                System.out.println("user location:" + latitude + "," + longitude);

 

                float curScore = 0.0f;

                float disScore = 0.0f;

 

                Double distance = 0.0;

                String locationStr = locationValues.strVal(doc); //locationValues取得当前doclocations的值,string类型

                if (null == latitude || null == longitude

                        || StringUtils.isEmpty(locationStr)) {

                    distance = MAX_DISTANCE;

                } else {

                    distance = MAX_DISTANCE;

                    Double lat1 = latitude;//当前用户的经纬度信息

                    Double lng1 = longitude;//当前用户的经纬度信息

                    try {

                        ObjectMapper mapper = new ObjectMapper();

                        List locations = mapper.readValue(

                                locationStr,

                                new TypeReference>() {});//Jackson string类型的位置信息转化list

//与当前位置计算,取最小距离

                        for (TeacherLocation location: locations) {

                            try {

                                Double lat2 = Double.valueOf(location

                                        .getLatitude());

                                Double lng2 = Double.valueOf(location

                                        .getLongitude());

                                Double distanceTmp = LatLonUtil.getDistance(

                                        lng1, lat1, lng2, lat2);

                                if (distanceTmp < distance) {

                                    distance = distanceTmp;

                                }

                            } catch (Exception e) {

                                // logger.error(e, e);

                                continue;

                            }

                        }

                    } catch (Exception e) {

                        // logger.error(e, e);

                    }

                }

 

                // 按距离排序

                if (null != sortType

                        && sortType.equals(SearchTypeEnum.DISTANCE.getType()

                                + "")) {

                    Double score = MAX_DISTANCE - distance;

                    disScore = score.floatValue();

                }

                // 综合排序考虑距离值及popularity的综合得分

                else if (null != sortType

                        && sortType.equals(SearchTypeEnum.POPULARITY.getType()

                                + "")) {

                    String popularityStr = popularityValues.strVal(doc);//取得当前文档的popularit值,string类型

                    curScore = Integer.valueOf(popularityStr);

                    System.out.println(popularityStr);

                    if (distance < 1) {

                        disScore = 10f;

                    } else if (distance < 5) {

                        disScore = 5f;

                    } else if (distance < 10) {

                        disScore = 3f;

                    } else {

                        disScore = 0f;

                    }

                }

                System.out.println("disScore:" + disScore + "," + "curScore:" + curScore);

                return disScore + curScore; //返回自己计算出的得分值

            }

 

            @Override

            public String toString(int doc) {

                return name() + '(' + locationValues.strVal(doc) + ','

                        + popularityValues.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 "DistanceField";

    }

}

 

以上就定义好了distfunc(sortType,latitude,longitude)函数。

将代码打包放到webapp/WEB-INF/lib下,

solrconfig.xml中加入声明

 

重启,就可以使用该函数了。

//searchDO为各种查询条件,queryStr为之前构件好的查询条件字符串

SolrQuery query = new SolrQuery();

String distParams = sortType + ",";

            if(searchDO.getLatitude() != null ){

                distParams += searchDO.getLatitude();

            } else{

                distParams += "null";

            }

            distParams += ",";

            if(searchDO.getLongitude() != null){

                distParams += searchDO.getLongitude();

            } else{

                distParams += "null";

            }

            queryStr += "AND _val_:\"distfunc(" + distParams + ")\"";

       

        query.setQuery(queryStr);

QueryResponse response = server.query(query);

 

妥妥哒~

还有一种使用方式,是在solrconfig.xml里自定义一个requestHandler,在里面使用distfunc方法,然后搜索时Request-Handler设置成自己定义的这个,不过这个最终没搞定怎么使,requestHandler里头好些参数还不清楚怎么配置。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有