美文网首页
Elasticsearch 查询

Elasticsearch 查询

作者: 侧耳倾听y | 来源:发表于2021-11-22 10:25 被阅读0次

    基于词项和基于全文搜索

    基于Term的查询

    Term是表达语义的最小单位,在Elasticsearch中,Term查询,对输入不做分词。会将输入作为一个整体,在倒排索引中查找准确的词项,并且使用相关度算分公式,为每个包含该词项的文档进行相关度算分。

    准确查询不需要算分,可以通过 Constant Score 将查询转换成一个Filtering,避免算分,并利用缓存,提高性能。

    一些TermLevelQuery:

    • Term Query(精确查询)
    • Range Query(范围查询)
    • Exists Query(存在查询)
    • Prefix Query(前缀查询)
    • Wildcard Query(通配符查询)

    基于全文的查询

    索引和搜索时都会进行分词,查询字符串先传递到一个合适的分词器,然后生成一个共查询的词项列表。

    查询的时候,会先对输入的查询进行分词,然后每个词项逐个进行底层的查询,最终将结果进行合并,并为每个文档生成一个算分。

    查询 Matrix reloaded

    一些全文Query:

    • Match Query
    • Match Phrase Query
    • Query String Query

    比较

    假设有一条文档:{ "productID" : "XHDK-A-1293-#fJ3","desc":"iPhone" },desc字段是text类型,desc.keyword是keyword类型。
    有以下查询:

    {
      "query": {
        "term": {
          "desc": {
    // 查不到
            "value": "iPhone"
    // 可以查到
            // "value":"iphone"
          }
        }
      }
    }
    
    {
      "query": {
        "term": {
          "desc.keyword": {
    // 可以查到
            // "value": "iPhone"
    // 查不到
            "value":"iphone"
          }
        }
      }
    }
    // 查询keyword类型字段时,就算使用match也不会分词
    {
      "query": {
        "match": {
    // 可以查到
          "desc.keyword": "iPhone"
    // 查不到
            "desc.keyword":"iphone"
        }
      }
    }
    

    使用constant_score

    很多场景,搜索的结果,只有"是" 和 "否"两种结果,这时候算分就没有必要,使用constant_score,可以提高查询效率。

    POST /products/_search
    {
      "query": {
        "constant_score": {
          "filter": {
            "term": {
              "desc": "iPhone"
            }
          }
        }
      }
    }
    

    复合查询

    ES中的查询分为:

    • Query Context:相关性算分;
    • Filter Context:不需要算分(yes or no),可以利用cache,获得更好的性能。

    多条件查询可以使用bool查询。一个bool查询,是一个或者多个查询子句的组合,总共包括4种子句,其中2种会影响算分,2种不影响算分。
    must - 必须匹配。贡献算分。
    should - 选择性匹配。贡献算分。
    must_not - Filter Context 查询子句,必须不能匹配。
    filter - Filter Context 必须匹配,但是不贡献算分。

    相关性并不只是全文本检索的专利。也适用于yes | no的子句,匹配的子句越多,相关性评分越高。如果多条查询子句被合并为一条复合查询语句,比如bool 查询,则每个查询子句计算得出的评分会被合并到总的相关性评分中。

    bool查询中:

    • 子查询可以任意顺序出现;
    • 可以嵌套多个查询;
      1.同一层级下单竞争字段,具有相同的权重;
    # brown red quick dog有相同权重
    POST /animals/_search
    {
      "query": {
        "bool": {
          "should": [
            { "term": { "text": "brown" }},
            { "term": { "text": "red" }},
            { "term": { "text": "quick"   }},
            { "term": { "text": "dog"   }}
          ]
        }
      }
    }
    

    2.通过嵌套bool查询,可以改变对算分的影响。

    # red和brown加起来,和上面的才有相同的权重
    POST /animals/_search
    {
      "query": {
        "bool": {
          "should": [
            { "term": { "text": "quick" }},
            { "term": { "text": "dog"   }},
            {
              "bool":{
                "should":[
                   { "term": { "text": "red" }},
                     { "term": { "text": "brown" }}
                ]
              }
    
            }
          ]
        }
      }
    }
    
    • 如果bool查询中,没有must条件,should中必须至少满足一条查询。

    Boosting & Boosting Query

    • Boosting
    # 通过boost调整算分
    POST blogs/_search
    {
      "query": {
        "bool": {
          "should": [
            {"match": {
              "title": {
                "query": "apple,ipad",
                "boost": 1.1
              }
            }},
    
            {"match": {
              "content": {
                "query": "apple,ipad",
                "boost":2
              }
            }}
          ]
        }
      }
    }
    
    • Boosting Query
    # pie内容的数据往后排
    POST news/_search
    {
      "query": {
        "boosting": {
          "positive": {
            "match": {
              "content": "apple"
            }
          },
          "negative": {
            "match": {
              "content": "pie"
            }
          },
          "negative_boost": 0.5
        }
      }
    }
    

    单字段多字符串查询

    Disjunction Max Query

    POST /blogs/_search
    {
        "query": {
            "bool": {
                "should": [
                    { "match": { "title": "Brown fox" }},
                    { "match": { "body":  "Brown fox" }}
                ]
            }
        }
    }
    

    上面例子中,title与body相互竞争,不应该将分数简单叠加,而是应该找到单个最佳匹配字段的评分。

    Disjunction Max Query是将任何与任一查询匹配的文档作为结果返回。采用字段上最匹配的评分最终评分返回。

    # 使用一个字段上的最高评分作为最终评分
    POST blogs/_search
    {
        "query": {
            "dis_max": {
                "queries": [
                    { "match": { "title": "Brown fox" }},
                    { "match": { "body":  "Brown fox" }}
                ]
            }
        }
    }
    

    通过Tie Breaker参数调整:

    • 获得最佳匹配语句的评分;
    • 将其他匹配语句的评分与tie_breaker相乘;
    • 对以上评分求和并规范化。

    Tie Breaker是一个介于0到1之间的浮点数,0代表使用最佳匹配,1代表所有语句同样重要。

    MultiMatch

    单字符串多字段查询三种场景:

    • 最佳字段(Best Fields):
      当字段之间相互竞争,又相互关联。例如title 和body 这样的字段。评分来自最匹配字段。
    # Best Fields是默认类型,可以不用指定;Minimum should match等参数可以传递到生成的query中。
    POST blogs/_search
    {
      "query": {
        "multi_match": {
          "type": "best_fields",
          "query": "Quick pets",
          "fields": ["title","body"],
          "tie_breaker": 0.2,
          "minimum_should_match": "20%"
        }
      }
    }
    
    • 多数字段(Most Fields):
      处理英文内容时,一种常见的手段是,在主字段(English Analyzer),抽取词干,加入同义词,以匹配更多文档。相同的文本,加入子字段(Standard Analyzer),以提供更加精确的匹配。其他字段作为匹配文档提高相关度的信号。匹配字段越多越好。
    PUT /titles
    {
      "mappings": {
        "properties": {
          "title": {
            "type": "text",
            "analyzer": "english",
            "fields": {"std": {"type": "text","analyzer": "standard"}}
          }
        }
      }
    }
    
    GET /titles/_search
    {
       "query": {
            "multi_match": {
                "query":  "barking dogs",
                "type":   "most_fields",
                "fields": [ "title", "title.std" ]
            }
        }
    }
    
    • 混合字段(Cross Field):
      对于某些实体,例如人名、地址、图书信息。需要在多个字段中确定信息,单个字段只能作为整体的一部分。希望在任何这些列出的字段中找到尽可能多的词。
    GET /titles/_search
    {
       "query": {
            "multi_match": {
                "query":  "barking dogs road",
                "type":   "cross_fields",
                "operator": "and", 
                "fields": [ "title" ]
            }
        }
    }
    

    使用别名

    # 可以添加过滤器
    POST _aliases
    {
      "actions": [
        {
          "add": {
            "index": "movies-2019",
            "alias": "movies-lastest-highrate",
            "filter": {
              "range": {
                "rating": {
                  "gte": 4
                }
              }
            }
          }
        }
      ]
    }
    

    Function Score Query

    可以在查询结束后,对每一个匹配的文档进行一系列的重新算分,根据新生成的分数进行排序。

    提供了几种默认的计算分值的函数:

    1. Weight:为每一个文档设置一个简单而不被规范化的权重;
    2. Field Value Factor:使用该数值来修改_score,例如将"热度"和"点赞数"作为算分的参考要素;
    3. Randow Score:为每一个用户使用一个不同的,随机算分结果;
    4. 衰减函数:以某个字段的值为标准,距离某个值越近,得分越高;
    5. Script Score:自定义脚本完全控制所需逻辑。
    // 按欢迎程度提升权重:搜索的评分作为排序的主要依据,同时votes多的靠前。
    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
    // 新的算分 = 老的算分 * 投票数
    // 票数为0或者票数很大的时候差异很大
          "field_value_factor": {
            "field": "votes"
          }
        }
      }
    }
    
    • 使用Modifier 平滑曲线
    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
    // 新的算分 = 老的算分 * log(1 + 投票数)
    // 别的modifier:log、log2p、ln、ln1p、ln2p、square、sqrt、reciprocal
          "field_value_factor": {
            "field": "votes",
            "modifier": "log1p"
          }
        }
      }
    }
    
    • 引入Factor(曲线更平滑)
    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
    // 新的算分 = 老的算分 * log(1 + factor * 投票数)
          "field_value_factor": {
            "field": "votes",
            "modifier": "log1p" ,
            "factor": 0.1
          }
        }
      }
    }
    
    • Boost Mode 和Max Boost
      Boost Mode :
      1.Multiply:算分与函数值的乘积;
      2.Sum:算分与函数值的和;
      3.Min/ Max:算分与函数取 最小/最大值;
      4.Replace:使用函数值取代算分。
      Max Boost 可以将算分控制在一个最大值。
    POST /blogs/_search
    {
      "query": {
        "function_score": {
          "query": {
            "multi_match": {
              "query":    "popularity",
              "fields": [ "title", "content" ]
            }
          },
          "field_value_factor": {
            "field": "votes",
            "modifier": "log1p" ,
            "factor": 0.1
          },
          "boost_mode": "sum",
          "max_boost": 3
        }
      }
    }
    
    • 一致性随机函数
    POST /blogs/_search
    {
      "query": {
        "function_score": {
    // 不同seed值,返回结果算分不同
          "random_score": {
            "seed": 911119
          }
        }
      }
    }
    

    Suggester

    现代的搜索引擎,一般提供Suggest as you type的功能。
    帮助用户在输入搜索过程中,进行自动补全或者纠错。通过协助用户输入更加精准的关键词,提高后续搜索阶段文档匹配的程度。

    搜索引擎中类似的功能,在Elasticsearch 中是通过Suggester API实现的。
    原理:将输入的文本分解为Token,然后在索引的字典里查找相似的Term 并返回。

    根据不同的适用场景,Elasticsearch 设计了4中类别的 Suggester:

    • Term & Phrase Suggester;
    • Complete & Context Suggester。

    搜索建议

    • Term Suggester

    Suggester 就是一种特殊类型的搜索。

    // 每个建议都包含了一个算分,相似性是通过 Levenshtein Edit Distance 的算法实现的。
    // 核心思想就是一个词改动多少字符就可以和另外一个词一致。提供了很多可选参数来控制相似性的模糊程度。例如“max_edits”
    POST /articles/_search
    {
      "size": 1,
      "query": {
        "match": {
          "body": "lucen rock"
        }
      },
      "suggest": {
        "term-suggestion": {
    // text 里是调用时候提供的文本,通常来自用户输入的内容
          "text": "lucen rock",
          "term": {
    // 当无法搜索到结果时(missing),返回建议的词
            "suggest_mode": "missing",
    // 会到 指定的字段body 上搜索
            "field": "body",
            "prefix_length":0,
            "sort": "frequency"
          }
        }
      }
    }
    

    Suggestion Mode:

    • Missing - 如果索引中已经存在,就不提供建议;
    • Popular - 推荐出现频率更加高的词;
    • Always - 无论是否存在,都提供建议。
    • Phrase Suggester
      Phrase Suggester 在 Term Suggester 上增加了一些额外的逻辑,例如一些参数:
    • Suggest Mode:missing、popular;
    • Max Errors:最多可以拼错的Terms 数;
    • Confidence:限制返回结果数,默认为1。
    POST /articles/_search
    {
      "suggest": {
        "my-suggestion": {
          "text": "lucne and elasticsear rock hello world ",
          "phrase": {
            "field": "body",
            "max_errors":2,
            "confidence":0,
            "direct_generator":[{
              "field":"body",
              "suggest_mode":"always"
            }],
            "highlight": {
              "pre_tag": "<em>",
              "post_tag": "</em>"
            }
          }
        }
      }
    }
    

    自动补全和基于上下文提示

    Completion Suggester 提供了“自动完成(Auto Complete)”的功能。用户每输入一个字符,就需要即时发送一个查询请求到后端查询匹配项;
    对性能要求比较苛刻。Elasticsearch 采用了不同的数据结构,并非通过倒排索引来完成。而是将 Analyze 的数据编码成 FST 和索引 一起存放。FST 会被 ES整个加载进内存,速度很快;
    FST 只能用于前缀查找。

    使用Completion Suggester 的一些步骤:

    • 定义Mapping,使用“completion”type;
    PUT articles
    {
      "mappings": {
        "properties": {
          "title_completion":{
            "type": "completion"
          }
        }
      }
    }
    
    • 索引数据
    • 运行“suggest”查询,得到搜索建议。
    // 会返回 elk 开头的数据
    POST articles/_search
    {
      "size": 0,
      "suggest": {
        "article-suggester": {
          "prefix": "elk ",
          "completion": {
            "field": "title_completion"
          }
        }
      }
    }
    

    Context Suggester

    Completion Suggester 的扩展,可以在搜索中加入更多的上下文信息,例如,输入“star”

    • 咖啡相关:建议“Starbucks”;
    • 电影相关:“star wars”

    两种类型的 Context:

    • Category - 任意的字符串;
    • Geo - 地理位置信息。

    实现Context Suggester 的具体步骤:

    • 定制一个Mapping;
    PUT comments/_mapping
    {
      "properties": {
        "comment_autocomplete":{
          "type": "completion",
    // 
          "contexts":[{
            "type":"category",
            "name":"comment_category"
          }]
        }
      }
    }
    
    • 索引数据,并且为每个文档加入Context 信息;
    // 分类是电影
    POST comments/_doc
    {
      "comment":"I love the star war movies",
      "comment_autocomplete":{
        "input":["star wars"],
        "contexts":{
          "comment_category":"movies"
        }
      }
    }
    // 分类是咖啡
    POST comments/_doc
    {
      "comment":"Where can I find a Starbucks",
      "comment_autocomplete":{
        "input":["starbucks"],
        "contexts":{
          "comment_category":"coffee"
        }
      }
    }
    
    • 结合Context 进行 Suggestion 查询。
    // 会查出咖啡类型的数据
    POST comments/_search
    {
      "suggest": {
        "MY_SUGGESTION": {
          "prefix": "sta",
          "completion":{
            "field":"comment_autocomplete",
            "contexts":{
              "comment_category":"coffee"
            }
          }
        }
      }
    }
    

    比较

    • 精准度:
      Completion > Phrase > Term
    • 召回率
      Term > Phrase > Completion
    • 性能
      Completion > Phrase > Term

    跨集群搜索

    水平扩展的痛点

    单集群 - 当水平扩展时,节点数不能无限增加,因为当集群的 meta 信息(节点,索引,集群状态)过多,会导致更新压力变大,单个Active Master 会成为性能瓶颈,导致整个集群无法正常工作。

    早期版本,通过Tribe Node 可以实现多集群访问的需求,但还存在一定的问题:

    • Tribe Node 会以 Client Node 的方式加入每个集群。集群中Master 节点的任务变更需要 Tribe Node 的回应才能继续;
    • Tribe Node 不保存Cluster State 的信息,一旦重启,初始化很慢;
    • 当多个集群存在索引重名的情况时,只能设置一种Prefer 规则。

    Cross Cluster Search

    早期 Tribe Node 的方案 存在一定的问题,现在已被 Deprecated;
    Elasticsearch 5.3 引入了跨集群搜索的功能(Cross Cluster Search),推荐使用:

    1. 允许任何节点扮演 federated节点,以轻量的方式,将搜索请求进行代理;
    2. 不需要以 Client Node 的形式加入其它集群。
    • 配置
    //在每个集群上设置动态的设置
    PUT _cluster/settings
    {
      "persistent": {
        "cluster": {
          "remote": {
            "cluster0": {
              "seeds": [
                "127.0.0.1:9300"
              ],
              "transport.ping_schedule": "30s"
            },
            "cluster1": {
              "seeds": [
                "127.0.0.1:9301"
              ],
              "transport.compress": true,
              "skip_unavailable": true
            },
            "cluster2": {
              "seeds": [
                "127.0.0.1:9302"
              ]
            }
          }
        }
      }
    }
    
    • 查询
    // 在第一个集群搜索
    GET /users,cluster1:users,cluster2:users/_search
    {
      "query": {
        "range": {
          "age": {
            "gte": 20,
            "lte": 40
          }
        }
      }
    }
    

    相关文章

      网友评论

          本文标题:Elasticsearch 查询

          本文链接:https://www.haomeiwen.com/subject/kywptrtx.html