工作中用到了关键词置顶、拉黑的操作,自己毫无办法,考虑了很久打算用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的设计思想和实现细节。 总结来说,Solr 6.2.0是一个强大的全文搜索引擎,它的分布式特性、实时性以及丰富的功能使得它成为企业级搜索应用的理想选择。通过...
源码分析是深入理解一个软件系统工作原理的重要途径,对于Solr这样的复杂系统尤其如此。这里我们将围绕"solr-9.0.0-src.tgz"这个源码包,详细探讨其主要组成部分、核心功能以及开发过程中的关键知识点。 1. **Solr...
源代码实例是学习Solr内部工作原理和自定义功能的关键资源,尤其对于开发者而言,深入理解源码有助于提升系统的优化和扩展能力。 在Solr 4.0版本中,`solr.xml`是Solr的核心配置文件,它定义了Solr实例的基本设置,...
这个源码包包含了Solr 4.10.4的所有源代码,对于开发者来说,这是一个深入了解Solr工作原理、定制功能以及进行二次开发的重要资源。 1. **Solr简介** Apache Solr是一个基于Lucene的全文检索服务,提供了一个高效...
Solr5是一款强大的开源搜索引擎,它提供了全文检索、命中高亮、分类聚类等多种功能,广泛应用于企业级的信息检索系统。在处理中文文本时,一个关键的组件就是中文分词器,而IK(Intelligent Chinese)分词器是Solr...
标题中的"JAVA+Solr分词项目工程实例Java实用源码整理learns"指的是一个基于Java编程语言并结合Solr搜索引擎的项目实例。这个项目主要关注于文本处理和信息检索,利用Solr的分词功能来提升搜索效率和准确性。Solr是...
这个源码包包含了 Solr 的所有源代码,对于理解 Solr 的工作原理、进行二次开发或者定制化配置有着非常重要的意义。接下来,我们将深入探讨 Solr 6.6.0 中的一些关键知识点。 一、Solr 架构与组件 Solr 的核心架构...
在这个源码包中,我们可以深入理解Solr的工作原理以及其核心组件的实现。 首先,让我们了解Solr的基本架构。Solr基于Lucene库构建,Lucene是一个高性能、全文本检索库。Solr在其之上添加了分布式处理、集群管理、...
这个项目工程实例是关于如何使用Java与Solr进行集成,实现分词搜索功能的示例。Solr提供了强大的文本分析、索引和查询能力,广泛应用于内容管理系统、电子商务网站、新闻门户等场景。 1. **Solr简介** - Solr是...
solr全文检索,里面包含文档,源代码,jar包,使用的是solr4.2,东西比较全,安装文档就能跑起来,,适合参考借鉴
Solr教程与实例详解 Apache Solr是一款开源的企业级全文搜索引擎,由Apache软件基金会开发,基于Java语言,具有高效、可扩展的特点。它为大型、分布式搜索应用提供了强大的支持,包括文档检索、拼写建议、高亮显示...
本实例将详细介绍如何在本地环境中部署并运行一个Solr实例。 **一、Solr概述** Apache Solr是基于Java开发的,能够处理大量数据的高性能搜索平台。它提供了分布式、可扩展、实时和近实时搜索功能。Solr的核心特性...
Solr项目源码及solr资源包是一个针对搜索引擎平台Apache Solr的学习与实践资源集合,主要结合了Spring Data Solr框架进行操作。这个项目旨在帮助开发者更好地理解和运用Solr进行数据索引和检索。让我们详细地探讨...
在Solr集群中,每个Solr实例被称为一个"SolrCore",每个SolrCore可以看作是一个独立的搜索引擎。通过Zookeeper,Solr集群可以实现文档的分布式存储和检索,确保高可用性和数据的一致性。 构建Solr集群的第一步是...
在本套课程中,我们将全面的讲解Solr,从Solr基础到Solr高级,再到项目实战,基本上涵盖了Solr中所有的知识点。 主讲内容 章节一:Solr基础(上) 1. 环境搭建 2. 核心讲解 3. 数据导入 4. 各种中文分析器 章节二:...
本人用ant idea命令花了214分钟,35秒编译的lucene-solr源码,可以用idea打开,把项目放在D:\space\study\java\lucene-solr路径下,再用idea打开就行了
Solr,全称为Apache Solr,是一款开源的全文搜索引擎,广泛应用于电商、新闻、文档检索等领域。它提供了高效、可扩展的搜索与分析能力。在电商领域,搜索结果的排序和打分对于用户体验至关重要,因为它直接影响到...
在深入探讨Solr-search过程的源码分析时,我们聚焦于关键步骤与核心组件,以求全面理解Solr搜索机制的内部运作。Solr作为一款高性能、可伸缩的开源搜索平台,其搜索处理流程涉及多个层次的组件交互与数据处理,其中...
本章我们将深入探讨Solr如何实现搜索引擎,并结合《解密搜索引擎技术实战》第八章的代码实例进行详细解析。 1. **Solr的基本架构** Solr的核心架构包括索引、查询和处理三个主要部分。索引部分负责将数据转换为可...
Solr4.3是Apache Solr的一个早期版本,它是一个基于Lucene的全文搜索服务器,提供了高可配置、可扩展的搜索和分析功能。Solr4.3源代码的获取通常是为了深入理解其内部工作原理,进行定制开发或优化。在你提供的信息...