(这个使用的solr的版本是4.10)
承接上一篇文章,在对单值域的数字类型的域做facet的时候,会使用FCS方法,里面再调用的方法是NumericFacets.getCounts(searcher, base, field, offset, limit, mincount, missing, sort);所以看看这个的代码吧:
/** * 处理单值域的数字类型的facet * @param searcher * @param docs 基础范围(即有q和fq确定的所有的doc的id) * @param fieldName 要facet的域的名字 * @param offset 最后返回的结果的偏移量 * @param limit 最后返回的结果的数量,小于0表示全部返回! * @param mincount 能够被facet的term值得最小的doc的数量,如果一个term匹配的doc的数量小于这个值,则不计算这个term。如果这个值位0且其他的term不能够满足条件,则要收集匹配的doc数量为0的term,即没有在上面的docs中doc的term。 * @param missing 要不要返回null的值。即上面的 * @param sort 收集到的值得排序 * @return * @throws IOException */ public static NamedList<Integer> getCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort) throws IOException { final boolean zeros = mincount <= 0;//要不要收集没有doc的值(包括没有在docs中的那些doc匹配的term) mincount = Math.max(mincount, 1); //这么做是有好处的,这样可以加快速度。因为可能不需要使用不命中的那些值,单单那些已经命中的doc的term就已经可以得到结果了。 final SchemaField sf = searcher.getSchema().getField(fieldName);// final FieldType ft = sf.getType(); final NumericType numericType = ft.getNumericType(); if (numericType == null) {//只能facet数字类型的 throw new IllegalStateException(); } final List<AtomicReaderContext> leaves = searcher.getIndexReader().leaves(); // 1. 先把已经搜索到的docs的对应的值都收集了,收集到hashTable中。单独创建了这么一个类,用于保存term和匹配的doc的数量。 final HashTable hashTable = new HashTable(); final Iterator<AtomicReaderContext> ctxIt = leaves.iterator(); AtomicReaderContext ctx = null; FieldCache.Longs longs = null; Bits docsWithField = null; int missingCount = 0;// for (DocIterator docsIt = docs.iterator(); docsIt.hasNext();) {// final int doc = docsIt.nextDoc(); if (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()) {//找到这个doc所在的段 do { ctx = ctxIt.next(); } while (ctx == null || doc >= ctx.docBase + ctx.reader().maxDoc()); //从fieldCache中获得这个doc的值,从之前的博客中可以知道,fieldCache也是优先获取docValue的值的,所以说这个收集方式就是优先使用docValue的值。 switch (numericType) { case LONG: longs = FieldCache.DEFAULT.getLongs(ctx.reader(), fieldName, true); break; case INT: final FieldCache.Ints ints = FieldCache.DEFAULT.getInts(ctx.reader(), fieldName, true); longs = new FieldCache.Longs() { @Override public long get(int docID) { return ints.get(docID); } }; break; case FLOAT: final FieldCache.Floats floats = FieldCache.DEFAULT.getFloats(ctx.reader(), fieldName, true); longs = new FieldCache.Longs() { @Override public long get(int docID) { return NumericUtils.floatToSortableInt(floats.get(docID)); } }; break; case DOUBLE: final FieldCache.Doubles doubles = FieldCache.DEFAULT.getDoubles(ctx.reader(), fieldName, true); longs = new FieldCache.Longs() { @Override public long get(int docID) { return NumericUtils.doubleToSortableLong(doubles.get(docID)); } }; break; default: throw new AssertionError(); } docsWithField = FieldCache.DEFAULT.getDocsWithField(ctx.reader(), fieldName);//含有这个域的doc的bit } long v = longs.get(doc - ctx.docBase);//获得这个id的值 if (v != 0 || docsWithField.get(doc - ctx.docBase)) {//如果v != 0说明是一定有值得,但是==0的话可能也有值的,所以要判断两次。 hashTable.add(doc, v, 1);//收集到了,加入到hash表里面。 } else { ++missingCount;//没有值,也就是null的数量,如果需要返回missing的话这个就有用了。 } } // 2. 从hash表中根据规则 选择offset+limit个。 final int pqSize = limit < 0 ? hashTable.size : Math.min(offset + limit, hashTable.size);,如果limit小于0 则全部的term都要返回,否则返回offset+ limit个。 final PriorityQueue<Entry> pq;//根据排序创建一个优先队列 if (FacetParams.FACET_SORT_COUNT.equals(sort) || FacetParams.FACET_SORT_COUNT_LEGACY.equals(sort)) {//如果排序是按照term匹配的doc数量排序 pq = new PriorityQueue<Entry>(pqSize) { @Override protected boolean lessThan(Entry a, Entry b) { if (a.count < b.count || (a.count == b.count && a.bits > b.bits)) {//现根据count排序,如果count一样,按照数字排序 return true; } else { return false; } } }; } else { pq = new PriorityQueue<Entry>(pqSize) { @Override protected boolean lessThan(Entry a, Entry b) {//按照facet到的数字的大小排序 return a.bits > b.bits; } }; } Entry e = null; for (int i = 0; i < hashTable.bits.length; ++i) {//循环已经收集的term,这些的doc都是大于0的,因为他们的获取方式就是从已经搜索到的doc中获取的。 if (hashTable.counts[i] >= mincount) {//如果大于指定的值,hashTable.counts[i]的这个值最小是1,所以如果这些的term已经够数量了,就不去查询词典表了,所以前面才将其置位最小是1的数字,当然如果指定了>1的数字,就使用那个数字 if (e == null) { e = new Entry(); } e.bits = hashTable.bits[i]; e.count = hashTable.counts[i]; e.docID = hashTable.docIDs[i]; e = pq.insertWithOverflow(e); } } // 4. build the NamedList 构建最后的结果 final ValueSource vs = ft.getValueSource(sf, null);//使用valueSource查询具体的值,因为之前查询的都是long类型的值,而我们要返回的是字符串,这次就是要查询字符串。 final NamedList<Integer> result = new NamedList<>(); // 如果上面的term的数量不够,体现在两个方面,一个是排序,即收集的term的排序是按照term的字面值排序的,或者是minCount=0,表示要获得所有的term, 则要查询词典表,这就复杂了! // This stuff is complicated because if facet.mincount=0, the counts needs to be merged with terms from the terms dict(翻译过来是:如果mincount=0,则要读取词典表获得所有的term,因为现在仅仅是收集了一部分doc的term) // 或者不计算不命中的doc的term值或者是按照count排序的,就不需要查词典表了。 if (!zeros || FacetParams.FACET_SORT_COUNT.equals(sort) || FacetParams.FACET_SORT_COUNT_LEGACY.equals(sort)) { final Deque<Entry> counts = new ArrayDeque<>();//保存offset后面的那些值 while (pq.size() > offset) {//删除offset个到counts中去 counts.addFirst(pq.pop()); } // Entries from the PQ first, then using the terms dictionary for (Entry entry : counts) { final int readerIdx = ReaderUtil.subIndex(entry.docID, leaves); final FunctionValues values = vs.getValues(Collections.emptyMap(), leaves.get(readerIdx));//valueSource读取真正的值,使用FieldCache result.add(values.strVal(entry.docID - leaves.get(readerIdx).docBase), entry.count);//放入结果 } //如果计算那些不命中的且单单使用docSet不够数量,则要查看词典表,即检查所有的term if (zeros && (limit < 0 || result.size() < limit)) { // need to merge with the term dict if (!sf.indexed()) {//此时必须要简历索引,不然没法查词典表了 throw new IllegalStateException("Cannot use " + FacetParams.FACET_MINCOUNT + "=0 on field " + sf.getName() + " which is not indexed"); } // Add zeros until there are limit results final Set<String> alreadySeen = new HashSet<>(); //将使用docSet已经查找到的所有的值放入set集合里面,放置重复了 while (pq.size() > 0) {//第一步是放入offset的那些 Entry entry = pq.pop(); final int readerIdx = ReaderUtil.subIndex(entry.docID, leaves); final FunctionValues values = vs.getValues(Collections.emptyMap(), leaves.get(readerIdx)); alreadySeen.add(values.strVal(entry.docID - leaves.get(readerIdx).docBase)); } //第二部是放入已经放入到result里面的那些 for (int i = 0; i < result.size(); ++i) { alreadySeen.add(result.getName(i)); } //获得这个域的所有的term final Terms terms = searcher.getAtomicReader().terms(fieldName); if (terms != null) { final String prefixStr = TrieField.getMainValuePrefix(ft);//这个域的前缀 final BytesRef prefix; if (prefixStr != null) { prefix = new BytesRef(prefixStr); } else { prefix = new BytesRef(); } final TermsEnum termsEnum = terms.iterator(null); BytesRef term; switch (termsEnum.seekCeil(prefix)) { case FOUND: case NOT_FOUND: term = termsEnum.term(); break; case END: term = null; break; default: throw new AssertionError(); } final CharsRef spare = new CharsRef(); //继续跳过offset-hashtable.size,因为这一部分不要。 for (int skipped = hashTable.size; skipped < offset && term != null && StringHelper.startsWith(term, prefix);) { ft.indexedToReadable(term, spare); final String termStr = spare.toString(); if (!alreadySeen.contains(termStr)) { ++skipped; } term = termsEnum.next(); } //读取limit-result.size个term for (; term != null && StringHelper.startsWith(term, prefix) && (limit < 0 || result.size() < limit); term = termsEnum.next()) { ft.indexedToReadable(term, spare); final String termStr = spare.toString(); if (!alreadySeen.contains(termStr)) {//如果从来没有出现过! result.add(termStr, 0);//添加到结果中 } } } } } else {//收集docset中没有的且按照字面值排序,读取词典表 // sort=index, mincount=0 and we have less than limit items => Merge the PQ and the terms dictionary on the fly if (!sf.indexed()) { throw new IllegalStateException("Cannot use " + FacetParams.FACET_SORT + "=" + FacetParams.FACET_SORT_INDEX + " on a field which is not indexed"); } //key是facet的数字的字面值,value是次数 final Map<String, Integer> counts = new HashMap<>(); while (pq.size() > 0) {//从优先队列里面取出来,再放入到counts里面,放入的key是字面值,value是在docSet中facet到的次数 final Entry entry = pq.pop(); final int readerIdx = ReaderUtil.subIndex(entry.docID, leaves); final FunctionValues values = vs.getValues(Collections.emptyMap(), leaves.get(readerIdx)); counts.put(values.strVal(entry.docID - leaves.get(readerIdx).docBase), entry.count); } final Terms terms = searcher.getAtomicReader().terms(fieldName); if (terms != null) { final String prefixStr = TrieField.getMainValuePrefix(ft); final BytesRef prefix; if (prefixStr != null) { prefix = new BytesRef(prefixStr); } else { prefix = new BytesRef(); } final TermsEnum termsEnum = terms.iterator(null); BytesRef term; switch (termsEnum.seekCeil(prefix)) { case FOUND: case NOT_FOUND: term = termsEnum.term(); break; case END: term = null; break; default: throw new AssertionError(); } final CharsRef spare = new CharsRef(); for (int i = 0; i < offset && term != null && StringHelper.startsWith(term, prefix); ++i) {// term = termsEnum.next(); } for (; term != null && StringHelper.startsWith(term, prefix) && (limit < 0 || result.size() < limit); term = termsEnum.next()) { ft.indexedToReadable(term, spare); final String termStr = spare.toString(); Integer count = counts.get(termStr); if (count == null) { count = 0; } result.add(termStr, count); } } } if (missing) {//添加null的值得数量 result.add(null, missingCount); } return result; }
从上面可以总结出经验来,在对单值域的数字类型的域做facet的时候,最好是设置上mincount>0,且按照doc的数量排序,在这个时候仅仅是使用命中的所有的doc的term做聚合,数量较少,不会有其他的操作; 否则会读取词典表,导致效率低下。还需要注意的是,上面的所有的操作都是在一个线程中完成的,之前说的多线程是在多个facet.field的情况下才会使用的。
相关推荐
Solr 的默认 requestHandler(org.apache.solr.handler.component.SearchHandler)已经包含了 Facet 组件(org.apache.solr.handler.component.FacetComponent)。如果自定义 requestHandler 或者对默认的 ...
Solr 提供了多种 Facet 组件,包括 `facet.field` 用于基于字段的 Faceting,`facet.query` 用于自定义查询的 Faceting,以及 `facet.date` 用于日期的 Faceting。这些组件可以通过配置在查询请求中指定,以满足不同...
三、Solr 6.2.0的改进与新特性 1. 改进的ShardHandler API:增强了对请求的并发处理能力,提高了性能。 2. 引入了新的查询执行模型(Distributed Searcher):优化了分布式查询的性能和稳定性。 3. 增强的CSV处理:...
- **配置**:Solr的配置文件在`conf`目录下,包括schema.xml(定义字段和字段类型)、solrconfig.xml(配置索引和查询行为)等。 - **集合与分片**:在分布式环境中,Solr将数据分为多个集合,每个集合可以进一步...
solr-mongo-importer-1.1.0.jar solr-mongo-importer-1.1.0.jar solr-mongo-importer-1.1.0.jar
solr.warsolr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包solr.war包...
搜索引擎如ElasticSearch和Solr,以及分布式和微服务相关技术(服务发现/注册、网关、服务调用、熔断/降级、配置中心、认证鉴权、分布式事务、任务调度、链路追踪和监控、日志分析和监控)也是必备技能。 最后,...
Solr集群安装与配置(一)第11讲 Solr集群安装与配置(二)第12讲 SolrCloud基本概念第13讲 Solrj操作SolrCloud第14讲 solr索引主从同步第15讲 solr之Facet第16讲 solr之FacetPivot第17讲 solr之Group第18讲 solr之...
Solr它是一种开放源码的、基于 Lucene 的搜索服务器,可以高效的完成全文检索的功能。在本套课程中,我们将全面的讲解Solr,从Solr基础到Solr高级,再到项目实战,基本上涵盖了Solr中所有的知识点。 主讲内容 章节一...
这个源码包包含了 Solr 的所有源代码,对于理解 Solr 的工作原理、进行二次开发或者定制化配置有着非常重要的意义。接下来,我们将深入探讨 Solr 6.6.0 中的一些关键知识点。 一、Solr 架构与组件 Solr 的核心架构...
Solr项目源码及solr资源包是一个针对搜索引擎平台Apache Solr的学习与实践资源集合,主要结合了Spring Data Solr框架进行操作。这个项目旨在帮助开发者更好地理解和运用Solr进行数据索引和检索。让我们详细地探讨...
2. **Lucene 库**:Solr构建在Lucene之上,Lucene是Java的一个开源全文检索库。4.0版本可能使用了Lucene的较新版本,学习源码可以帮助理解Lucene如何支持Solr的全文搜索、高亮显示、拼写检查等功能。 3. **配置文件...
PART 1 MEET SOLR. .................................................................1 1 ■ Introduction to Solr 3 2 ■ Getting to know Solr 26 3 ■ Key Solr concepts 48 4 ■ Configuring Solr 82 5 ■ ...
这个源码包包含了Solr 4.10.4的所有源代码,对于开发者来说,这是一个深入了解Solr工作原理、定制功能以及进行二次开发的重要资源。 1. **Solr简介** Apache Solr是一个基于Lucene的全文检索服务,提供了一个高效...
Solr,全称为Apache Solr,是一款开源的全文搜索引擎,被广泛应用于企业级搜索解决方案中。它基于Java,能够高效地处理大量数据的检索、排序和过滤等任务。在这个“配置好的solr启动环境”中,我们有一个预先配置好...
在给定的压缩包“apache-solr-dataimporthandler-extras-1.4.0.jar.zip”中,主要包含了一个名为“apache-solr-dataimporthandler-extras-1.4.0.jar”的文件,这个文件是Solr的一个重要组件——DataImportHandler...
把solr.war(solr-4.2.0\example\solr-webapp\solr.war)里的东西全复制到WebRoot下 2. 创建solr/home, 把solr-4.2.0\example\solr所有文件复制到你创建的solr/home目录下 3. 创建JNDI让程序找到solr/home(当然你也...
Solr 数据导入调度器(solr-dataimport-scheduler.jar)是一个专门为Apache Solr 7.x版本设计的组件,用于实现数据的定期索引更新。在理解这个知识点之前,我们需要先了解Solr的基本概念以及数据导入处理...
3. **Solr配置**:源码中可能包含针对不同环境(如开发、测试、生产)的Solr配置,通过solr.yml管理。这些配置可能涉及Solr服务器地址、端口、数据目录等。 4. **错误处理**:在与Solr通信过程中,可能会遇到网络...
中文分词器ik-analyzer-solr5-5.x.jar,已经打包好,直接用就可以 2积分不黑心