Elasticsearch 查询

基于词项和基于全文搜索

基于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": "",
          "post_tag": ""
        }
      }
    }
  }
}

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

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 查询)