Elasticsearch查询之Disjunction Max Query

前言

Disjunction Max Query 又称最佳 best_fields 匹配策略,用来优化当查询关键词出现在多个字段中,以单个字段的最大评分作为文档的最终评分,从而使得匹配结果更加合理

写入数据

如下的两条例子数据:

docId: 1
title: java python go
content: java scala

docId: 2
title: kubernetes docker
content: java spring python

POST test01/doc/_bulk
{ "index" : { "_id" : "1" } }
{ "title" : "kubernetes docker", "content": "java spring python" }
{ "index" : { "_id" : "2" } }
{ "title" : "java python go", "content": "java scala" }

查询数据

GET test01/_search?
{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "java spring"}},
        {"match": {"content": "java spring"}}
        ]
    }

  }
}

结果如下:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 6,
    "successful" : 6,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "java python go",
          "content" : "java scala"
        }
      },
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "kubernetes docker",
          "content" : "java spring python"
        }
      }
    ]
  }
}

可以看到,两个 doc 的 score 一样,尽管从内容上看 id=1 的 数据更应该排在前面,但默认的排序策略是有可能会导致id=2 的数据排在 id=1 的前面。

原理分析

在 ES 的默认评分策略下,boolean 查询的score是所有 should 条件匹配到的评分相加,下面简化分析一下得分流程,真实评分会比这个复杂,但大致思路一致:

在 id=1 中数据,由于 title 无命中,但 content 匹配到了 2 个关键词,所以得分为 2.

在 id=2 中数据,其 title 命中 1 个关键词 ,并且其 content 也命中一个关键词,所以最后得分也为 2.

从而得出了最终结果两个 doc 的得分一样

dis_max 查询

使用 dis_max查询优化匹配机制,采用单字段最大评分,作为最终的 score

GET test01/_search?
{
  "query": {
    "dis_max": {
      "queries": [
        {"match": {"title": "java spring"}},
        {"match": {"content": "java spring"}}
        ]
    }

  }
}

结果如下:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 6,
    "successful" : 6,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "kubernetes docker",
          "content" : "java spring python"
        }
      },
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "java python go",
          "content" : "java scala"
        }
      }
    ]
  }
}

结果已经符合预期了

tie_breaker参数

前面的结果我们看到已经符合预期了,现在如果我们用 dis max 继续查询另一种 case:


GET test01/_search?
{
  "query": {
    "dis_max": {
      "queries": [
        {"match": {"title": "python scala"}},
        {"match": {"content": "python scala"}}
        ]
    }
  }
}

结果如下:


    "hits" : [
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "java python go",
          "content" : "java scala"
        }
      },
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "kubernetes docker",
          "content" : "java spring python"
        }
      }
    ]

可以看到两者的评分又一样了,但从实际来说,我们肯定希望 id = 2 的文档的得分更高的,因为其在多个字段中都有命中,但因为 dis max的匹配评分机制,又导致忽略了其他字段的评分的贡献,这个时候就需要进一步优化了,在 dis max 里面可以使用 tie_breaker 参数来控制,tie_breaker的值默认是 0 ,其设置了tie_breaker参数之后,dis max 的工作原理如下:

  1. 从得分最高的匹配子句中获取相关性得分。
  2. 将任何其他匹配子句的分数乘以 tie_breaker 值。
  3. 将最高分数和其他子句相乘的分数进行累加,得到最终的排序 score 值。

改进后的查询语句如下:

GET test01/_search?
{
  "query": {
    "dis_max": {
      "queries": [
        {"match": {"title": "python scala"}},
        {"match": {"content": "python scala"}}
        ],
        "tie_breaker": 0.4
    }

  }
}

查询结果:


  "hits" : {
    "total" : 2,
    "max_score" : 0.40275493,
    "hits" : [
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 0.40275493,
        "_source" : {
          "title" : "java python go",
          "content" : "java scala"
        }
      },
      {
        "_index" : "test01",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "kubernetes docker",
          "content" : "java spring python"
        }
      }
    ]
  }

这样结果就符合我们的预期了

总结

使用dis max 查询可以达到 best_fields 匹配的效果,在某些细分的检索场景下效果更好,但单纯的 dis max 查询会导致忽略其他字段评分贡献,这种一刀切的机制并不是最优的策略,所以需要配合 tie_breaker 参数,来弱化非 best field 子句的评分贡献,从而达到最终的优化效果

你可能感兴趣的:(elasticsearch,大数据,搜索引擎)