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

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的)。

  

猜你喜欢

转载自suichangkele.iteye.com/blog/2411062