我们把Solr当数据仓库用了,常常用到查询统计即Facet。因此,对Facet接触挺多的,它实在是一个很不错的东西。对FacetComponent也有好多东西想分享,但都是找不到一个合适的角度来写,这已是第三稿,希望能写清晰而又简洁。OK,开始吧。
如果你已经知道分布式统计天生自带Bug的,那我们继续往下,否则请略过吧。
我们只考虑facet.sort=count的情况,Faceting为了尽可能减少误差,做了以下两件事。
- 加大Slave节点返回的结果集,使得TopK尽可能落在TopN结果集里。当N趋向结果集大小时,误差可以消除。
- 使得TopK里的统计完全准确。即当每个分片的TopN都大于等于m,若任意一个
Term TA
在其中Shard的数量为m+1,在其它分片的数据小于m。此时其它所有分片中都不会统计到Term TA
的数量。所有需要做二次统计了,统计其它分片的Term在自己分片的数量,然后再合并再取TopK。
再说回第一件事。Faceting提供两个参数来调整每个Shard返回的数据集大小。分别是
facet.overrequest.count | (Advanced) A number of documents, beyond the effective facet.limit to request from each shard in a distributed search |
---|---|
facet.overrequest.ratio | (Advanced) A multiplier of the effective facet.limit to request from each shard in a distributed search |
每个分片最终返回的结果集大小由于这个两个参数同共决定,即是 facet.overrequest.count+facet.overrequest.ratio*facet.limit
。默认情况是 K = 10 + 100 * 1.5 = 160
还有一个小细节,顺道讲一下。Faceting的ShardReqeust由QueryComponent发出的,同时与其它组件共用结果集。对了为什么Group与不是与QueryComponent共存呢?因为Group根本就不是一个组件,它只是QueryComponent的一种形式。
最后,我们知道,启用什么组件或不启用什么组件都是由参数控制的,子请求也是一样。
另一方面,SolrCloud的子请求分两个维度,阶段(Stage)和意途(Purpose)。一个阶段可以有多个意途,意途即是涉及的SearchComponent,Stage的话主要由SearchHandler控制。
同一阶段的请求可以由合为一个ShardRequest由SearchHandler发出。
public void addRequest(SearchComponent me, ShardRequest sreq) {
outgoing.add(sreq);
sreq.rb = this;
if ((sreq.purpose & ShardRequest.PURPOSE_PRIVATE) == 0) {
// if this isn't a private request, let other components modify it.
for (SearchComponent component : components) {
if (component != me) {
component.modifyRequest(this, me, sreq); // 各组件对相关请求参数进行改写
}
}
}
}