欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

solr的facet源码解读(二)——facet.field

程序员文章站 2022-04-03 19:11:17
...

facet.field比facet.query要复杂的多,参数也更多,看看代码吧,方法是:SimpleFacets.getFacetFieldCounts()

public NamedList<Object> getFacetFieldCounts() throws IOException, SyntaxError {
	NamedList<Object> res = new SimpleOrderedMap<>();
	String[] facetFs = params.getParams(FacetParams.FACET_FIELD);
	if (null == facetFs) {
		return res;
	}
 	int maxThreads = req.getParams().getInt(FacetParams.FACET_THREADS, 0);//有的情况下可以使用多个线程处理facet。参数是facet.threads,如果大于0,使用一个多线程的线程池。不过多个任务的处理是对于多个facet field来说的,如果只有一个域的facet,则这个参数设置了也没用。
	Executor executor = maxThreads == 0 ? directExecutor : facetExecutor;
	//如果小于0,表示使用一个没有限制的线程池(当然他的实现还是使用了一个类似CachedThreadPool的线程池,即最大值没有限制的,但是可以使用下面的semaphore来做一个限制,实现同样的功能)
	final Semaphore semaphore = new Semaphore((maxThreads <= 0) ? Integer.MAX_VALUE : maxThreads);
	List<Future<NamedList>> futures = new ArrayList<>(facetFs.length);//多个域的facet的结果。
	try {
		for (String f : facetFs) {//循环所有的facet field
			parseParams(FacetParams.FACET_FIELD, f);//解析这个域的facet的参数
			final String termList = localParams == null ? null : localParams.get(CommonParams.TERMS);//没有看这个情况,下文中全部假设termList=null
			final String workerKey = key;//在没有localParam的时候,具体的field
			final String workerFacetValue = facetValue;//facet的对象,也就是field的名字
			final DocSet workerBase = this.docs;//之前的查询的过程中由q和fq获得的所有的doc的id
			Callable<NamedList> callable = new Callable<NamedList>() {//这个就是要提交到线程池中的任务,用于处理某一个facet.field
				@Override
				public NamedList call() throws Exception {
					try {
						NamedList<Object> result = new SimpleOrderedMap<>();
						if (termList != null) {//不考虑这种情况。工作中没用到
							List<String> terms = StrUtils.splitSmart(termList, ",", true);
							result.add(workerKey, getListedTermCounts(workerFacetValue, workerBase, terms));
						} else {
							result.add(workerKey, getTermCounts(workerFacetValue, workerBase));//具体的方法就是这个,
						}
						return result;
					} catch (SolrException se) {
						throw se;
					} catch (Exception e) {
						throw new SolrException(ErrorCode.SERVER_ERROR,
								"Exception during facet.field: " + workerFacetValue, e);
					} finally {
						semaphore.release();//释放资源
					}
				}
			};
			RunnableFuture<NamedList> runnableFuture = new FutureTask<>(callable);
			semaphore.acquire();// may block and/or interrupt
			executor.execute(runnableFuture);// releases semaphore when done
			futures.add(runnableFuture);
		} // facetFs loop
		// Loop over futures to get the values. The order is the same as facetFs but shouldn't matter.
		for (Future<NamedList> future : futures) {
			res.addAll(future.get());
		}
		assert semaphore.availablePermits() >= maxThreads;
	} catch (InterruptedException e) {
		throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,"Error while processing facet fields: InterruptedException", e);
	} catch (ExecutionException ee) {
		Throwable e = ee.getCause();// unwrap
		if (e instanceof RuntimeException) {
			throw (RuntimeException) e;
		}
		throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,"Error while processing facet fields: " + e.toString(), e);
	}
	return res;
}

上面可以看到,对于多个facet.field是可以使用一个线程池的,将多个facet.field提交给不同的cpu并行处理,可以提高速度,控制使用多线程的参数是facet.threads,但是如果只有一个facet.field的话,就没有用了,因为他只有一个任务。

下面最关键的就是getTermCounts方法了

/**
 * 在base的范围内进行facet <br/>
 * Term counts for use in field faceting that resepects the appropriate mincount
 * @see FacetParams#FACET_MINCOUNT
 */
public NamedList<Integer> getTermCounts(String field, DocSet base) throws IOException {
	Integer mincount = params.getFieldInt(field, FacetParams.FACET_MINCOUNT);//每个term匹配的doc的最少的数量
	return getTermCounts(field, mincount, base);
}

 

/**
 * @param filed:facet的域
 * @param mincount :符合条件的term匹配最少的doc的值
 * @param base 由q和fq确定的集合
 */
private NamedList<Integer> getTermCounts(String field, Integer mincount, DocSet base) throws IOException {
	
	int offset = params.getFieldInt(field, FacetParams.FACET_OFFSET, 0);//偏移量
	int limit = params.getFieldInt(field, FacetParams.FACET_LIMIT, 100);//要多少个
	if (limit == 0)
		return new NamedList<>();
	
	if (mincount == null) {
		// 判断要不要收集没有doc匹配的term的值
		Boolean zeros = params.getFieldBool(field, FacetParams.FACET_ZEROS);
		mincount = (zeros != null && !zeros) ? 1 : 0;
	}
	// 是不是要收集null的值,有的doc在这个域中没有值,用null表示,如果是true,则要返回一个null的term命中的doc的数量。
	boolean missing = params.getFieldBool(field, FacetParams.FACET_MISSING, false);
	// default to sorting if there is a limit.    facet结果的排序,如果没有指定sort,如果limit>0,则使用count排序,即命中的doc的数量,否则使用facet到的值得字面值排序
	String sort = params.getFieldParam(field, FacetParams.FACET_SORT,limit > 0 ? FacetParams.FACET_SORT_COUNT : FacetParams.FACET_SORT_INDEX);
	String prefix = params.getFieldParam(field, FacetParams.FACET_PREFIX);//必须匹配的前缀的值

	NamedList<Integer> counts;
	SchemaField sf = searcher.getSchema().getField(field);
	FieldType ft = sf.getType();

	// 确定faceting method
	final String methodStr = params.getFieldParam(field, FacetParams.FACET_METHOD);
	FacetMethod method = null;
	if (FacetParams.FACET_METHOD_enum.equals(methodStr)) {
		method = FacetMethod.ENUM;//忽略这个情况,没有遇到过
	} else if (FacetParams.FACET_METHOD_fcs.equals(methodStr)) {
		method = FacetMethod.FCS;
	} else if (FacetParams.FACET_METHOD_fc.equals(methodStr)) {
		method = FacetMethod.FC;
	}
	if (method == FacetMethod.ENUM && TrieField.getMainValuePrefix(ft) != null) {
		method = sf.multiValued() ? FacetMethod.FC : FacetMethod.FCS;
	}
	if (method == null && ft instanceof BoolField) {
		// Always use filters for booleans... we know the number of values is very small.
		method = FacetMethod.ENUM;
	}
	//是不是多值域或者分词的。
	final boolean multiToken = sf.multiValued() || ft.multiValuedFieldCache();
	if (method == null && ft.getNumericType() != null && !sf.multiValued()) {//如果没有指定方法,且是单值域的数字类型,优先使用FCS.
		// the per-segment approach is optimal for numeric field types since there are no global ords to merge and no need to create an expensive top-level reader
		method = FacetMethod.FCS;//fcs(只能facet单值域的域)
	}
	if (ft.getNumericType() != null && sf.hasDocValues()) {//如果是数字类型的且有docVaue,推荐使用FCS
		// only fcs is able to leverage the numeric field caches
		method = FacetMethod.FCS;//
	}
	if (method == null) {//如果上面的都没有进入,默认使用FC
		method = FacetMethod.FC;
	}
	if (method == FacetMethod.FCS && multiToken) {//FCS不能处理多值域的,所以要切换为FC
		method = FacetMethod.FC;
	}
	if (method == FacetMethod.ENUM && sf.hasDocValues()) {
		method = FacetMethod.FC;
	}
	if (params.getFieldBool(field, GroupParams.GROUP_FACET, false)) {//没有用到这个功能,忽略
	        counts = getGroupedCounts(searcher, base, field, multiToken, offset, limit, mincount, missing, sort,prefix);
	} else {
		assert method != null;
		switch (method) {
		case ENUM:
			assert TrieField.getMainValuePrefix(ft) == null;
			counts = getFacetTermEnumCounts(searcher, base, field, offset, limit, mincount, missing, sort, prefix);
			break;
		case FCS://只能处理单值域且不分词的。
			assert !multiToken;
			if (ft.getNumericType() != null/* && !sf.multiValued()*/) {//这个是我自己注释的。因为如果使用FCS且getNumericType !=null 的话,一定不会是multiValued的,所以第二个条件是没用的。
				if (prefix != null && !prefix.isEmpty()) {
					throw new SolrException(ErrorCode.BAD_REQUEST, FacetParams.FACET_PREFIX + " is not supported on numeric types");
				}
				// 这个会尽可能的不使用读取词典表,除非是要返回的结果不够了且使用了minCount=0的参数
				counts = NumericFacets.getCounts(searcher, base, field, offset, limit, mincount, missing, sort);
			} else {//单值域的facet
				PerSegmentSingleValuedFaceting ps = new PerSegmentSingleValuedFaceting(searcher, base, field, offset, limit, mincount, missing, sort, prefix);
				Executor executor = threads == 0 ? directExecutor : facetExecutor;
				ps.setNumThreads(threads);
				counts = ps.getFacetCounts(executor);
			}
			break;
		case FC:
			if (sf.hasDocValues()) {//如果有docValue,
				counts = DocValuesFacets.getCounts(searcher, base, field, offset, limit, mincount, missing, sort, prefix);
			} else if (multiToken || TrieField.getMainValuePrefix(ft) != null) {//如果没有docValue且是多值域的
				UnInvertedField uif = UnInvertedField.getUnInvertedField(field, searcher);
				counts = uif.getCounts(searcher, base, offset, limit, mincount, missing, sort, prefix);
			} else {
				counts = getFieldCacheCounts(searcher, base, field, offset, limit, mincount, missing, sort, prefix);
			}
			break;
		default:
			throw new AssertionError();
		}
	}
	return counts;
}

 从上面的代码来看,如果没有指定类型,如果是单值域的数字,则优先使用FCS,否则使用FC.如果指定了使用FCS,但是如果域是多值域的也会使用FC,所以我们只需要看一下FCS的数字的情况和FC的情况即可。在接下来的几篇博客中,会仔细的看下FCS的数字和非数字的两种情况以及FCS的含有docValue的情况(不含有docValue的情况不看了,工作中都是含有docValue的)。