`
suichangkele
  • 浏览: 200319 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

solr中对于关键字置顶(竞价排名)、拉黑的源码实现已经实例讲解(一)

    博客分类:
  • solr
阅读更多
 

工作中用到了关键词置顶、拉黑的操作,自己毫无办法,考虑了很久打算用payload,但是又来在一个研究lucene源码的群中某个小伙伴给我提示说solr中已经为我们实现了这个功能,顿时大喜,马上百度了一下,然后内心很激动,solr真的太好用了,都为我们考虑到了。不过这远远不够,还有更多的事情需要做,不明白他的实现原理,只能猜,一遍一遍的试错,成本太高,所以还是拿来源码看吧。(对于没有对solr的置顶或者竞价排名从来没有接触过的小伙伴,麻烦先百度下“solr 竞价排名”去看看他的基本的配置文件,好心理有个数,我的这篇博客不是入门级的,不适合从头开始尴尬

 

      先说一下什么是关键字置顶,在我们使用百度的时候,当输入一个词,可能会有广告出现,而且他们都是排在最前面的,这些就是关键词置顶,或者叫做竞价排名。他的实现并不是使用boost来实现的,如果使用boost的话对于任何的词都会出现在前面,而百度中只是在搜索某些词的时候才会出现关联的广告,所以必须寻求另一种办法来实现。先说一下,我在写这篇博客时使用的solr是4.7.2.公司就是使用的这个。    在solr中如果想要实现这个功能的话需要使用elevator这个searchComponent,这个searchcomponent在solr的solrConfig是默认存在的,在/elevate这个requestHandler中是开启的,但是在/select这个requestHandler中默认是不开启的,鉴于我们都是使用/select这个requestHandler,所以我们在/select中添加这个component,在/select的最后添加

 <arr name="last-components">
      <str>elevator</str>
 </arr>

 这样就算是打开了,我们先看一下elevator这个searchComponent:

  <searchComponent name="elevator" class="solr.QueryElevationComponent" >
    <str name="queryFieldType">string</str>    <!-- 根据这个域的类型找到分词器,在搜索的时候用来对搜索的词进行分词,可以先不用管他,等会再源码中我会讲解的-->
    <str name="config-file">elevate.xml</str>  <!-- 这个是指定配置文件的来源,也即是conf下的elevate.xml -->
  </searchComponent>

 在使用置顶功能时,必须有一个配置文件,用于说明当搜什么的时候将哪个doc置顶,在solr的配置文件中就存在elevator.xml

<elevate>
 <query text="foo bar">  <!--这个不好用来做例子,要看在elevator中定义的queryFieldType,对于不同的配置,是不同的,一会看了源码就懂了,下面的更好理解一些-->
  <doc id="1" />
  <doc id="2" />
  <doc id="3" />
 </query>
 
 <query text="hello">  <!--这个表示在搜hello的时候,将id是1的doc置顶,而不显示ID是IW-02的-->
   <doc id="1" />  <!-- put the actual document at the top -->
   <doc id="IW-02" exclude="true" /> <!-- exclude this document -->
 </query>
 </elevate>

 看完了这个,就能大概明白了,在搜hello的时候要把id是1的排在前面,但是不显示id是IW-02的,因为他的后面是exclude=true,但是这是不准确的,甚至是错误的,等看完源码就懂了。

 

下面进入源码阶段,在上面的xml的配置中可以发现,在solr4.7中,elevator这个的封装类是QueryElevationComponent,所以我们进入到这个类的源码,先看一下inform方法,他在初始化elevator这个searchComponent的时候调用:

 /** 加载searchComponent,读取elevator.xml到内存中,优先从zk中读取,在从配置文件中读取,如果读取到了就会读取一次 */
  @Override
  public void inform(SolrCore core) {
    IndexSchema schema = core.getLatestSchema();
    String a = initArgs.get(FIELD_TYPE);//读取这个searchComponent配置的queryFieldType,initArgs就是用来封装我们在定义searchComponent的时候配置的参数。
    if (a != null) {
      FieldType ft = schema.getFieldTypes().get(a);//根据a确定fieldType,也就是域的类型
      if (ft == null) {
        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
            "Unknown FieldType: '" + a + "' used in QueryElevationComponent");
      }
      analyzer = ft.getQueryAnalyzer();//根据fieldType获得analyzer。
    }
    
    SchemaField sf = schema.getUniqueKeyField();//获得id的域
    if (sf == null) {
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
          "QueryElevationComponent requires the schema to have a uniqueKeyField.");
    }
    idSchemaFT = sf.getType();//id的fieldType
    idField = sf.getName();//id的域的名字
    
    // register the EditorialMarkerFactory   这个小块我没有细看
    String excludeName = initArgs.get(QueryElevationParams.EXCLUDE_MARKER_FIELD_NAME, "excluded");//获得表示排除的关键字,默认是excluded,使用默认即可
    if (excludeName == null || excludeName.equals("") == true) {
      excludeName = "excluded";
    }
    ExcludedMarkerFactory excludedMarkerFactory = new ExcludedMarkerFactory();
    core.addTransformerFactory(excludeName, excludedMarkerFactory);
    ElevatedMarkerFactory elevatedMarkerFactory = new ElevatedMarkerFactory();
    String markerName = initArgs.get(QueryElevationParams.EDITORIAL_MARKER_FIELD_NAME, "elevated");
    if (markerName == null || markerName.equals("") == true) {
      markerName = "elevated";
    }
    core.addTransformerFactory(markerName, elevatedMarkerFactory);
    
    forceElevation = initArgs.getBool(QueryElevationParams.FORCE_ELEVATION, forceElevation);//他的意思是如果我们在请求的参数中指定了sort,还要不要将我们要置顶的doc置顶,因为他们可能不符合条件,true表示置顶。
    try {
      synchronized (elevationCache) {
        elevationCache.clear();
        String f = initArgs.get(CONFIG_FILE);//获得配置文件,也就是elevator.xml
        if (f == null) {
          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
              "QueryElevationComponent must specify argument: '" + CONFIG_FILE + "' -- path to elevate.xml");
        }
        boolean exists = false;
        
        // check if using ZooKeeper,检查是否使用了zk,如果是的话从zk上读取配置文件,也就是elevator.xml,因为在solrCloud的情况下,配置文件就是在zk上
        ZkController zkController = core.getCoreDescriptor().getCoreContainer().getZkController();
        if (zkController != null) {// 从zk的配置中读取f文件
          exists = zkController.configFileExists(zkController.getZkStateReader()
              .readConfigName(core.getCoreDescriptor().getCloudDescriptor().getCollectionName()), f);
        } else {//如果不是使用solrCloud,从本地的配置中读取,则放入的是null
          File fC = new File(core.getResourceLoader().getConfigDir(), f);//从配置文件,也就是conf目录下查找
          File fD = new File(core.getDataDir(), f);//从data,也就是solrHome下的索引中查找
          if (fC.exists() == fD.exists()) {//如果同时存在或者都不存在,则报错
            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
                "QueryElevationComponent missing config file: '" + f + "\n" + "either: " + fC.getAbsolutePath() + " or "
                    + fD.getAbsolutePath() + " must exist, but not both.");
          }
          if (fC.exists()) {//如果在conf下存在,则将封装的配置文件放入到elevationCache中,是一个map,key是indexRader,因为可能会在indexReader发生变化后重新加载elevator.xml(仅仅是可能,等会就看到了,有时候不会重新加载)
            exists = true;
            log.info("Loading QueryElevation from: " + fC.getAbsolutePath());
            Config cfg = new Config(core.getResourceLoader(), f);
            elevationCache.put(null, loadElevationMap(cfg));//在loadElevationMap中读取配置文件封装为一个java对象,放入到map中,记住key是null,不是一个indexReader,稍后可以发现这种情况下是只加载一次配置文件。
          }
        }
        
        // 如果上面的没有读取到,则从data中读取。
        // in other words, we think this is in the data dir, not the conf dir
        if (!exists) {
          // preload the first data
          RefCounted<SolrIndexSearcher> searchHolder = null;
          try {
            searchHolder = core.getNewestSearcher(false);
            IndexReader reader = searchHolder.get().getIndexReader();
            getElevationMap(reader, core);//这个方法中会读取配置文件,然后将配置文件放入evevationCache中(此处不贴代码了),此时的key不是null,而是真正的indexReader,这种情况在indexReader发生变化时会重新加载的。
          } finally {
            if (searchHolder != null) searchHolder.decref();
          }
        }
      }
    } catch (Exception ex) {
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error initializing QueryElevationComponent.", ex);
    }
  }

 

 

看完上述代码后,我们再看一下这个searchComponent再处理请求的时候的操作,看看prepare方法和process方法

 // 从solrj发情的每一次查询请求,都先处理这个方法
  public void prepare(ResponseBuilder rb) throws IOException {
    
    SolrQueryRequest req = rb.req;//请求
    SolrParams params = req.getParams();//请求中封装的参数
    // A runtime param can skip
    if (!params.getBool(QueryElevationParams.ENABLE, true)) {//如果参数中没有开启enableElevation,则不进行操作,on/true/yes都算开启了,看来要使用置顶功能,必须添加enableElevation=on才会起作用!
      return;
    }
    //exclusive表示是否只返回设置的要置顶的document,而不再从solr中查找其他符合条件的document。默认是false,如果要开启则设置exclusive=on即可
    boolean exclusive = params.getBool(QueryElevationParams.EXCLUSIVE, false);
    // A runtime parameter can alter the config value for forceElevation
    boolean force = params.getBool(QueryElevationParams.FORCE_ELEVATION, forceElevation);//是否强制排序,他的是如果我们在参数中设置了sort,要不要仍然将置顶的那些docuement放在开头,ture表示仍然置顶
    boolean markExcludes = params.getBool(QueryElevationParams.MARK_EXCLUDES, false);//是否在返回的document的结果中不删除置顶为exclude的document,而仅仅是用一个字段标记这些document。
    //下面的这两个的意思是我们可以不使用配置的elevator中的规定,而是在查询时使用从请求中获得的要置顶或者删除的id,太好了,这个会极大的简化我们的操作。
    String boostStr = params.get(QueryElevationParams.IDS); //请求中指定的要置顶的id,使用elevateIds=1,2,3表示,用英文逗号分隔
    String exStr = params.get(QueryElevationParams.EXCLUDE);//请求中指定的排除的id,使用excludeIds表示,同上
    
    Query query = rb.getQuery();//在request中的query
    SolrParams localParams = rb.getQparser().getLocalParams();
    String qstr = localParams == null ? rb.getQueryString() : localParams.get(QueryParsing.V);//在请求中的字符串,如果我输入的是title:aa,那么就是title:aa,并不是aa,所以如果你在elevator.xml中配置的是aa,那么如果从solrj中输入的是title:aa,那么配置文件将不会起作用。
    if (query == null || qstr == null) {
      return;
    }
    
    ElevationObj booster = null;//最终使用的booster,可能来自于请求,也就是上面的boostStr和exStr,也可能来自于配置文件(比如zk或者data中的)
    try {
      
      if (boostStr != null || exStr != null) {//如果在请求中指定了,则使用请求的,不会使用配置的
        List<String> boosts = (boostStr != null) ? StrUtils.splitSmart(boostStr, ",", true) : new ArrayList<String>(0);//可以看出用英文逗号分隔
        List<String> excludes = (exStr != null) ? StrUtils.splitSmart(exStr, ",", true) : new ArrayList<String>(0);
        booster = new ElevationObj(qstr, boosts, excludes);//将从参数中传来的id封装为一个elevationObj。
      } else {//如果没有从请求中传过来,使用配置的,也就是优先使用配置的
        IndexReader reader = req.getSearcher().getIndexReader();
        qstr = getAnalyzedQuery(qstr);//进行分词,这个地方很关键,要对传过来的词进行分词处理,经过分词之后,得到的结果并不一定是之前设置的词,所以导致在elevator.xml中配置的失效,在上面已经说了,我看了看这个getAnalyzerdQuery方法,他是将分的多个term串联起来的,但是没什么鸟用。
        booster = getElevationMap(reader, req.getCore()).get(qstr);//使用当前的indexReader获得ElevatorObj,等会看这个方法的代码。
      }
    } catch (Exception ex) {
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error loading elevation", ex);
    }
    
    if (booster != null) {
      
      rb.req.getContext().put(BOOSTED, booster.ids);//要指定的id
      rb.req.getContext().put(BOOSTED_PRIORITY, booster.priority);//这个priority指定要排序的document的顺序
      
      // Change the query to insert forced documents
      if (exclusive == true) {//如果仅仅是返回那些指定的document
        // we only want these results
        rb.setQuery(booster.include);//include就是那些要置顶的document形成的query,他也是一个booleanquery,等会上代码。
      } else {//重新设置这次请求的query
        BooleanQuery newq = new BooleanQuery(true);
        newq.add(query, BooleanClause.Occur.SHOULD);//添加真正的query
        newq.add(booster.include, BooleanClause.Occur.SHOULD);//要置顶的id形成的query,
        if (booster.exclude != null) {//
          if (markExcludes == false) {//对于排除的id,如果不是打标记,也就是直接删除,
            for (TermQuery tq : booster.exclude) {
              newq.add(new BooleanClause(tq, BooleanClause.Occur.MUST_NOT));
            }
          } else {//设置为打标记,但是不在结果中删除,这个我没有看
            // we are only going to mark items as excluded, not actually exclude them. This works
            // with the EditorialMarkerFactory
            rb.req.getContext().put(EXCLUDED, booster.excludeIds);
          }
        }
        rb.setQuery(newq);//重新设置query
      }
      
      //下面是要排序的部分,很关键
      ElevationComparatorSource comparator = new ElevationComparatorSource(booster);//这个类用于形成一个Comparator,用于排序,他会将置顶的那些docuemnt放在前面。
      // if the sort is 'score desc' use a custom sorting method to
      // insert documents in their proper place
      SortSpec sortSpec = rb.getSortSpec();
      
      if (sortSpec.getSort() == null) {
        sortSpec.setSortAndFields(new Sort(new SortField[] {new SortField("_elevate_", comparator, true),//这个域的名字_elevate_没有关系,关键是comparator,他指定了排序的规则,等会看看他的代码,新产生的排序规则是先按照compartor排序,然后再按照score进行排序,置顶功能的关键就是这里。
            new SortField(null, SortField.Type.SCORE, false)}), Arrays.asList(new SchemaField[2]));
      } else {
        // Check if the sort is based on score
        SortSpec modSortSpec = this.modifySortSpec(sortSpec, force, comparator);//如果已经指定了,则根据是否force进行修改。
        if (null != modSortSpec) {
          rb.setSortSpec(modSortSpec);
        }
      }
      .....下面的没有贴
  
  }

 

 

上面还留有几个任务,还有很多的方法和示例没有写,我写在了下一篇博客中。

 

 

 

分享到:
评论

相关推荐

    solr-6.2.0源码

    源码中包含了丰富的注释和示例,帮助开发者深入理解Solr的设计思想和实现细节。 总结来说,Solr 6.2.0是一个强大的全文搜索引擎,它的分布式特性、实时性以及丰富的功能使得它成为企业级搜索应用的理想选择。通过...

    solr(solr-9.0.0-src.tgz)源码

    源码分析是深入理解一个软件系统工作原理的重要途径,对于Solr这样的复杂系统尤其如此。这里我们将围绕"solr-9.0.0-src.tgz"这个源码包,详细探讨其主要组成部分、核心功能以及开发过程中的关键知识点。 1. **Solr...

    Solr 4.0 源代码实例

    源代码实例是学习Solr内部工作原理和自定义功能的关键资源,尤其对于开发者而言,深入理解源码有助于提升系统的优化和扩展能力。 在Solr 4.0版本中,`solr.xml`是Solr的核心配置文件,它定义了Solr实例的基本设置,...

    solr 4.10源码

    这个源码包包含了Solr 4.10.4的所有源代码,对于开发者来说,这是一个深入了解Solr工作原理、定制功能以及进行二次开发的重要资源。 1. **Solr简介** Apache Solr是一个基于Lucene的全文检索服务,提供了一个高效...

    solr5的ik中文分词器源码

    Solr5是一款强大的开源搜索引擎,它提供了全文检索、命中高亮、分类聚类等多种功能,广泛应用于企业级的信息检索系统。在处理中文文本时,一个关键的组件就是中文分词器,而IK(Intelligent Chinese)分词器是Solr...

    JAVA+Solr分词项目工程实例Java实用源码整理learns

    标题中的"JAVA+Solr分词项目工程实例Java实用源码整理learns"指的是一个基于Java编程语言并结合Solr搜索引擎的项目实例。这个项目主要关注于文本处理和信息检索,利用Solr的分词功能来提升搜索效率和准确性。Solr是...

    solr6.6.0源码

    这个源码包包含了 Solr 的所有源代码,对于理解 Solr 的工作原理、进行二次开发或者定制化配置有着非常重要的意义。接下来,我们将深入探讨 Solr 6.6.0 中的一些关键知识点。 一、Solr 架构与组件 Solr 的核心架构...

    solr-4.5源码包

    在这个源码包中,我们可以深入理解Solr的工作原理以及其核心组件的实现。 首先,让我们了解Solr的基本架构。Solr基于Lucene库构建,Lucene是一个高性能、全文本检索库。Solr在其之上添加了分布式处理、集群管理、...

    JAVA+Solr分词项目工程实例Java源码

    这个项目工程实例是关于如何使用Java与Solr进行集成,实现分词搜索功能的示例。Solr提供了强大的文本分析、索引和查询能力,广泛应用于内容管理系统、电子商务网站、新闻门户等场景。 1. **Solr简介** - Solr是...

    solr源码及文档

    solr全文检索,里面包含文档,源代码,jar包,使用的是solr4.2,东西比较全,安装文档就能跑起来,,适合参考借鉴

    solr教程+实例

    Solr教程与实例详解 Apache Solr是一款开源的企业级全文搜索引擎,由Apache软件基金会开发,基于Java语言,具有高效、可扩展的特点。它为大型、分布式搜索应用提供了强大的支持,包括文档检索、拼写建议、高亮显示...

    全文检索(solr)实例

    本实例将详细介绍如何在本地环境中部署并运行一个Solr实例。 **一、Solr概述** Apache Solr是基于Java开发的,能够处理大量数据的高性能搜索平台。它提供了分布式、可扩展、实时和近实时搜索功能。Solr的核心特性...

    Solr项目源码及solr资源包

    Solr项目源码及solr资源包是一个针对搜索引擎平台Apache Solr的学习与实践资源集合,主要结合了Spring Data Solr框架进行操作。这个项目旨在帮助开发者更好地理解和运用Solr进行数据索引和检索。让我们详细地探讨...

    solr(中文分词器)集群

    在Solr集群中,每个Solr实例被称为一个"SolrCore",每个SolrCore可以看作是一个独立的搜索引擎。通过Zookeeper,Solr集群可以实现文档的分布式存储和检索,确保高可用性和数据的一致性。 构建Solr集群的第一步是...

    java进阶Solr从基础到实战

    在本套课程中,我们将全面的讲解Solr,从Solr基础到Solr高级,再到项目实战,基本上涵盖了Solr中所有的知识点。 主讲内容 章节一:Solr基础(上) 1. 环境搭建 2. 核心讲解 3. 数据导入 4. 各种中文分析器 章节二:...

    lucene-solr源码,编译成的idea项目源码

    本人用ant idea命令花了214分钟,35秒编译的lucene-solr源码,可以用idea打开,把项目放在D:\space\study\java\lucene-solr路径下,再用idea打开就行了

    solr实现电商自定义打分

    Solr,全称为Apache Solr,是一款开源的全文搜索引擎,广泛应用于电商、新闻、文档检索等领域。它提供了高效、可扩展的搜索与分析能力。在电商领域,搜索结果的排序和打分对于用户体验至关重要,因为它直接影响到...

    Solr-search过程源码分析

    在深入探讨Solr-search过程的源码分析时,我们聚焦于关键步骤与核心组件,以求全面理解Solr搜索机制的内部运作。Solr作为一款高性能、可伸缩的开源搜索平台,其搜索处理流程涉及多个层次的组件交互与数据处理,其中...

    solr实现的搜索引擎

    本章我们将深入探讨Solr如何实现搜索引擎,并结合《解密搜索引擎技术实战》第八章的代码实例进行详细解析。 1. **Solr的基本架构** Solr的核心架构包括索引、查询和处理三个主要部分。索引部分负责将数据转换为可...

    solr4.3源代码一

    Solr4.3是Apache Solr的一个早期版本,它是一个基于Lucene的全文搜索服务器,提供了高可配置、可扩展的搜索和分析功能。Solr4.3源代码的获取通常是为了深入理解其内部工作原理,进行定制开发或优化。在你提供的信息...

Global site tag (gtag.js) - Google Analytics