3. 跨集群搜索的查询语法:remote索引模式、跨集群查询DSL、结果合并与排序
好,咱们接着聊。上一节我们把跨集群搜索的准备工作做完了——集群连上了,角色也配好了。这一节,我带你看看实际干活的时候怎么写查询。
说白了,跨集群搜索的查询语法,核心就三件事:怎么指路(remote索引模式)、怎么写条件(跨集群DSL)、怎么收尾(结果合并与排序)。
我个人习惯把这三步拆开理解,这样遇到问题也好定位。咱们一个一个来。
3.1 remote索引模式:给查询指路
跨集群搜索最直观的写法,就是直接在索引名前面加上集群名。格式很简单:
cluster_name:index_name
举个例子。假设我有两个集群:cluster_a 和 cluster_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本身没有任何变化。
你该怎么写 match、term、range、bool,跨集群搜索里还怎么写。唯一的变化就是索引名前面多了个集群前缀。
看个实际例子。假设我要查两个集群里,最近一小时状态为 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" } }
]
}
你看,除了索引名那块,其他跟单集群查询一模一样。我在项目中遇到过很多次,开发同事跑来问我“跨集群查询是不是不能用聚合?”——其实都能用。聚合、排序、分页,全都支持。
不过有一点要提醒你:跨集群查询不支持 scroll 和 search_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-east、dc-west、dc-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解决
下一节,我会讲讲跨集群搜索的性能调优。说实话,跨集群查询最怕的就是慢——网络延迟、数据倾斜、连接池耗尽,这些问题我在线上都踩过。到时候我会把排查思路和优化方案都分享出来。