3. 跨集群搜索的查询语法:remote索引模式、跨集群查询DSL、结果合并与排序

好,咱们接着聊。上一节我们把跨集群搜索的准备工作做完了——集群连上了,角色也配好了。这一节,我带你看看实际干活的时候怎么写查询。

说白了,跨集群搜索的查询语法,核心就三件事:怎么指路(remote索引模式)、怎么写条件(跨集群DSL)、怎么收尾(结果合并与排序)。

我个人习惯把这三步拆开理解,这样遇到问题也好定位。咱们一个一个来。

3.1 remote索引模式:给查询指路

跨集群搜索最直观的写法,就是直接在索引名前面加上集群名。格式很简单:

cluster_name:index_name

举个例子。假设我有两个集群:cluster_acluster_b。我想同时查这两个集群里的 logs 索引,可以这么写:

GET cluster_a:logs,cluster_b:logs/_search
{
  "query": {
    "match_all": {}
  }
}

嗯,这里要注意:集群名必须跟你配置的 cluster.remote 名字完全一致。大小写敏感,别写错了。我曾经在线上遇到过一个问题,配置里写的是 cluster-a,查询时写成了 cluster_a,结果查了半天没数据——这种低级错误,排查起来最浪费时间。

还有一种写法,用通配符。比如我想查 cluster_a 里所有以 app- 开头的索引:

GET cluster_a:app-*/_search
{
  "query": {
    "match": {
      "status": "error"
    }
  }
}

通配符在跨集群场景下特别好用。你想想看,如果每个集群里都有几十个索引,一个个列出来得多累。

小技巧: 如果你不确定集群名或索引名对不对,可以先跑一个 GET _remote/info 看看。这个API会返回所有已连接的远程集群信息,包括它们的连接状态和版本号。我每次配置完新集群,都会先跑一下这个命令确认连通性。

3.2 跨集群查询DSL:写法跟本地一样

这一点可能是新手最容易困惑的地方。很多人以为跨集群查询要用什么特殊的语法,其实不然。查询DSL本身没有任何变化

你该怎么写 matchtermrangebool,跨集群搜索里还怎么写。唯一的变化就是索引名前面多了个集群前缀。

看个实际例子。假设我要查两个集群里,最近一小时状态为 critical 的日志:

GET cluster_a:logs-*,cluster_b:logs-*/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "status": "critical" } },
        { "range": { "@timestamp": { "gte": "now-1h" } } }
      ]
    }
  },
  "size": 100,
  "sort": [
    { "@timestamp": { "order": "desc" } }
  ]
}

你看,除了索引名那块,其他跟单集群查询一模一样。我在项目中遇到过很多次,开发同事跑来问我“跨集群查询是不是不能用聚合?”——其实都能用。聚合、排序、分页,全都支持。

不过有一点要提醒你:跨集群查询不支持 scrollsearch_after 做深度分页。这是Elasticsearch的硬限制。为什么?因为每个集群各自维护自己的排序上下文,合并时没法保证全局一致性。如果你需要拉取大量数据,建议改用 _reindex 或者数据复制方案。

避坑指南: 我曾经在生产环境用 scroll 做跨集群深度分页,结果发现返回的数据有重复和遗漏。查了半天文档才意识到,跨集群场景下 scroll 的游标是每个集群独立的,合并时会出现乱序。从那以后,我只要涉及跨集群大量数据导出,一律改用 _reindex

3.3 结果合并与排序:谁说了算?

好,查询发出去了,数据从两个集群回来了。这时候问题来了:结果怎么合并?排序听谁的?

Elasticsearch的处理方式是这样的:

  • 协调节点(就是你发请求的那个集群)负责把查询分发给所有远程集群
  • 每个远程集群独立执行查询,返回各自的前N条结果
  • 协调节点收到所有结果后,重新排序,再取最终的前N条

这里有个关键点:每个集群返回的文档数 = 你指定的 size。比如你查两个集群,size 设为 10,那么每个集群都会返回 10 条,协调节点收到 20 条,再排序取前 10。

这个机制会导致一个问题:如果某个集群的数据特别多,它的前10条可能比另一个集群的前10条分数更高,但最终结果可能漏掉另一个集群里分数稍低但依然有价值的文档。说白了,跨集群搜索的排序是“先局部,再全局”,不是真正的全局排序。

那怎么解决?有两个思路:

方案 做法 适用场景
增大 size 把每个集群返回的文档数设大,比如 size: 1000,让协调节点有更多数据做全局排序 数据量不大,对精度要求高
改用 _reindex 把远程集群的数据复制到本地,再做真正的全局查询 数据量大,需要精确排序或深度分页

我个人习惯的做法是:如果只是做监控大盘或实时看板,跨集群搜索的默认行为完全够用。但如果是要做报表导出或精确分析,我会把数据集中到一个集群里再查。

3.4 一个完整的实战例子

说了这么多,咱们来一个完整的例子。假设我有三个集群:dc-eastdc-westdc-central。每个集群里都有 nginx-access 索引。我想查最近5分钟,响应时间超过3秒的请求,按时间倒序排,取前20条。

GET dc-east:nginx-access,dc-west:nginx-access,dc-central:nginx-access/_search
{
  "query": {
    "bool": {
      "filter": [
        { "range": { "@timestamp": { "gte": "now-5m" } } },
        { "range": { "response_time": { "gt": 3000 } } }
      ]
    }
  },
  "size": 20,
  "sort": [
    { "@timestamp": { "order": "desc" } }
  ],
  "_source": ["@timestamp", "client_ip", "request_path", "response_time", "status"]
}

这个查询会返回三个集群中,所有符合条件的请求,按时间倒序排好。注意我用了 filter 而不是 must,因为我不需要算分,只要过滤条件。这样性能会好一些。

重点回顾:
  • remote索引模式:集群名:索引名,支持通配符
  • 查询DSL:跟本地查询完全一样,没有特殊语法
  • 结果合并:协调节点做二次排序,每个集群返回 size
  • 排序精度:增大 size 或改用 _reindex 解决

下一节,我会讲讲跨集群搜索的性能调优。说实话,跨集群查询最怕的就是慢——网络延迟、数据倾斜、连接池耗尽,这些问题我在线上都踩过。到时候我会把排查思路和优化方案都分享出来。