💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 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 中才能查找统计信息。