Recipes(诀窍)

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

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

贡献者 : 李坚ApacheCNApache中文网

Mixing exact search with stemming(用 stemming(词干)进行精确搜索)

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

  1. PUT index
  2. {
  3. "settings": {
  4. "analysis": {
  5. "analyzer": {
  6. "english_exact": {
  7. "tokenizer": "standard",
  8. "filter": [
  9. "lowercase"
  10. ]
  11. }
  12. }
  13. }
  14. },
  15. "mappings": {
  16. "type": {
  17. "properties": {
  18. "body": {
  19. "type": "text",
  20. "analyzer": "english",
  21. "fields": {
  22. "exact": {
  23. "type": "text",
  24. "analyzer": "english_exact"
  25. }
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }
  32. PUT index/type/1
  33. {
  34. "body": "Ski resort"
  35. }
  36. PUT index/type/2
  37. {
  38. "body": "A pair of skis"
  39. }
  40. POST index/_refresh

按照这样的设置,在 body 中搜索 ski 这两个 documents(文档) 都将会被返回:

  1. GET index/_search
  2. {
  3. "query": {
  4. "simple_query_string": {
  5. "fields": [ "body" ],
  6. "query": "ski"
  7. }
  8. }
  9. }
  1. {
  2. "took": 2,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 5,
  6. "successful": 5,
  7. "failed": 0
  8. },
  9. "hits": {
  10. "total": 2,
  11. "max_score": 0.25811607,
  12. "hits": [
  13. {
  14. "_index": "index",
  15. "_type": "type",
  16. "_id": "2",
  17. "_score": 0.25811607,
  18. "_source": {
  19. "body": "A pair of skis"
  20. }
  21. },
  22. {
  23. "_index": "index",
  24. "_type": "type",
  25. "_id": "1",
  26. "_score": 0.25811607,
  27. "_source": {
  28. "body": "Ski resort"
  29. }
  30. }
  31. ]
  32. }
  33. }

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

  1. GET index/_search
  2. {
  3. "query": {
  4. "simple_query_string": {
  5. "fields": [ "body.exact" ],
  6. "query": "ski"
  7. }
  8. }
  9. }
  1. {
  2. "took": 1,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 5,
  6. "successful": 5,
  7. "failed": 0
  8. },
  9. "hits": {
  10. "total": 1,
  11. "max_score": 0.25811607,
  12. "hits": [
  13. {
  14. "_index": "index",
  15. "_type": "type",
  16. "_id": "1",
  17. "_score": 0.25811607,
  18. "_source": {
  19. "body": "Ski resort"
  20. }
  21. }
  22. ]
  23. }
  24. }

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

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

  1. GET index/_search
  2. {
  3. "query": {
  4. "simple_query_string": {
  5. "fields": [ "body" ],
  6. "quote_field_suffix": ".exact",
  7. "query": "\"ski\""
  8. }
  9. }
  10. }
  1. {
  2. "took": 2,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 5,
  6. "successful": 5,
  7. "failed": 0
  8. },
  9. "hits": {
  10. "total": 1,
  11. "max_score": 0.25811607,
  12. "hits": [
  13. {
  14. "_index": "index",
  15. "_type": "type",
  16. "_id": "1",
  17. "_score": 0.25811607,
  18. "_source": {
  19. "body": "Ski resort"
  20. }
  21. }
  22. ]
  23. }
  24. }

在上述情况下,由于 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 中才能查找统计信息。