3、Solr CCS (Cross Collection Search)与协调节点(Coordinator Node)的协同工作逻辑及自定义开发指南

  • Solr CCS (Cross Collection Search)与协调节点(Coordinator Node)的协同工作逻辑及自定义开发指南

Solr 跨集合查询(CCS)的核心是以协调节点(Coordinator Node)为核心引擎,将跨集合 / 分片的查询拆解、并行执行、汇总结果,CCS 是查询能力的抽象,协调节点是该能力的物理执行载体。以下从「协同运行逻辑」「协调节点汇总统计的底层机制」「自定义统计 / 排序逻辑开发」三个维度完整解答。

一、CCS 与协调节点的核心关系

  • CCS:是 Solr 定义的「跨多个集合 / 核心查询并返回统一结果」的能力规范,定义了查询语法、参数规则、结果合并的语义。
  • 协调节点 :是 SolrCloud 集群中承接客户端查询请求的节点(任意节点均可作为协调节点,默认由集群路由规则分配),是 CCS 逻辑的唯一执行主体------ 所有 CCS 的拆分、并行查询、结果汇总、排序都在协调节点上完成,其他节点仅作为「分片节点」执行子查询。

简单来说:CCS 是 "做什么"(跨集合查询),协调节点是 "谁来做"(执行查询 + 汇总)

二、CCS 与协调节点的协同运行逻辑

以你场景(查询 family,big 集合,统计字段)为例,完整运行流程如下(SolrCloud 模式):

步骤 1:请求接收与协调节点绑定

  1. 客户端(控制台 / API)发送 CCS 请求:http://<solr-host>:8983/solr/family,big/select?q=*:*&stats=true&stats.field=age
  2. Solr 集群的负载均衡 / 路由层(或 ZooKeeper 路由规则)将请求分配给任意节点(比如 Node1),该节点即为「协调节点」;
  3. 协调节点初始化 ResponseBuilder(核心上下文对象),存储查询参数、目标集合、结果容器等信息。

步骤 2:CCS 集合解析与分片拓扑拉取

协调节点解析 CCS 核心参数(collection=family,big),完成「集合→分片」的映射(依赖 ZooKeeper):

  1. 解析 family 集合:从 ZooKeeper 获取其分片拓扑(如 family_shard1_replica1(Node2)、family_shard2_replica1(Node3));
  2. 解析 big 集合:从 ZooKeeper 获取其分片拓扑(如 big_shard1_replica1(Node4));
  3. 生成「全部分片列表」:[family_shard1, family_shard2, big_shard1],并标记每个分片的地址、副本优先级(优先查主副本)。

步骤 3:分布式查询任务拆分(CCS → 分片级任务)

协调节点基于 SearchHandler 中的 DistributedSearchComponent(分布式查询核心组件),将 CCS 请求拆分为「分片级子查询任务」:

  1. 为每个目标分片生成 ShardRequest(分片请求对象),包含:

    • 子查询参数(复用原请求的 qstatsstats.field 等);
    • 分片地址(如 http://Node2:8983/solr/family_shard1);
    • 任务类型(如 STATS 统计、QUERY 基础查询);
  2. 所有 ShardRequest 被加入协调节点的「并行任务队列」,由线程池(默认 solr.parallel.max.threads=16)调度执行。

步骤 4:并行子查询执行(协调节点→分片节点)

  1. 协调节点通过 HTTP 客户端(内部封装)并行向所有分片节点发送子查询;

  2. 分片节点(Node2/Node3/Node4)接收子查询后,独立执行:

    • family_shard1/2:查询本地索引,计算分片内的 age 统计值(sum/count/avg 等),返回 ShardResponse
    • big_shard1:无数据,返回空统计结果(sum=0、count=0);
  3. 分片节点的响应会携带「分片级元数据」(如 numFound、统计结果、排序字段值),异步返回给协调节点。

步骤 5:协调节点汇总分片统计 / 排序结果(核心环节)

这是协调节点的核心工作,所有 CCS 的汇总逻辑都在此完成,核心依赖 ResponseBuilder 收集所有 ShardResponse 并聚合:

5.1 统计结果汇总(以 StatsComponent 为例)

Solr 内置的统计组件(StatsComponent)在协调节点上执行「分片统计值→全局统计值」的聚合,核心逻辑:

  1. 遍历所有分片的 ShardResponse,提取统计字段的原始值(如每个分片的 sum_agecount_agemin_agemax_age);

  2. 按统计类型执行聚合算法:

    • 累加类(sum/count):全局sum = shard1.sum + shard2.sum + ...
    • 计算类(avg):全局avg = 全局sum / 全局count
    • 极值类(min/max):全局min = min(shard1.min, shard2.min, ...)
  3. 将聚合后的全局统计值写入 ResponseBuilderstatsInfo 容器。

5.2 排序结果汇总(若需全局排序)

若请求包含 sort 参数(如 sort=age desc),协调节点的排序汇总逻辑:

  1. 若分片已按同字段排序(默认分片会按 sort 执行本地排序):协调节点采用「归并排序」(Merge Sort),将多个有序分片结果合并为全局有序结果(时间复杂度 O (N log K),K 为分片数);
  2. 若分片未排序:协调节点收集所有分片结果后,在内存中执行全量排序;
  3. start/rows 截取最终分页结果,写入 ResponseBuilderdocs 容器。

步骤 6:响应组装与返回

  1. 协调节点将 ResponseBuilder 中的全局统计结果、排序后的文档列表、元数据(numFound/QTime)封装为标准 Solr 响应格式;
  2. 将响应返回给客户端,整个 CCS 流程结束(所有汇总结果仅存在于协调节点内存,不会写入任何集合)。

三、协调节点汇总 Shard 统计结果的底层机制

1. 核心依赖的组件 / 类

核心类 / 组件 作用
DistributedSearchComponent 分布式查询的核心组件,负责拆分分片任务、收集分片响应、触发汇总逻辑
StatsComponent 统计功能的核心组件,定义统计字段的聚合规则(可扩展)
ResponseBuilder 全局上下文对象,存储所有分片响应、最终结果、查询参数
ShardRequest/ShardResponse 分片请求 / 响应对象,封装分片地址、参数、返回结果
SimpleFacets/StatsValues 分面 / 统计值的存储结构,支持分片级值的累加 / 聚合

2. 汇总的核心流程(源码级简化逻辑)

java 复制代码
// 协调节点汇总统计的核心逻辑(伪代码)
public void aggregateStats(ResponseBuilder rb) {
    // 1. 初始化全局统计容器
    StatsValues globalStats = new StatsValues();
    // 2. 遍历所有分片响应
    for (ShardResponse shardResp : rb.getShardResponses()) {
        // 2.1 提取分片级统计结果
        StatsValues shardStats = shardResp.getStatsValues("age");
        // 2.2 聚合到全局(累加sum/count,计算min/max)
        globalStats.addSum(shardStats.getSum());
        globalStats.addCount(shardStats.getCount());
        globalStats.updateMin(shardStats.getMin());
        globalStats.updateMax(shardStats.getMax());
    }
    // 3. 计算派生指标(如avg)
    globalStats.calculateAvg();
    // 4. 写入最终响应
    rb.getResponse().add("stats", globalStats);
}

3. 不同统计类型的汇总规则

统计类型 汇总算法 示例(family_shard1: sum=100, count=5;family_shard2: sum=200, count=10;big_shard1: sum=0, count=0)
Count/Sum 分片值累加 全局 count=15,全局 sum=300
Avg 全局 sum / 全局 count 全局 avg=20(300/15)
Min/Max 所有分片的 Min/Max 极值 若 shard1.min=10,shard2.min=15 → 全局 min=10
Facet(分面) 分面项计数累加(如 age:20 的 count=shard1+shard2) age:20 的全局 count=shard1.count (20) + shard2.count (20)

四、自定义开发协调节点的统计 / 排序逻辑

Solr 支持通过「扩展 Component 组件」自定义协调节点的汇总逻辑,核心思路是:继承 / 重写 Solr 内置的 Component(如 StatsComponent、SearchComponent),在协调节点的汇总阶段插入自定义逻辑

前提准备

  1. 环境:Solr 源码 / 自定义插件开发环境(JDK 11+,与 Solr 版本一致,如 Solr 9.x);
  2. 核心原则:自定义逻辑仅需在协调节点执行(分片节点仍用默认逻辑),需通过 rb.isDistributed() 判断是否为分布式查询(CCS 属于分布式查询)。

场景 1:自定义统计汇总逻辑(示例:加权 Sum 统计)

需求:汇总时为不同集合的分片统计值加权重(如 family 分片权重 1,big 分片权重 0.5)。

步骤 1:开发自定义 StatsComponent

java 复制代码
import org.apache.solr.handler.component.StatsComponent;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.common.util.NamedList;

public class CustomWeightedStatsComponent extends StatsComponent {

    // 重写协调节点的统计汇总逻辑
    @Override
    public void finishStage(ResponseBuilder rb) {
        // 仅在协调节点执行汇总(分布式查询阶段)
        if (rb.isDistributed() && rb.stage == ResponseBuilder.STAGE_GET_FIELDS) {
            // 1. 获取所有分片响应
            List<ShardResponse> shardResponses = rb.getShardResponses();
            // 2. 初始化加权全局统计容器
            NamedList<Object> globalStats = new NamedList<>();
            double weightedSum = 0.0;
            long totalCount = 0;

            // 3. 遍历分片响应,按集合加权汇总
            for (ShardResponse resp : shardResponses) {
                // 获取分片所属集合(从分片地址/元数据解析)
                String collection = getCollectionFromShard(resp.getShardInfo().getShardName());
                // 提取分片统计值
                double shardSum = (Double) resp.getSolrResponse().getResponse().get("stats").get("sum_age");
                long shardCount = (Long) resp.getSolrResponse().getResponse().get("stats").get("count_age");

                // 4. 自定义加权规则
                double weight = "family".equals(collection) ? 1.0 : 0.5; // big 分片权重 0.5
                weightedSum += shardSum * weight;
                totalCount += shardCount;
            }

            // 5. 写入自定义统计结果
            globalStats.add("weighted_sum_age", weightedSum);
            globalStats.add("weighted_avg_age", weightedSum / totalCount);
            // 合并到原始统计结果中
            rb.rsp.add("custom_stats", globalStats);
        }

        // 执行父类默认逻辑(保留原有统计)
        super.finishStage(rb);
    }

    // 辅助方法:从分片名解析所属集合(如 family_shard1 → family)
    private String getCollectionFromShard(String shardName) {
        if (shardName.startsWith("family")) return "family";
        if (shardName.startsWith("big")) return "big";
        return "";
    }
}

步骤 2:注册自定义 Component

  1. solrconfig.xml 中注册自定义组件(所有协调节点需配置):
xml 复制代码
<searchComponent name="customStats" class="com.yourpackage.CustomWeightedStatsComponent">
    <str name="stats.field">age</str>
</searchComponent>

<!-- 将自定义组件加入查询处理器 -->
<requestHandler name="/select" class="solr.SearchHandler">
    <arr name="components">
        <str>query</str>
        <str>customStats</str> <!-- 自定义统计组件 -->
        <str>stats</str> <!-- 保留原生统计组件(可选) -->
        <str>facet</str>
        <str>sort</str>
    </arr>
</requestHandler>

步骤 3:打包部署

  1. 将自定义类打包为 JAR(依赖 Solr 核心包,如 solr-core-9.4.0.jar);
  2. 将 JAR 放入 Solr 节点的 server/solr/lib 目录(所有协调节点需部署);
  3. 重启 Solr 集群,生效配置。

场景 2:自定义全局排序逻辑

需求:跨分片排序时,不仅按 age 排序,还按「集合优先级」排序(family 文档优先于 big 文档)。

步骤 1:开发自定义 SortComponent

java 复制代码
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.common.SolrDocument;
import java.util.Comparator;
import java.util.List;

public class CustomCollectionSortComponent extends SearchComponent {

    @Override
    public void finishStage(ResponseBuilder rb) {
        // 仅在协调节点执行全局排序
        if (rb.isDistributed() && rb.stage == ResponseBuilder.STAGE_SORT) {
            // 1. 获取所有分片返回的文档列表
            List<SolrDocument> docs = rb.getResults().docs;
            // 2. 自定义排序器:先按集合优先级,再按age降序
            docs.sort(new Comparator<SolrDocument>() {
                @Override
                public int compare(SolrDocument d1, SolrDocument d2) {
                    // 2.1 解析文档所属集合(需分片返回时携带集合元数据)
                    String coll1 = (String) d1.get("_collection"); // 需在分片查询时添加该字段
                    String coll2 = (String) d2.get("_collection");
                    // 2.2 集合优先级:family > big
                    int collCompare = getCollectionPriority(coll2) - getCollectionPriority(coll1);
                    if (collCompare != 0) return collCompare;
                    // 2.3 按age降序
                    Integer age1 = (Integer) d1.get("age");
                    Integer age2 = (Integer) d2.get("age");
                    return age2.compareTo(age1);
                }
            });
            // 3. 替换原始排序结果
            rb.getResults().docs = docs;
        }
    }

    // 定义集合优先级(数值越大优先级越高)
    private int getCollectionPriority(String collection) {
        return "family".equals(collection) ? 2 : 1; // big 优先级 1
    }

    @Override
    public String getDescription() {
        return "Custom collection-aware sort component";
    }
}

步骤 2:配置与部署

  1. solrconfig.xml 中注册自定义排序组件(替换 / 追加到 select 处理器);
  2. 确保分片查询时返回 _collection 字段(在 fl 参数中添加 _collection,如 fl=*,_collection);
  3. 打包 JAR 并部署到协调节点,重启生效。

自定义开发的关键注意事项

  1. 仅在协调节点执行 :通过 rb.isDistributed() 判断分布式查询,避免分片节点执行自定义逻辑;
  2. 性能控制 :协调节点汇总 / 排序依赖内存,自定义逻辑需避免全量数据加载(控制 shards.rows);
  3. 元数据传递 :自定义逻辑需分片返回额外元数据(如 _collection)时,需在子查询中通过 fl 参数显式指定;
  4. 版本兼容 :自定义组件需与 Solr 版本匹配(如 Solr 8.x 和 9.x 的 ResponseBuilder 接口有差异)。

五、核心总结

  1. CCS 与协调节点的协同:CCS 定义跨集合查询的规则,协调节点负责将 CCS 请求拆分为分片任务、并行执行、汇总结果,所有逻辑均在协调节点内存完成;
  2. 统计汇总机制 :协调节点通过遍历分片响应,按统计类型(sum/count/avg)执行累加 / 极值 / 计算类聚合,核心依赖 StatsComponent
  3. 自定义开发 :通过扩展 Solr 的 SearchComponent(如 StatsComponent/ 自定义 Sort 组件),在协调节点的 finishStage 阶段插入自定义汇总 / 排序逻辑,打包部署到协调节点即可生效。

若需简化开发,也可通过 Solr 的「自定义函数(Function Query)」实现轻量级统计 / 排序定制(无需开发插件),仅需在查询参数中使用自定义函数(如 sort=custom_weight(age, collection) desc)。

相关推荐
liyanchao201814 小时前
2、Solr跨集合查询(Cross Collection Search)机制
solr
咨询QQ:4877392785 天前
C#程序源码:相机图片展示、工艺图片打包压缩及全屏显示缺陷图片的类别查询功能
solr
LFly_ice9 天前
LINQ语法
服务器·solr·linq
爱编程的鱼1 个月前
C# var 关键字详解:从入门到精通
开发语言·c#·solr
酥酥禾2 个月前
C# LINQ常用语法
solr·lucene
李白你好3 个月前
Solr任意文件读取-快速利用工具
solr
代码的余温3 个月前
ElasticSearch对比Solr
大数据·elasticsearch·solr
playStudy3 个月前
从0到1玩转 Google SEO
python·搜索引擎·github·全文检索·中文分词·solr·lucene
健康平安的活着4 个月前
es7.x es的高亮与solr高亮查询的对比&对比说明
大数据·elasticsearch·solr