boolean model
boolean model: 所谓bool 就是 true false
match operator: or and not
bool => must/must not/should
term->doc分数
query "hello world" --> 过滤 --> hello / world / hello & world
bool --> must/must not/should --> 过滤 --> 包含 / 不包含 / 可能包含
doc --> 不打分数 --> 正或反 true or false --> 为了减少后续要计算的doc的数量,提升性能query: hello world
"match": {
"title": "hello world"
}
"bool": {
"should": [
{
"match": {
"title": "hello"
}
},
{
"match": {
"title": "world"
}
}
]
}
boost:权重
TF/IDF算法
relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度
Elasticsearch使用的是 term frequency/inverse document frequency算法,简称为TF/IDF算法
-
TF(词频term frequency): 关键词在每个doc中出现的次数 一个term在一个doc中,出现的次数越多,那么最后给的相关度评分就会越高
-
IDF(反文档词频inversed document frequency):关键词在整个索引中出现的次数 一个term在所有的doc中,出现的次数越多,那么最后给的相关度评分就会越低
-
length norm 字段长度越长,值越小 搜索的field的长度越长,给的相关度评分越低;field长度越短,给的相关度评分越高
举例说明
query match: hello world
假设三个文档:
| doc | hello | world |
|---|---|---|
| doc1 | hello es | hello es |
| doc2 | big world | big world |
| doc3 | hello world | hello world |
score(q,d) =
queryNorm(q)
· coord(q,d)
· ∑ (
tf(t in d)
· idf(t)2
· t.getBoost()
· norm(t,d)
) (t in q)
- score(q,d) query对一个doc最终的评分结果 这个公式的最终结果,就是一个query(叫做q),对一个doc(叫做d)的最终的总评分
- queryNorm(q) 在不影响相互关系的前提下,把看似离散的数据,转换到一个相近的区间 是用来让一个doc的分数处于一个合理的区间内,不要太离谱,举个例子,一个doc分数是10000,一个doc分数是0.1
- coord(q,d) 简单来说,就是对更加匹配的doc,进行一些分数上的成倍的奖励 对匹配的结果加分,越匹配的doc加分越多
- ∑:doc对query中每个trem的权重的总和
- tf(t in d) 计算每一个term对doc的分数的时候,就是TF/IDF算法
- idf(t) is the inverse document frequency for term t. 计算每一个term对doc的分数的时候,就是TF/IDF算法
- t.getBoost() 增加权重
- norm(t,d) 搜索出的field长度越长,给的相关度评分越低; field长度越短,给的相关度评分越高
高级查询
shard local idf和global idf
在es中检索某个field中是否包含关键字,会使用到TF/IDF算法来计算相关度分数,计算相关度分数主要从以下三点考虑
1、在一个doc中field中关键字出现的次数(越大相关度越高)
2、在所有doc中field中关键字出现的次数(次数越大相关度越低)
3、doc中field的长度
在计算相关度分数时,第二点很关键,因为es默认在一个shard中统计的,不是索引里所有的primary shard
基于此种默认情况,有可能出现在多shard下计算相关度分数不准确
解决办法:
1、生产环境下,数据量大,尽可能实现均匀分配,es在多个shard中均匀路由数据的,路由的时候根据_id,负载均衡
2、测试环境下,将索引的primary shard设置为1个,number_of_shards=1
3、搜索附带search_type=dfs_query_then_fetch参数,会将local IDF取出来计算global IDF
获取所有shard的local IDF计算结果,在本地进行global IDF分数的计算,会将所有shard的doc作为上下文来进行计算,也能确保准确性。但是生产环境下,不推荐这个参数,因为性能很差
multi_match多字段搜索
best_fields、most_fields和cross_fields策略
- best_fields:默认值,对于同一个query,单个field匹配更多的term,则优先排序。
- most_fields:如果一次请求中,对于同一个doc,匹配到某个term的field越多,则越优先排序。
- cross_fields: 所有术语都必须存在于至少一个字段中才能与文档匹配
GET /product/_search
{
"query": {
"multi_match": {
"query": "小米手机",
"fields": ["desc","name"]
}
}
}
GET /product/_search
{
"query": {
"multi_match": {
"type": "most_fields",
"query": "小米手机",
"fields": ["desc","name"]
}
}
}
等同 bool
GET /_search
{
"query": {
"multi_match" : {
"query": "Will Smith",
"type": "best_fields",
"fields": [ "first_name", "last_name" ],
"operator": "and"
}
}
}
(+first_name:will +first_name:smith) | (+last_name:will +last_name:smith)
dis_max 和 tie_breaker
dis_max只取某一个query最大的分数,完全不考虑其他query的分数,这种一刀切的做法,可能导致在有其他query的影响下,score不准确的情况,这时为了使用结果更准确,最好还是要考虑到其他query的影响
tie_breaker参数的意义,将其他query的分数乘以tie_breaker,然后综合考虑后与最高分数的那个query的分数综合在一起进行计算,这样做除了取最高分以外,还会考虑其他的query的分数。tie_breaker的值,设置在在0~1之间,是个小数就行,没有固定的值
最佳的精确值需要根据数据与查询调试得出,但是合理值应该与零接近(处于 0.1 - 0.4 之间),这样就不会颠覆 dis_max 最佳匹配性质的根本。
GET product/_search
{
"query": {
"dis_max": {
"queries": [
{"match": {"name": "超级快充"}},
{"match": {"desc": "超级快充"}}
],
"tie_breaker": 0.7
}
}
}
function score query
在使用 Elasticsearch 进行全文搜索时,搜索结果默认会以文档的相关度进行排序,如果想要改变默认的排序规则,也可以通过sort指定一个或多个排序字段。
但是使用sort排序过于绝对,它会直接忽略掉文档本身的相关度(根本不会去计算)。在很多时候这样做的效果并不好,这时候就需要对多个字段进行综合评估,得出一个最终的排序。
在 Elasticsearch 中function_score是用于处理文档分值的 DSL,它会在查询结束后对每一个匹配的文档进行一系列的重打分操作,最后以生成的最终分数进行排序。它提供了几种默认的计算分值的函数:
- weight:设置权重
- field_value_factor:将某个字段的值进行计算得出分数。
- factor:对字段值进行预处理,乘以指定的数值(默认为 1)
- modifier将字段值进行加工,有以下的几个选项:
- none:不处理
- log:计算对数
- log1p:先将字段值 +1,再计算对数
- log2p:先将字段值 +2,再计算对数
- ln:计算自然对数
- ln1p:先将字段值 +1,再计算自然对数
- ln2p:先将字段值 +2,再计算自然对数
- square:计算平方
- sqrt:计算平方根
- reciprocal:计算倒数
- random_score:随机得到 0 到 1 分数
- decay Function:衰减函数,同样以某个字段的值为标准,距离某个值越近得分越高
- script_score:通过自定义脚本计算分值(前几种方式已经解决了大部分问题,但有局限性,只能针对一个字段计算分值,(field_value_factor 一般只用于数字类型,而衰减函数一般只用于数字、位置和时间类型),script_score支持我们自己编写一个脚本运行,在该脚本中我们可以拿到当前文档的所有字段信息,并且只需要将计算的分数作为返回值传回 Elasticsearch 即可)
- boost_mode可以指定计算后的分数与原始的_score如何合并,有以下选项:
- multiply:将结果乘以_score 默认值
- sum:将结果加上_score
- min:取结果与_score的较小值
- max:取结果与_score的较大值
- replace:使结果替换掉_score
- max_boost:分数上限
# weight
GET product/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{
"filter": {
"term": {
"name": "手机"
}
},
"weight": 23
}
]
}
}
}
# field_value_factor
GET product/_search
{
"query": {
"function_score": {
"query": {
"match": {
"name": "手机"
}
},
"field_value_factor": {
"field": "collected_num",
"modifier": "none",
"factor": 1.2
}
}
}
}
# random_score
# 它有一个非常有用的特性是可以通过seed属性设置一个随机种子,该函数保证在随机种子相同时返回值也相同,这点使得它可以轻松地实现对于用户的个性化推荐。
GET product/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{
"random_score": {
"seed": 10,
"field": "_id"
}
}
]
}
}
}
# 原点(origin):该字段最理想的值,这个值可以得到满分(1.0)
# 偏移量(offset):与原点相差在偏移量之内的值也可以得到满分
# 衰减规模(scale):当值超出了原点到偏移量这段范围,它所得的分数就开始进行衰减了,衰减规模决定了这个分数衰减速度的快慢
# 衰减值(decay):该字段可以被接受的值(默认为 0.5),相当于一个分界点,具体的效果与衰减的模式有关
GET product/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"gauss": {
"createtime": {
"origin": "2020-05-20",
"scale": "10d",
"offset": "5d",
"decay": 0.5
}
}
}
}
}
# script_score
GET product/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"script_score": {
"script": {
"source": "Math.log(1 + doc['price'].value)"
}
}
}
}
}
Nested Search
当需要存储关系对象时 比如 一个帖子下有多个评论
主要解决对象关系存储
需要手动指定索引各字段的类型 并指定关系对象为nested类型
注意只能在新建索引时指定好,否则后续不能修改,需要通过删除索引 重建
PUT /order/_doc/1
{
"order_name": "小米10 Pro订单",
"desc": "shouji zhong de zhandouji",
"goods_count": 3,
"total_price": 12699,
"goods_list": [
{
"name": "小米10 PRO MAX 5G",
"price": 4999
},
{
"name": "钢化膜",
"price": 19
},
{
"name": "手机壳",
"price": 199
}
]
}
PUT /order/_doc/2
{
"order_name": "扫地机器人订单",
"desc": "shouji zhong de zhandouji",
"goods_count": 2,
"total_price": 12699,
"goods_list": [
{
"name": "小米扫地机器热儿",
"price": 1999
},
{
"name": "洗碗机",
"price": 4999
}
]
}
GET order/_search
GET order/_mapping
DELETE order
PUT order
{
"mappings": {
"properties": {
"desc": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"goods_count": {
"type": "long"
},
"goods_list": {
"type": "nested",
"properties": {
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {
"type": "long"
}
}
},
"order_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"total_price": {
"type": "long"
}
}
}
}
发现不设置mapping时,直接使用goods_list. 模式去查询时,得到的结果是不准确的,是因为内部对象被扁平化为一个简单的字段名称和值列表,如下
{
"goods_list.name": ["小米扫地机器热儿", "洗碗机"],
"goods_list.price": [4999, 1999]
}
重新设置mapping,重新插入数据,发现结果正常。 nested的主要作用是,嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着可以独立于其他对象查询每个嵌套对象。
# path:nested对象的查询深度
GET /order/_search
{
"query": {
"nested": {
"path": "goods_list",
"query": {
"bool": {
"must": [
{
"match": {
"goods_list.name": "小米10"
}
},
{
"match": {
"goods_list.price": 4999
}
}
]
}
}
}
}
}
# 删除demo
POST blog_new/blog/1/_update
{
"script": {
"lang": "painless",
"source": "ctx._source.comments.removeIf(it -> it.name == 'John');"
}
}
# 更新demo
POST blog_new/blog/2/_update
{
"script": {
"source": "for(e in ctx._source.comments){if (e.name == 'steve') {e.age = 25; e.comment= 'very very good article...';}}"
}
}
# 聚合demo
GET blog_new/_search
{
"size": 0,
"aggs": {
"comm_aggs": {
"nested": {
"path": "comments"
},
"aggs": {
"min_age": {
"min": {
"field": "comments.age"
}
}
}
}
}
}
- score_mode:聚合分数计算方式
- avg (默认):使用所有匹配的子对象的平均相关性得分。
- max:使用所有匹配的子对象中的最高相关性得分。
- min:使用所有匹配的子对象中最低的相关性得分。
- none:不要使用匹配的子对象的相关性分数。该查询为父文档分配得分为0。
- sum:将所有匹配的子对象的相关性得分相加。
父子查询(has_child has_parent parent_ID)
# branch:代表一个分公司
# employee:代表员工
# 关系: 一个公司可以包含多个员工
DELETE company
PUT /company
{
"mappings": {
"properties": {
"join-field": {
"type": "join",
"relations": {
"parent": "child"
}
}
}
}
}
GET /company/_mapping
POST /company/_doc/_bulk
{ "index": { "_id": "london" }}
{ "name": "London Westminster", "city": "london", "country": "UK", "join-field": "parent" }
{ "index": { "_id": "liverpool" }}
{ "name": "Liverpool Central", "city": "liverpool", "country": "UK", "join-field": "parent" }
{ "index": { "_id": "paris" }}
{ "name": "Champs Élysées", "city": "paris", "country": "France" , "join-field": "parent" }
PUT company/_doc/1?routing=london
{
"name": "Mark Thomas",
"dob": "1982-05-16",
"hobby": "diving",
"join-field": {
"name": "child",
"parent": "london"
}
}
PUT company/_doc/2?routing=london
{
"name": "Barry Smith",
"dob": "1979-04-01",
"hobby": "hiking",
"join-field": {
"name": "child",
"parent": "london"
}
}
PUT company/_doc/3?routing=paris
{
"name": "Adrien Grand",
"dob": "1987-05-11",
"hobby": "horses",
"join-field": {
"name": "child",
"parent": "paris"
}
}
# 返回某父文档的子文档
# 查询london下的员工
GET company/_search
{
"query": {
"parent_id":{
"type":"child",
"id":"london"
}
}
}
# 返回包含某子文档的父文档
GET /company/_search
{
"query": {
"has_child": {
"type": "child",
"query": {
"match_all": {}
}
}
}
}
# 返回包含某父文档的子文档
GET /company/_search
{
"query": {
"has_parent": {
"parent_type": "parent",
"query": {
"match_all": {}
}
}
}
}
Term Vectors
termvector会获取document中的某个field内的各个term的统计信息。
- term_vector设置接受:
- no 没有术语向量被存储。(默认)
- yes 仅存储该字段中的术语。
- with_positions 条款和职位已存储。
- with_offsets 存储术语和字符偏移量。
- with_positions_offsets 存储术语,位置和字符偏移量。
- with_positions_payloads 术语,位置和有效载荷已存储。
- with_positions_offsets_payloads 存储术语,位置,偏移量和有效载荷。
PUT my-index-000001
{
"mappings": {
"properties": {
"text": {
"type": "text",
"term_vector": "with_positions_offsets"
}
}
}
}
PUT my-index-000001/_doc/1
{
"text": "Quick brown fox"
}
GET /my-index-000001/_termvectors/1
高亮语法
- plain highlight:使用standard Lucene highlighter,对简单的查询支持度非常好。
- unified highlight:默认的高亮语法,使用Lucene Unified Highlighter,将文本切分成句子,并对句子使用BM25计算词条的score,支持精准查询和模糊查询。
- fast vector highlighter:使用Lucene Fast Vector highlighter,功能很强大,如果在mapping中对field开启了term_vector,并设置了with_positions_offsets,就会使用该highlighter,对内容特别长的文本(大于1MB)有性能上的优势。
GET my-index-000001/_search
{
"query": {
"match": {
"text": "brown fox"
}
},
"highlight": {
"pre_tags": [
"<em class=\"c_color\">"
],
"post_tags": [
"</em>"
],
"fields": {
"text": { "type": "plain"}
}
}
}
一般情况下,用plain highlight也就足够了,不需要做其他额外的设置。
如果对高亮的性能要求很高,可以尝试启用unified highlight。
如果field的值特别大,超过了1M,那么可以用fast vector highlight。
suggest
自动补全或者纠错。 通过协助用户输入更精准的关键词,提高后续全文搜索阶段文档匹配的程度
PUT suggest_carinfo
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"suggest": {
"type": "completion",
"analyzer": "ik_max_word"
}
}
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
POST _bulk
{"index":{"_index":"suggest_carinfo","_id":1}}
{"title":"宝马X5 两万公里准新车","content":"这里是宝马X5图文描述"}
{"index":{"_index":"suggest_carinfo","_id":2}}
{"title":"宝马5系","content":"这里是奥迪A6图文描述"}
{"index":{"_index":"suggest_carinfo","_id":3}}
{"title":"宝马3系","content":"这里是奔驰图文描述"}
{"index":{"_index":"suggest_carinfo","_id":4}}
{"title":"奥迪Q5 两万公里准新车","content":"这里是宝马X5图文描述"}
{"index":{"_index":"suggest_carinfo","_id":5}}
{"title":"奥迪A6 无敌车况","content":"这里是奥迪A6图文描述"}
{"index":{"_index":"suggest_carinfo","_id":6}}
{"title":"奥迪双钻","content":"这里是奔驰图文描述"}
{"index":{"_index":"suggest_carinfo","_id":7}}
{"title":"奔驰AMG 两万公里准新车","content":"这里是宝马X5图文描述"}
{"index":{"_index":"suggest_carinfo","_id":8}}
{"title":"奔驰大G 无敌车况","content":"这里是奥迪A6图文描述"}
{"index":{"_index":"suggest_carinfo","_id":9}}
{"title":"奔驰C260","content":"这里是奔驰图文描述"}
GET suggest_carinfo/_search?pretty
{
"suggest": {
"car_suggest" : {
"prefix" : "奔",
"completion" : {
"field" : "title.suggest"
}
}
}
}
POST suggest_carinfo/_search
{
"suggest": {
"car_suggest": {
"prefix": "宝马5系",
"completion": {
"field": "title.suggest",
"skip_duplicates":true,
"fuzzy": {
"fuzziness": 2
}
}
}
}
}
- term suggester:根据词项的词频来推荐.重要参数:
- text:用户搜索的文本
- field:要从哪个字段选取推荐数据
- analyzer:使用哪种分词器
- size:每个建议返回的最大结果数
- sort:如何按照提示词项排序,参数值只可以是以下两个枚举:
- score:分数>词频>词项本身
- frequency:词频>分数>词项本身
- suggest_mode:搜索推荐的推荐模式,参数值亦是枚举:
- missing:默认值,仅匹配不在索引中的词项
- popular:仅推荐比原始推荐词项文档词频(doc count)更高的相似词项
- always:根据 建议文本中的词项 推荐 任何匹配的建议词
- max_edits:可以具有最大偏移距离候选建议以便被认为是建议。只能是1到2之间的值。任何其他值都将导致引发错误的请求错误。默认为2
- prefix_length:前缀匹配的时候,必须满足的最少字符
- min_word_length:最少包含的单词数量
- min_doc_freq:最少的文档频率
- phrase suggester:phrase suggester和term suggester相比,对建议的文本会参考上下文,也就是一个句子的其他token,不只是单纯的token距离匹配,它可以基于共生和频率选出更好的建议。
- direct_generator:phrase suggester使用候选生成器生成给定文本中每个项可能的项的列表。单个候选生成器类似于为文本中的每个单独的调用term suggester。生成器的输出随后与建议候选项中的候选项结合打分。目前只支持一种候选生成器,即direct_generator。建议API接受密钥直接生成器下的生成器列表;列表中的每个生成器都按原始文本中的每个项调用。
- highlight:高亮标签
- pre_tag:起始标签,如
- post_tag:闭合标签,如
- completion suggester:自动补全,自动完成,支持三种查询【前缀查询(prefix)/模糊查询(fuzzy)/正则表达式查询(regex)】
- Completion:es的一种特有类型,专门为suggest提供,基于内存,性能很高。
- prefix query:基于前缀查询的搜索提示,是最常用的一种搜索推荐查询。
- prefix:客户端搜索词
- field:建议词字段
- size:需要返回的建议词数量
- skip_duplicates:是否过滤掉重复建议,默认false
- fuzzy query
- fuzziness:允许的偏移量,默认auto
- transpositions:如果设置为true,则换位计为一次更改而不是两次更改,默认为true。
- min_length:返回模糊建议之前的最小输入长度,默认 3
- prefix_length:输入的最小长度(不检查模糊替代项)默认为 1
- unicode_aware:如果为true,则所有度量(如模糊编辑距离,换位和长度)均以Unicode代码点而不是以字节为单位。这比原始字节略慢,因此默认情况下将其设置为false。
- regex query:可以用正则表示前缀,不建议使用
- context suggester:完成建议者会考虑索引中的所有文档,但是通常希望提供由某些条件过滤和/或增强的建议。
前缀搜索、通配符搜索、正则搜索 fuzzy
前缀搜索
以xx开头的搜索,不计算相关度评分,和filter比,没有bitcache。前缀搜索,尽量把前缀长度设置的更长,性能差。
GET index/_search
{
"query": {
"prefix": {
"title": {
"value": "text"
}
}
}
}
通配符搜索
通配符运算符是匹配一个或多个字符的占位符。例如,*通配符运算符匹配零个或多个字符。您可以将通配符运算符与其他字符结合使用以创建通配符模式
GET /noble_affix/_search
{
"query": {
"wildcard": {
"createName": {
"value": "*珍"
}
}
}
}
正则 regexp查询的性能可以根据提供的正则表达式而有所不同。为了提高性能,应避免使用通配符模式,如.或 .?+未经前缀或后缀
Fuzzy模糊查询
混淆字符 (box → fox) 缺少字符 (black → lack)
多出字符 (sic → sick) 颠倒次序 (act → cat)
参数:
①value:(必需,字符串)
②fuzziness:(可选,字符串)最大误差 并非越大越好, 召回率高 但是结果不准确
③max_expansions:可选,整数)匹配的最大词项数量。默认为50。
④prefix_length:创建扩展时保留不变的开始字符数。默认为0
⑤transpositions:(可选,布尔值)指示编辑是否包括两个相邻字符的变位(ab→ba)。默认为true。
⑥rewrite:(可选,字符串)用于重写查询的方法
GET /_search
{
"query": {
"fuzzy": {
"user": {
"value": "keyword"
}
}
}
}
ElasticSearch完整目录
1. Elasticsearch是什么
2.Elasticsearch基础使用
3.Elasticsearch Mapping
4.Elasticsearch 集群原理
5.Elasticsearch Scripts和读写原理
6.Elasticsearch 分词器
7.Elasticsearch TF-IDF算法及高级查询
8.Elasticsearch 地理位置及搜索
9.Elasticsearch ELK