ES-Bouncing Results Problem

27 阅读4分钟

用ES这条语句去es集群中查询数据的时候出现一个非常诡异的问题 , 每次查询的结果居然不一样.???

q.Query(req). 
Sort("_score", false). 
Sort("_id", false)

www.cnblogs.com/junjiang3/p…

elasticsearch.cn/question/11…

www.cnblogs.com/junjiang3/p…

developer.aliyun.com/article/789…

一、为啥 _score + _id 排序还会出现结果抖动?

核心逻辑链:_score 重复 → 跨分片合并有 “优先级争议” → 主副分片争议解法不同 → 轮询路由切换分片 → 结果抖动。

咱们一步步拆,结合实际场景:

1. 前提:_score 重复是绕不开的 “导火索”

你排序是「先按 _score 降序,再按 _id 降序」,但 _score(相关性得分)太容易重复了:

  • 比如查询 “安全管理平台”,100 个文档都完全命中关键词,ES 计算的 _score 全是 3.8(完全相同);
  • 这时候排序就只能靠 _id 兜底,但 _id 的作用范围有 “漏洞”—— 只在「同一个分片内」有用,跨分片没用。

2. 关键问题:跨分片合并时,_id 说了不算!

ES 集群数据是分片存储的(比如 2 个分片),查询时不会把所有分片的文档全拉到一起排序(性能太差),而是按「分片内先排 → 跨分片合并」的流程:

  • 分片内排序:_score 相同的文档,按 _id 降序排(符合你的配置),比如:

    • 分片 1:doc-10(_id=10)→ doc-08(_id=8)(_id 降序);
    • 分片 2:doc-09(_id=9)→ doc-07(_id=7)(_id 降序);
  • 跨分片合并:ES 要把两个分片的结果合并成最终 Top N,但这里有个 “争议”—— 分片 1 的 doc-10(_id=10)和分片 2 的 doc-09(_id=9),_score 相同,且不在一个分片,没法直接比 _id

  • ES 的解决办法:用「分片优先级」来决定顺序(比如按分片 ID、响应速度、负载等),但 主分片和副分片的 “分片优先级规则不一样”

    • 主分片合并规则:按「分片 ID 从小到大」→ 先拿分片 1 的 doc-10、doc-08,再拿分片 2 的 doc-09、doc-07 → 最终排序:10→8→9→7;
    • 副分片合并规则:按「分片响应速度从快到慢」→ 先拿分片 2 的 doc-09、doc-07,再拿分片 1 的 doc-10、doc-08 → 最终排序:9→7→10→8;

这里要注意:_id 确实是全局唯一的,但它只能解决 “同一个分片内” 的排序,跨分片合并时,「分片优先级」比 _id 排序优先级更高 —— 所以哪怕 _id 唯一,跨分片的文档顺序还是会乱。

3. 触发条件:轮询路由让查询 “在两个答案间切换”

ES 为了负载均衡,默认会让相邻查询「轮流命中主分片或副分片」:

  • 第一次查询:路由到主分片 → 拿到 Top3:[10,8,9];
  • 第二次查询:路由到副分片 → 拿到 Top3:[9,7,10];
  • 最终现象:两次查询的 Top10 结果不一样(部分文档位置互换、甚至被挤出),这就是 “结果抖动”。

总结抖动的核心原因:

不是 _id 不唯一,而是「跨分片合并时主副分片的优先级规则不同」,再加上「轮询路由切换分片」,导致相同查询拿到不同合并结果。

二、s.Preference("default-fixed-key") 是怎么减少抖动的?

preference 参数的核心作用:把查询 “绑死” 在同一组分片(主或副),不让它在主副分片间切换,从而让合并规则固定,结果稳定。

1. 先搞懂 preference 的工作原理:

  • ES 会根据 preference 后面的字符串(比如 fixed-key)计算一个哈希值;
  • 这个哈希值会映射到集群中的「特定一组分片」(比如只映射到主分片,或只映射到某几个副分片);
  • 之后,所有带这个 preference 参数的查询,都会被强制路由到这一组分片,不会再轮询切换到其他分片。

2. 具体怎么解决抖动?

结合之前的场景:

  • 你添加 s.Preference("fixed-key") 后,查询会被固定路由到「主分片」(假设哈希映射到主分片);
  • 第一次查询:路由到主分片 → 合并规则是 “按分片 ID 排序” → 结果 [10,8,9];
  • 第二次查询:还是路由到主分片 → 合并规则不变 → 结果还是 [10,8,9];
  • 哪怕 _score 重复、跨分片合并有争议,但因为「合并规则固定」,结果也不会变 —— 抖动直接消失。