# Recipes（诀窍）

原文链接 : [https://www.elastic.co/guide/en/elasticsearch/reference/5.3/recipes.html](https://www.elastic.co/guide/en/elasticsearch/reference/5.3/recipes.html)

译文链接 : [http://www.apache.wiki/pages/viewpage.action?pageId=10027113](http://www.apache.wiki/pages/viewpage.action?pageId=10027113)

贡献者 : [李坚](/display/~lijian)，[ApacheCN](/display/~apachecn)，[Apache中文网](/display/~apachechina)

### Mixing exact search with stemming（用 stemming（词干）进行精确搜索）

在构建搜索应用程序时，stemming （词干）经常是必须有的，因为它描述了针对 skiing 上的查询，以匹配包含 ski 或 skis 的文档。但是用户如果想要指定 skiing 来搜索呢？执行此操作的典型方式是使用 multi-field，以便以两种不同的方式对相同的内容进行索引。

```
PUT index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "english_exact": {
          "tokenizer": "standard",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "type": {
      "properties": {
        "body": {
          "type": "text",
          "analyzer": "english",
          "fields": {
            "exact": {
              "type": "text",
              "analyzer": "english_exact"
            }
          }
        }
      }
    }
  }
}

PUT index/type/1
{
  "body": "Ski resort"
}

PUT index/type/2
{
  "body": "A pair of skis"
}

POST index/_refresh
```

按照这样的设置，在 body 中搜索 ski 这两个 documents（文档） 都将会被返回：

```
GET index/_search
{
  "query": {
    "simple_query_string": {
      "fields": [ "body" ],
      "query": "ski"
    }
  }
}
```

```
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0.25811607,
    "hits": [
      {
        "_index": "index",
        "_type": "type",
        "_id": "2",
        "_score": 0.25811607,
        "_source": {
          "body": "A pair of skis"
        }
      },
      {
        "_index": "index",
        "_type": "type",
        "_id": "1",
        "_score": 0.25811607,
        "_source": {
          "body": "Ski resort"
        }
      }
    ]
  }
}
```

另一方面，搜索 body.exact 上的 ski 只会返回文档 1，因为 body.exact 的分析链不执行 stemming。

```
GET index/_search
{
  "query": {
    "simple_query_string": {
      "fields": [ "body.exact" ],
      "query": "ski"
    }
  }
}
```

```
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.25811607,
    "hits": [
      {
        "_index": "index",
        "_type": "type",
        "_id": "1",
        "_score": 0.25811607,
        "_source": {
          "body": "Ski resort"
        }
      }
    ]
  }
}
```

这还不是最终暴露给用户的东西，因为我们需要有一个方法来确定是否完全匹配他们正在寻找的东西，并重定向到相应的字段。如果只有部分查询需要匹配，而其他部分仍然应该考虑在内，该怎么办？

幸运的是，query_string 和 simple_query_string 查询具有解决这个问题的功能：quote_field_suffix。
这告诉 Elasticsearch，引号之间出现的单词将被重定向到一个不同的字段，如下所示：

```
GET index/_search
{
  "query": {
    "simple_query_string": {
      "fields": [ "body" ],
      "quote_field_suffix": ".exact",
      "query": "\"ski\""
    }
  }
}
```

```
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.25811607,
    "hits": [
      {
        "_index": "index",
        "_type": "type",
        "_id": "1",
        "_score": 0.25811607,
        "_source": {
          "body": "Ski resort"
        }
      }
    ]
  }
}
```

在上述情况下，由于 ski 是在引号之间，所以在 body.exact 字段上搜索，由于 quote_field_suffix 参数存在，所以只有文档1匹配。这样，用户可以根据需要将精确搜索与搜索结合在一起。

### Getting consistent scoring（获得一致的得分）

实际上，Elasticsearch 运行 shards（分片） 和 replicas（副本）的时候会增加压力，同时也会获得一个比较好的得分。

#### Scores are not reproducible（得分不可重现）

同一个用户连续两次运行相同的请求，documents（文档） 不会在同一个 order 中返回【将会返回两次】，这是一个非常糟糕的体验，不是吗？不幸的是，如果您开启了副本（index.number_of_replicas 大于 0），这些可能会发生。原因是 Elasticsearch 以循环方式选择查询应该进行的 shards（分片），因此如果您连续运行相同的查询两次，那么它将转到相同 shards（分片）的不同 replicas（副本）去查询。

为什么会出现这种情况？ index（索引）统计信息是得分的一个重要组成部分。由于已删除的文档，这些index（索引）统计数据可能因同一分片的副本而异。documents（文档）被删除或更新时，旧的 documents（文档）不会立即从索引中删除，它只是被标记为已删除，并且只有在下一次合并该旧 documents（文档）所属段时才会从磁盘中删除 。但是由于实际的原因，这些已删除的 documents（文档）被考虑在 index（索引）统计上。所以假设主 shards（分片）刚刚完成了一个大的合并，删除了大量被标记为已删除的 documents（文档），那么它可能具有与 replicas（副本）（仍然有大量已删除的文档）有很大不同的 index（索引）统计信息，因此分数也不同。

 解决此问题推荐的方法是使用标识被记录的用户（例如，用户 ID 或会话 ID）作为首选项的字符串。这样可以确保给定用户的所有查询都将始终打到相同的 shards（分片），因此分数在查询之间保持更加一致。

这样做有另一个好处：当两个 documents（文档）具有相同的分数时，它们将按照 Lucene 内部 doc id（与_id或_uid无关）排序。但是，这些 documents（文档）在同一个 shards（分片）的 replicas（副本）上可能不同。 所以总是碰到同样的 shards（分片），我们会得到更一致的顺序的 documents（文档）并具有相同的分数。

#### Relevancy looks wrong（相关性错误）

如果您注意到具有相同内容的两个 documents（文档）获得不同的分数，或者完全匹配不排在第一位，则该问题可能与 shards（分片）有关。默认情况下，Elasticsearch 使每个 shards（分片）都生成自己的分数。然而，由于 index（索引）统计数据是分数的重要贡献者，所以如果 shards（分片）具有相似的 index（索引）统计信息，这只能使它运行的更好。假设默认情况下 documents（文档）均匀分配到 shards（分片），所以 index（索引）统计数据应该非常相似，评分将按预期方式工作。但是，如果您在索引时使用 routing（路由）， - 查询多个 index（索引），或者索引中的数据太少，在搜索请求中所涉及的所有碎片都没有类似的索引统计信息，相关性可能很差。

如果您有一个小数据集，解决此问题的最简单的方法是将所有内容索引到具有单个 shards（分片）（index.number_of_shards：1）的 index（索引）中。 那么所有文件的 index（索引）统计将是一样的，分数将是一致的。

否则，解决这个问题的推荐方式来是使用 dfs_query_then_fetch 搜索类型。这将使 Elasticsearch 对所有相关的 shards（分片）进行初始化往返，询问它们相对于查询的 index（索引）统计信息，然后协调节点将合并这些统计信息，并在请求 shards（分片）执行查询阶段时发送合并的统计信息， 因此 shards（分片）可以使用这些全局统计数据而不是自己的统计数据来进行评分。

在大多数情况下，这次额外的往返操作资源消耗不大。但是，如果您的查询包含大量的 fileds / terms 或  fuzzy queries（模糊查询），注意，只统计数据的话可能资源消耗会比较高，因为所有 term 都必须在 terms dictionaries 中才能查找统计信息。