前言

有了前面的理论知识和上机实操的经验,那么下面我们将使用程序开发es。当然本篇说白了就是前面知识的总结和回顾。

一 ES不分词(exact value)搜索

1.1  实战体验term filter各种不分词搜索

term filter/query:对搜索文本不分词,它直接拿条件去倒排索引中匹配。

例如:term :“hello world” --> “hello world”,直接去倒排索引中匹配“hello world”。

反过来如果对搜索文本分词的话。则“helle world” --> “hello”和“world”,两个词分别去倒排索引中匹配。

term filter/query::根据exact value搜索,数字、boolean、date天然支持

1.1.1 初始化数据

POST /forum/_bulk
{ "index": { "_id": 1 }}
{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }
{ "index": { "_id": 2 }}
{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }
{ "index": { "_id": 3 }}
{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }
{ "index": { "_id": 4 }}
{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }

1.1.2 数字不分词搜索测试

查询ueserId=1的数据,结果应该是两个

GET /forum/_search
{"query": {"constant_score": {"filter": {"term": {"userID": 1}}}}
}

1.1.3 boolean不分词搜索测试

搜索没有隐藏的帖子hidden=false ,结果应该是3条

GET /forum/_search
{"query": {"constant_score": {"filter": {"term": {"hidden": false}}}}
}

1.1.4 Date不分词搜索测试

搜索发帖日期 postDate=2017-01-01 的帖子,结果应该是2条

GET /forum/_search
{"query": {"constant_score": {"filter": {"term": {"postDate": "2017-01-01"}}}}
}

1.1.5 Text 利用keyword不分词搜索

我们如果直接搜索 articleID=XHDK-A-1293-#fJ3 的帖子,看上去结果应该有1条,其实是没有的。因为 用搜索文本(XHDK-A-1293-#fJ3) 去匹配 分词 xhdk,a,1293,fj3,肯定匹配不到。

GET /forum/_search
{"query": {"constant_score": {"filter": {"term": {"articleID": "XHDK-A-1293-#fJ3"}}}}
}

我们可以查看下分词

GET /forum/_analyze
{
"field": "articleID",
"text": "XHDK-A-1293-#fJ3"
}

articleID 默认是analyzed的text类型的field,建立倒排索引的时候,就会对所有的articleID分词,分词以后,原本的articleID就没有了,只有分词后的各个word存在于倒排索引中,即 XHDK-A-1293-#fJ3 --> xhdk,a,1293,fj3。

term 是不对搜索文本分词的(即 XHDK-A-1293-#fJ3 --> XHDK-A-1293-#fJ3);但是articleID建立索引是分词的(即XHDK-A-1293-#fJ3 --> xhdk,a,1293,fj3),因此他们不能匹配上。

keyword 不分词搜索

articleID.keyword,是es最新版本内置建立的field,就是不分词的。所以一个articleID过来的时候,会建立两次索引,一次是自己本身,是要分词的,分词后放入倒排索引;另外一次是基于articleID.keyword,不分词,保留256个字符最多,直接一个字符串放入倒排索引中。

GET /forum/_search
{"query": {"constant_score": {"filter": {"term": {"articleID.keyword": "XHDK-A-1293-#fJ3"}}}}
}

所以term filter,对text过滤,可以考虑使用内置的field.keyword来进行匹配。但是有个问题,默认就保留256个字符。所以尽可能还是自己去手动建立索引。

1.1.6 Text 自定义不分词搜索

自定义mapping

DELETE /forum
PUT /forum
{"mappings": {"properties": {"articleID": {"type": "keyword"}}}
}
POST /forum/_bulk
{"index":{"_id":1}}
{"articleID":"XHDK-A-1293-#fJ3","userID":1,"hidden":false,"postDate":"2017-01-01"}
{"index":{"_id":2}}
{"articleID":"KDKE-B-9947-#kL5","userID":1,"hidden":false,"postDate":"2017-01-02"}
{"index":{"_id":3}}
{"articleID":"JODL-X-1937-#pV7","userID":2,"hidden":false,"postDate":"2017-01-01"}
{"index":{"_id":4}}
{"articleID":"QQPX-R-3956-#aD8","userID":2,"hidden":true,"postDate":"2017-01-02"}

articleID已经设置为不分词(即XHDK-A-1293-#fJ3 --> XHDK-A-1293-#fJ3),且 term 是不对搜索文本分词的(即 XHDK-A-1293-#fJ3 --> XHDK-A-1293-#fJ3)因此他们不能匹配上。

GET /forum/_search
{"query": {"constant_score": {"filter": {"term": {"articleID": "XHDK-A-1293-#fJ3"}}}}

1.2 原理 bitset & caching

假设 一个倒排索引

word

doc1

doc2

doc3

2017-01-01

*

*

2017-02-02

*

*

2017-03-03

*

*

*

  1. filter :2017-02-02条件 去获取匹配的doc list  doc2,doc3
  2. 构建一个bitset,就是一个二进制的数组,数组每个元素都是0或1,用来标识一个doc对一个filter条件是否匹配,如果匹配就是1,不匹配就是0,即 [0, 1, 1]。(为啥用 0,1二进制表示,而不是用 true false来的更直观)因为对计算机而言 二进制语言 是最基本的机器语言,因此节省了一堆转化,提升内存空间和性能,因此我们以后设计功能的时候往往优先考虑最简单的数据结构。
  3. 遍历每个过滤条件对应的bitset,优先从最稀疏(匹配比较少的,即1最少的)的开始搜索。先遍历比较稀疏的bitset,就可以先过滤掉尽可能多的数据。
  1. 例如:请求:filter,postDate=2017-01-01,userID=1

假设bitset 1 是:postDate: [0, 0, 1, 1, 0, 0]

假设bitset 2   是:userID:    [0, 1, 0, 1, 0, 1]

则 返回第四个结果,即doc4 给client。

  1. caching bitset,跟踪query,在最近256个query中超过一定次数的过滤条件,缓存其bitset。对于小segment(<1000,或<3%),不缓存bitset。

例如postDate=2017-01-01,[0, 0, 1, 1, 0, 0],可以缓存在内存中,这样下次如果再有这个条件过来的时候,就不用重新扫描倒排索引,反复生成bitset,可以大幅度提升性能。

在最近的256个filter中,有某个filter超过了一定的次数(次数不固定),就会自动缓存这个filter对应的bitset

segment,filter针对小segment获取到的结果,可以不缓存,segment记录数<1000,或者segment大小<index总大小的3%

segment数据量很小,此时哪怕是扫描也很快;segment会在后台自动合并,小segment很快就会跟其他小segment合并成大segment,此时就缓存也没有什么意义,segment很快就消失了

针对一个小segment的bitset,[0, 0, 1, 0]

filter比query的好处就在于会caching,但是之前不知道caching的是什么东西,实际上并不是一个filter返回的完整的doc list数据结果。而是filter bitset缓存起来。下次不用扫描倒排索引了。

  1. filter大部分情况下来说,在query之前执行,先尽量过滤掉尽可能多的数据
  1. query:是会计算doc对搜索条件的relevance score,还会根据这个score去排序
  2. filter:只是简单过滤出想要的数据,不计算relevance score,也不排序
  1. 如果document有新增或修改,那么cached bitset会被自动更新
  1. 假设:postDate=2017-01-01,[0, 0, 1, 0]
  2. 新增一个 document,id=5,postDate=2017-01-01,则bitset缓存会自动更新。在末尾处新增匹配结果即为:[0, 0, 1, 0, 1]
  3. 修改第一个 document,原(id=1,postDate=2016-12-30)修改为postDate-2017-01-01,此时也会自动更新bitset,[1, 0, 1, 0, 1]
  1. 以后只要是有相同的filter条件的,会直接来使用这个过滤条件对应的 cached bitset

1.3 实战 bool组合多个filter search

1.3.1 多个条件 或 且

搜索发帖日期为2017-01-01,或者帖子ID为XHDK-A-1293-#fJ3的帖子,同时要求帖子的发帖日期绝对不为2017-01-02

sql语法为:

where (post_date='2017-01-01' or article_id='XHDK-A-1293-#fJ3') and post_date!='2017-01-02'

es 语法为:

GET /forum/_search
{"query": {"constant_score": {"filter": {"bool": {"should": [{"term": {"postDate": "2017-01-01"}},{"term": {"articleID": "XHDK-A-1293-#fJ3"}}],"must_not": {"term": {"postDate": "2017-01-02"}}}}}}
}
  1. must   必须匹配
  2. should   可以匹配其中任意一个即可
  3. must_not   必须不匹配

1.3.2 多个条件 或 且

搜索帖子ID为XHDK-A-1293-#fJ3,或者是帖子ID为JODL-X-1937-#pV7而且发帖日期为2017-01-01的帖子

sql语法为:

where article_id='XHDK-A-1293-#fJ3' or (article_id='JODL-X-1937-#pV7' and post_date='2017-01-01')

es 语法为:

注意 bool must should  must_not是可以嵌套的

GET /forum/_search
{"query": {"constant_score": {"filter": {"bool": {"should": [{"term": {"articleID": "XHDK-A-1293-#fJ3"}},{"bool": {"must": [{"term": {"articleID": "JODL-X-1937-#pV7"}},{"term": {"postDate": "2017-01-01"}}]}}]}}}}
}

1.4 实战 多值搜索

我们前面都是单个值搜索 即,term: {"field": "value"},多值搜索 即terms: {"field": ["value1","value2"]}

sql语法为:

col in ("value1", "value2")

1.4.1 初始化数据

我们还是沿用前面的数据,这里插入些测试数据:为帖子数据增加tag字段

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"tag":["java","hadoop"]}}
{"update":{"_id":"2"}}
{"doc":{"tag":["java"]}}
{"update":{"_id":"3"}}
{"doc":{"tag":["hadoop"]}}
{"update":{"_id":"4"}}
{"doc":{"tag":["java","elasticsearch"]}}

1.4.2 多值搜索不限定匹配值的个数

搜索articleID为KDKE-B-9947-#kL5或QQPX-R-3956-#aD8的帖子,

GET /forum/_search
{"query": {"constant_score": {"filter": {"terms": {"articleID": ["KDKE-B-9947-#kL5","QQPX-R-3956-#aD8"]}}}}
}

搜索tag中包含java的帖子

GET /forum/_search
{"query" : {"constant_score" : {"filter" : {"terms" : {  "tag" : ["java"]}}}}
}

1.4.3 多值搜索优化限定匹配值的个数

新增字段  tag_cnt  表示几个 tag标签 ,如果仅仅是java 则是 tag_cnt=1

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"tag_cnt":2}}
{"update":{"_id":"2"}}
{"doc":{"tag_cnt":1}}
{"update":{"_id":"3"}}
{"doc":{"tag_cnt":1}}
{"update":{"_id":"4"}}
{"doc":{"tag_cnt":2}}GET /forum/_search
{"query": {"constant_score": {"filter": {"bool": {"must": [{"term": {"tag_cnt": 1}},{"terms": {"tag": ["java"]}}]}}}}
}

1.5 实战 范围过滤搜索

1.5.1 准备数据

为帖子数据增加浏览量的字段 view_cnt  表示帖子浏览次数

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"view_cnt":30}}
{"update":{"_id":"2"}}
{"doc":{"view_cnt":50}}
{"update":{"_id":"3"}}
{"doc":{"view_cnt":100}}
{"update":{"_id":"4"}}
{"doc":{"view_cnt":80}}

1.5.2 搜索浏览量在30~60之间的帖子

gte 大于等于   gt 大于

lte  小于等于   lt  小于

GET /forum/_search
{"query": {"constant_score": {"filter": {"range": {"view_cnt": {"gt": 30,"lt": 60}}}}}
}

1.5.3 搜索发帖日期在最近1个月的帖子

准备数据

POST /forum/_bulk
{"index":{"_id":5}}
{"articleID":"DHJK-B-1395-#Ky5","userID":3,"hidden":false,"postDate":"2017-03-01","tag":["elasticsearch"],"tag_cnt":1,"view_cnt":10}

查询语法

now||-30d  即当前时间的前30天

GET /forum/_search
{"query": {"constant_score": {"filter": {"range": {"postDate": {"gt": "2017-03-10||-30d"}}}}}
}

二 ES 全文检索(分词检索)

2.1 实战 控制全文检索的精准度

2.1.1 准备数据

为帖子增加标题字段

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"title":"this is java and elasticsearch blog"}}
{"update":{"_id":"2"}}
{"doc":{"title":"this is java blog"}}
{"update":{"_id":"3"}}
{"doc":{"title":"this is elasticsearch blog"}}
{"update":{"_id":"4"}}
{"doc":{"title":"this is java, elasticsearch, hadoop blog"}}
{"update":{"_id":"5"}}
{"doc":{"title":"this is spark blog"}}

2.1.2 match query 不控制精度

match query 是负责进行全文检索的。当然如果要检索的field是not_analyzed类型的,那么match query也相当于term query。

GET /forum/_search
{"query": {"match": {"title": "java elasticsearch"}}
}

结果是包含 java、elasticsearch、java&elasticsearch 的三种情况都有的

2.1.3 match query 控制精度(operator=and)

如果你是希望所有的搜索关键字java elasticsearch都要匹配的,那么就用and,可以实现单纯match query无法实现的效果

GET /forum/_search
{"query": {"match": {"title": {"query": "java elasticsearch","operator": "and"}}}
}

2.1.4 match query 控制精度(minimum_should_match=xxx)

minimum_should_match 指定一些关键字中,必须至少匹配其中的多少个关键字,才能作为结果返回

GET /forum/_search
{"query": {"match": {"title": {"query": "java elasticsearch spark hadoop","minimum_should_match": "75%"}}}
}

2.1.5 match query 控制精度(bool多个条件)

GET /forum/_search
{"query": {"bool": {"must": {"match": {"title": "java"}},"must_not": {"match": {"title": "spark"}},"should": [{"match": {"title": "hadoop"}},{"match": {"title": "elasticsearch"}}]}}
}

2.1.6 match query 控制精度(bool多个条件+minimum_should_match)

默认情况下,should是可以不匹配任何一个的,比如上面的搜索中,this is java blog,就不匹配任何一个should条件,但是有个例外的情况,如果没有must的话,那么should中必须至少匹配一个才可以

比如下面的搜索,should中有4个条件,默认情况下,只要满足其中一个条件,就可以匹配作为结果返回

但是可以精准控制,should的4个条件中,至少匹配几个才能作为结果返回

GET /forum/_search
{"query": {"bool": {"should": [{"match": {"title": "java"}},{"match": {"title": "elasticsearch"}},{"match": {"title": "hadoop"}},{"match": {"title": "spark"}}],"minimum_should_match": 3}}
}

2.1.7 了解多个搜索条件如何计算relevance score

  1. must是确保说,谁必须有这个关键字,同时会根据这个must的条件去计算出document对这个搜索条件的relevance score
  2. should是可以影响相关度分数的,在满足must的基础之上,should中的条件,不匹配也可以,但是如果匹配的更多,那么document的relevance score就会更高

2.2 了解 match query如何转换为term+should

2.2.1  match query 如何转化 term+should

//from
{"match": { "title": "java elasticsearch"}
}
//to
{"bool": {"should": [{ "term": { "title": "java" }},{ "term": { "title": "elasticsearch"   }}]}
}

2.2.2 and match如何转换为term+must

//from
{"match": {"title": {"query":    "java elasticsearch","operator": "and"}}
}
//to
{"bool": {"must": [{ "term": { "title": "java" }},{ "term": { "title": "elasticsearch"   }}]}
}

2.2.3 minimum_should_match如何转换

//from
{"match": {"title": {"query":                "java elasticsearch hadoop spark","minimum_should_match": "75%"}}
}
//to
{"bool": {"should": [{ "term": { "title": "java" }},{ "term": { "title": "elasticsearch"   }},{ "term": { "title": "hadoop" }},{ "term": { "title": "spark" }}],"minimum_should_match": 3  }
}

三 ES搜索relevance score处理

3.1 实战 boost(权重)影响 relevance score

搜索条件的权重,boost,可以将某个搜索条件的权重加大,此时当匹配这个搜索条件和匹配另一个搜索条件的document,计算relevance score时,匹配权重更大的搜索条件的document,relevance score会更高,当然也就会优先被返回回来。

默认情况下,搜索条件的权重都是一样的,都是1

需求:搜索标题中必须包含blog的帖子,然后标题中至少包含一个或0个: java || hadoop || elasticsearch || spark,并且spark优先。

GET /forum/_search
{"query": {"bool": {"must": [{"match": {"title": "blog"}}],"should": [{"match": {"title": {"query": "java"}}},{"match": {"title": {"query": "hadoop"}}},{"match": {"title": {"query": "elasticsearch"}}},{"match": {"title": {"query": "spark","boost": 5}}}]}}
}

3.2 原理 多shard场景下relevance score不准确问题

如果你的一个index有多个shard的话,可能搜索结果会不准确。

3.2.1 造成relevance score不准确问题原因

因为搜索 会打到多个shard中  比如 节点1 包含java的doc有10个  ,那么10个doc的相关度就会拉低(数量多被平均了),节点2 包含java的doc有1个,那么他的相关度分数就很高,反而排在前面。

3.2.2 如何解决relevance score不准确问题

  1. 生产环境下,数据量大,尽可能实现均匀分配(概率学数据越大,越均匀)
  2. 测试环境下,将索引的primary shard设置为1个,number_of_shards=1,index settings 如果说只有一个shard,那么当然,所有的document都在这个shard里面,就没有这个问题了
  3. 测试环境下,搜索附带search_type=dfs_query_then_fetch参数,会将local IDF取出来计算global IDF

计算一个doc的相关度分数的时候,就会将所有shard对的local IDF计算一下,获取出来,在本地进行global IDF分数的计算,会将所有shard的doc作为上下文来进行计算,也能确保准确性。但是production生产环境下,不推荐这个参数,因为性能很差。

3.3 实战 多字段搜索 影响 relevance score

3.3.1 准备实验数据

为帖子数据增加content字段

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"content":"i like to write best elasticsearch article"}}
{"update":{"_id":"2"}}
{"doc":{"content":"i think java is the best programming language"}}
{"update":{"_id":"3"}}
{"doc":{"content":"i am only an elasticsearch beginner"}}
{"update":{"_id":"4"}}
{"doc":{"content":"elasticsearch and hadoop are all very good solution, i am a beginner"}}
{"update":{"_id":"5"}}
{"doc":{"content":"spark is best big data solution based on scala ,an programming language similar to java"}}

3.3.2 multi-field搜索默认relevance score

搜索title或content中包含java或solution的帖子

GET /forum/_search
{"query": {"bool": {"should": [{"match": {"title": "java solution"}},{"match": {"content": "java solution"}}]}}
}

id 2  排在前面因为 tile 包含java  content 包含java ,

id 5 排在2后面  只有content 包含 java  solution。

很容易得知符合多个条件(field)的doc排在前面,而符合某一个条件field,就算符合度比较高(即条件的值都符合),也只能排在后面。因为relevance score 的计算方式决定的。

relevance score 的计算方式:

假设: 两个条件都匹配,一个条件不匹配,

匹配1:是x字段 score 假设 2分

匹配2:  是y字段 score 假设 2分

不匹配1:z字段,0分

则得知: matched query count 是 2 (匹配两个条件)

query count 是3(一个三个条件)

relevance score=(2[x的分数]+2[y的分数])* 2[matched query count]/3[query count]=2.666666

按照这个公式 (doc2=(2+2)*2/2=4 )> (doc5=(2)*2/2=2) ,当然排在前面。这种策略也无可厚非。

但是我们往往不关心条件的匹配总数量,而是关心某一个条件最高的匹配度,比如doc5 ,虽然两个条件只匹配上了conent,但是他匹配conent的两个值。所以希望doc5 排在前面。因此es提供了best fields策略,即优先某一个field中匹配到了尽可能多的关键词。

3.3.3 best fields(dis_max)影响relevance score

best fields策略 它是指某一个field中匹配到了尽可能多的关键词,排在前面;而不是尽可能多的field匹配到了少数的关键词,排在了前面。因此他只关心  某一个field 匹配的值越多越靠前。

GET /forum/_search
{"query": {"dis_max": {"queries": [{ "match": { "title": "java solution" }},{ "match": { "content":  "java solution" }}]}}
}

3.3.4 tie_breake(系数)relevance score

搜索title或content中包含java beginner的帖子

GET /forum/_search
{"query": {"dis_max": {"queries": [{"match": {"title": "java beginner"}},{"match": {"content": "java beginner"}}]}}
}

dis_max,只是取分数最高的那个query的分数而已,完全不考虑其他query的分数,使用tie_breaker将其他query的分数也考虑进去。

tie_breaker参数的意义,在于说,将其他query的分数,乘以tie_breaker,然后综合与最高分数的那个query的分数,综合在一起进行计算。

tie_breaker的值,在0~1之间。

GET /forum/_search
{"query": {"dis_max": {"queries": [{"match": {"title": "java beginner"}},{"match": {"content": "java beginner"}}],"tie_breaker": 0.3}}
}

3.3.5 multi_match(best_fields+boost)影响relevance score

minimum_should_match 去长尾 ,控制搜索结果的精准度,只有匹配一定数量的关键词的数据,才能返回。

xxx^2 即权重2倍

GET /forum/_search
{"query": {"multi_match": {"query": "java solution","type": "best_fields","fields": ["title^2","content"],"tie_breaker": 0.3,"minimum_should_match": "50%"}}
}

等同于下面的方式

GET /forum/_search
{"query": {"dis_max": {"queries": [{"match": {"title": {"query": "java beginner","minimum_should_match": "50%","boost": 2}}},{"match": {"content": {"query": "java beginner","minimum_should_match": "30%"}}}],"tie_breaker": 0.3}}
}

3.3.6 multi_match(most fiels)影响relevance score

best-fields策略,主要是说将某一个field匹配尽可能多的关键词的doc优先返回回来

most-fields策略,主要是说尽可能返回更多field匹配到某个关键词的doc,优先返回回来

设置mapping

POST /forum/_mapping
{"properties": {"sub_title": {  "type":     "text","analyzer": "english","fields": {"std":   {  "type":     "text","analyzer": "standard"}}}}
}

新增字段

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"sub_title":"learning more courses"}}
{"update":{"_id":"2"}}
{"doc":{"sub_title":"learned a lot of course"}}
{"update":{"_id":"3"}}
{"doc":{"sub_title":"we have a lot of fun"}}
{"update":{"_id":"4"}}
{"doc":{"sub_title":"both of them are good"}}
{"update":{"_id":"5"}}
{"doc":{"sub_title":"haha, hello world"}}

查询测试

GET /forum/_search
{"query": {"match": {"sub_title": "learned course"}}
}

doc1  ( learning more courses)

doc2 ( learned a lot of course)

分数一样,那么为啥不应该是 doc2在最前面,最高。

sub_title用的是enligsh analyzer,所以还原了单词,就会将单词还原为其最基本的形态

learning --> learn

learned --> learn

courses --> course

sub_titile: learned course --> learn course

因此他两就是一样了,没法排序。

但是我们前面新增了不处理时态的分词器。

GET /forum/_search
{"query": {"multi_match": {"query":  "learned mores  courses","type":   "most_fields",  "fields": [ "sub_title", "sub_title.std" ]}}
}

很显然  doc2 最匹配排在最前面

doc1  ( learning more courses)

sub_title  匹配数:3

sub_title.std 匹配数: 0

doc2 ( learned a lot of course)

sub_title  匹配数:2

sub_title.std 匹配数: 1

如果是best_fields 那么doc1 排在前面,以为他关心匹配数最大的field  ,field的匹配数越大越靠前

如果是most_fields 那么doc2 排在前面,他关心匹配上的field的数量,即field匹配的数量越多越靠前

四 ES cross-fields搜索

4.1 实战体验 most_fields+cross-fields

cross-fields搜索,一个唯一标识,跨了多个field。比如一个人,标识,是姓名;一个建筑,它的标识是地址。姓名可以散落在多个field中,比如first_name和last_name中,地址可以散落在country,province,city中。

跨多个field搜索一个标识,比如搜索一个人名,或者一个地址,就是cross-fields搜索

初步来说,如果要实现,可能用most_fields比较合适。因为best_fields是优先搜索单个field最匹配的结果。

4.1.1 初始化数据

POST /forum/_bulk
{"update":{"_id":"1"}}
{"doc":{"author_first_name":"Peter","author_last_name":"Smith"}}
{"update":{"_id":"2"}}
{"doc":{"author_first_name":"Smith","author_last_name":"Williams"}}
{"update":{"_id":"3"}}
{"doc":{"author_first_name":"Jack","author_last_name":"Ma"}}
{"update":{"_id":"4"}}
{"doc":{"author_first_name":"Robbin","author_last_name":"Li"}}
{"update":{"_id":"5"}}
{"doc":{"author_first_name":"Tonny","author_last_name":"Peter Smith"}}

4.1.2 执行most_fields+cross-fields搜索

GET /forum/_search
{"query": {"multi_match": {"query": "Peter Smith","type": "most_fields","fields": ["author_first_name","author_last_name"]}}
}

问题1:只是找到尽可能多的field匹配的doc,而不是某个field完全匹配的doc

问题2:most_fields,没办法用minimum_should_match去掉长尾数据,就是匹配的特别少的结果

问题3:TF/IDF算法,比如Peter Smith和Smith Williams,搜索Peter Smith的时候,由于first_name中很少有Smith的,所以query在所有document中的频率很低,得到的分数很高,可能Smith Williams反而会排在Peter Smith前面

4.2 copy_to+cross-fields搜索

用copy_to,将多个field组合成一个field。

PUT /forum/_mapping/
{"properties": {"new_author_first_name": {"type": "text","copy_to": "new_author_full_name"},"new_author_last_name": {"type": "text","copy_to": "new_author_full_name"},"new_author_full_name": {"type": "text"}}
}

用了这个copy_to语法之后,就可以将多个字段的值拷贝到一个字段中,并建立倒排索引

PUT /forum/_mapping/
{"properties": {"new_author_first_name": {"type": "text","copy_to": "new_author_full_name"},"new_author_last_name": {"type": "text","copy_to": "new_author_full_name"},"new_author_full_name": {"type": "text"}}
}GET /forum/_search
{"query": {"match": {"new_author_full_name":       "Peter Smith"}}
}

4.3 原生cross-fiels搜索

GET /forum/_search
{"query": {"multi_match": {"query": "Peter Smith","type": "cross_fields","operator": "and","fields": ["author_first_name","author_last_name"]}}
}

五 ES搜索匹配

5.1 近似匹配基本概念

什么是近似匹配。

举个例子:

java is my favourite programming language, and I also think spark is a very good big data system.

java spark are very related, because scala is spark's programming language and scala is also based on jvm like java.

match query,搜索java spark

{

"match": {

"content": "java spark"

}

}

match query,只能搜索到包含java和spark的document,但是不知道java和spark是不是离的很近

如果说,要实现两个需求:

1、java spark,就靠在一起,中间不能插入任何其他字符,就要搜索出来这种doc

2、java spark,但是要求,java和spark两个单词靠的越近,doc的分数越高,排名越靠前

要实现上述两个需求,用match做全文检索,是搞不定的,必须得用proximity match,近似匹配

phrase match:短语匹配

proximity match:近似匹配

5.2 phrase matching 短语匹配

插入一个短语

POST /forum/_update/5
{"doc": {"content": "spark is best big data solution based on scala ,an programming language similar to java spark"}
}

match_phrase语法

GET /forum/_search
{"query": {"match_phrase": {"content": "java spark"}}
}

虽然是短语匹配,他其实还是去分词的,只不过,他首先过滤到 必须包含 两个分词的doc,然后比较java的position 与spark的position,必须是spark的position减去java的position等于1 ,即spark在java的后面。

5.3 phrase matching 近似匹配slop

slop  的值表示的是最多移动几次能匹配上。

GET /forum/_search
{"query": {"match_phrase": {"title": {"query": "java blog","slop": 2}}}
}

结果是 : doc2  [ this is java blog]  doc1 [this is java and elasticsearch blog] doc4 [this is java, elasticsearch, hadoop blog]

所以   doc2  移动0次 就可以匹配 ,doc1 移动两次 就能匹配  doc4 移动两次就能匹配

并且 移动距离越近,排名越靠前

5.4(match+match_phrase)query实现召回率与精准度的平衡

召回率(recall)

比如你搜索一个java spark,总共有100个doc,能返回多少个doc作为结果,就是召回率,recall

精准度(precision)

比如你搜索一个java blog,能不能尽可能让包含java blog,或者是java和blog离的很近的doc,排在最前面,precision

近似匹配的时候,召回率比较低,精准度太高了。

就是优先满足召回率,即,java blog,包含java的也返回,包含blog的也返回,包含java和blog的也返回;同时兼顾精准度,就是包含java和blog,同时java和blog离的越近的doc排在最前面

此时可以用bool组合match query和match_phrase query一起,来实现上述效果

GET /forum/_search
{"query": {"bool": {"must": {"match": {"title": {"query": "java blog"}}},"should": {"match_phrase": {"title": {"query": "java blog","slop": 50}}}}}
}

第一个匹配就是全文检索,第二个匹配就是利用相似匹配提供精准度。即java  blog  排在了前面。

5.5 rescoring(重打分) 近似匹配机制

match和phrase match(proximity match)区别是:

match --> 只要简单的匹配到了一个term,就可以理解将term对应的doc作为结果返回。

phrase match --> 首先扫描到所有term的doc list; 找到包含所有term的doc list; 然后对每个doc都计算每个term的position,是否符合指定的范围; slop,需要进行复杂的运算,来判断能否通过slop移动,匹配一个doc

match query的性能比phrase match和proximity match(有slop)要高很多。因为后两者都要计算position的距离。

match query比phrase match的性能要高10倍,比proximity match的性能要高20倍。

但是别太担心,因为es的性能一般都在毫秒级别,match query一般就在几毫秒,或者几十毫秒,而phrase match和proximity match的性能在几十毫秒到几百毫秒之间,所以也是可以接受的。

优化proximity match的性能,一般就是减少要进行proximity match搜索的document数量。主要思路就是,用match query先过滤出需要的数据,然后再用proximity match来根据term距离提高doc的分数,同时proximity match只针对每个shard的分数排名前n个doc起作用,来重新调整它们的分数,这个过程称之为rescoring,重计分。因为一般用户会分页查询,只会看到前几页的数据,所以不需要对所有结果进行proximity match操作。

用我们刚才的说法,match + proximity match同时实现召回率和精准度

默认情况下,match也许匹配了1000个doc,proximity match全都需要对每个doc进行一遍运算,判断能否slop移动匹配上,然后去贡献自己的分数

但是很多情况下,match出来也许1000个doc,其实用户大部分情况下是分页查询的,所以可能最多只会看前几页,比如一页是10条,最多也许就看5页,就是50条

proximity match只要对前50个doc进行slop移动去匹配,去贡献自己的分数即可,不需要对全部1000个doc都去进行计算和贡献分数

rescore:重打分

match:1000个doc,其实这时候每个doc都有一个分数了; proximity match,前50个doc,进行rescore,重打分,即可; 让前50个doc,term举例越近的,排在越前面

GET /forum/_search
{"query": {"match": {"content": "java spark"}},"rescore": {"window_size": 50,"query": {"rescore_query": {"match_phrase": {"content": {"query": "java spark","slop": 50}}}}}
}

5.6  前缀 通配符 正则 匹配搜索

5.6.1 前缀搜索

假设三个字段 C3D0-KD345 、C3K5-DFG65、C4I8-UI365

则 C3 --> 上面这两个都搜索出来 --> 根据字符串的前缀去搜索

准备测试数据

PUT hzm_index
{"mappings": {"properties": {"title": {"type": "keyword"}}}
}POST /hzm_index/_bulk
{"index":{"_id":1}}
{"title":"C3D0-KD345"}
{"index":{"_id":2}}
{"title":"C3K5-DFG65"}
{"index":{"_id":3}}
{"title":"C4I8-UI365"}

前缀搜索测试

GET hzm_index/_search
{"query": {"prefix": {"title": {"value": "C3"}}}
}

前缀搜索的原理

prefix query不计算relevance score,与prefix filter唯一的区别就是,filter会cache bitset

扫描整个倒排索引,前缀越短,要处理的doc越多,性能越差,尽可能用长前缀搜索。

假设字符串

C3-D0-KD345

C3-K5-DFG65

C4-I8-UI365

word

doc1

doc2

doc3

c3

*

*

d0

*

kd345

*

k5

*

dfg65

*

c4

*

i8

*

ui365

*

match 全文搜索  c3 --> 扫描倒排索引 --> 一旦扫描到c3,就可以停了,因为带c3的就2个doc,已经找到了 没有必要继续去搜索其他的term了,因此match性能往往是很高的。

不分词

c3 --> 先扫描到了C3-D0-KD345,找到了一个前缀带c3的字符串 --> 还是要继续搜索的,因为后面还有一个C3-K5-DFG65,也许还有其他很多的前缀带c3的字符串 ,必须继续搜索直到扫描完整个的倒排索引,才能结束,因此 prefix性能很差。

5.6.2 通配符搜索

他跟前缀搜索类似,功能更加强大

C3D0-KD345

C3K5-DFG65

C4I8-UI365

5字符-D任意个字符55?-*5:通配符去表达更加复杂的模糊搜索的语义

GET hzm_index/_search
{"query": {"wildcard": {"title": {"value": "C?K*5"}}}
}

?:任意字符

*:0个或任意多个字符

性能一样差,必须扫描整个倒排索引,才ok

5.6.3 正则搜索

C[0-9].+

GET hzm_index/_search
{"query": {"regexp": {"title": "C[0-9].+"}}
}

[0-9]:指定范围内的数字

[a-z]:指定范围内的字母

.:一个字符

+:前面的正则表达式可以出现一次或多次

wildcard和regexp,与prefix原理一致,都会扫描整个索引,性能很差。在实际应用中,能不用尽量别用。性能太差了。

六 ES 搜索推荐&fuzzy

6.1 match_phrase_prefix实现search-time搜索推荐

搜索推荐,search as you type,搜索提示

GET forum/_search
{"query": {"match_phrase_prefix": {"title": "java b"}}
}

原理跟match_phrase类似,唯一的区别,就是把最后一个term作为前缀去搜索

java 就是去进行match,搜索对应的doc。b,会作为前缀,去扫描整个倒排索引,找到所有b开头的doc

然后找到所有doc中,即包含java,又包含b开头的字符的doc。

也可以根据你的slop去计算,看在slop范围内,能不能让java  b,正好跟doc中的java和b开头的单词的position相匹配

也可以指定slop,但是只有最后一个term会作为前缀

max_expansions:指定prefix最多匹配多少个term,超过这个数量就不继续匹配了,限定性能

默认情况下,前缀要扫描所有的倒排索引中的term,去查找b打头的单词,但是这样性能太差。可以用max_expansions限定,b前缀最多匹配多少个term,就不再继续搜索倒排索引了。

尽量不要用,因为,最后一个前缀始终要去扫描大量的索引,性能可能会很差

6.2 原理 ngram和index-time搜索推荐

ngram 其实就是单词拆分的步长。例如:

quick,5种长度下的ngram

ngram length=1,q u i c k

ngram length=2,qu ui ic ck

ngram length=3,qui uic ick

ngram length=4,quic uick

ngram length=5,quick

什么是edge ngram,首字母后进行ngram。例如

q

qu

qui

quic

quick

切分单词后 保存在倒排索引中。

搜索的时候,不用再根据一个前缀,然后扫描整个倒排索引了; 简单的拿前缀去倒排索引中匹配即可,如果匹配上了,那么就好了; match,全文检索

6.2.1 准备数据

DELETE /hzm_index
PUT /hzm_index
{"settings": {"analysis": {"filter": {"autocomplete_filter": {"type":     "edge_ngram","min_gram": 1,"max_gram": 20}},"analyzer": {"autocomplete": {"type":      "custom","tokenizer": "standard","filter": ["lowercase","autocomplete_filter"]}}}}
}PUT /hzm_index/_mapping
{"properties": {"title": {"type":     "text","analyzer": "autocomplete","search_analyzer": "standard"}}
}POST /hzm_index/_bulk
{"index":{"_id":1}}
{"title":"hello world"}
{"index":{"_id":2}}
{"title":"hello win"}
{"index":{"_id":3}}
{"title":"hello dog"}GET /hzm_index/_search
{"query": {"match_phrase": {"title": "hello w"}}
}

如果用match,只有hello的也会出来,全文检索,只是分数比较低

推荐使用match_phrase,要求每个term都有,而且position刚好靠着1位,符合我们的期望的

6.3 实战错误拼写时的fuzzy模糊搜索技术

搜索的时候,可能输入的搜索文本会出现误拼写的情况

doc1: hello world

doc2: hello java

搜索:hallo world

fuzzy搜索技术 --> 自动将拼写错误的搜索文本,进行纠正,纠正以后去尝试匹配索引中的数据

POST /hzm_index/_bulk
{"index":{"_id":1}}
{"text":"Surprise me!"}
{"index":{"_id":2}}
{"text":"That was surprising."}
{"index":{"_id":3}}
{"text":"I wasn't surprised."}GET /hzm_index/_search
{"query": {"fuzzy": {"text": {"value": "surprize","fuzziness": 2}}}
}

surprize --> 拼写错误 --> surprise --> s -> z

surprize --> surprise -> z -> s,纠正一个字母,就可以匹配上,所以在fuziness指定的2范围内

surprize --> surprised -> z -> s,末尾加个d,纠正了2次,也可以匹配上,在fuziness指定的2范围内

surprize --> surprising -> z -> s,去掉e,ing,3次,总共要5次,才可以匹配上,始终纠正不了

fuzzy搜索以后,会自动尝试将你的搜索文本进行纠错,然后去跟文本进行匹配

fuzziness,你的搜索文本最多可以纠正几个字母去跟你的数据进行匹配,默认如果不设置,就是2

GET /hzm_index/_search
{"query": {"fuzzy": {"text": {"value": "surprize","fuzziness": 2}}}
}

七 ES 搜索内部原理

7.1 原理TF&IDF算法以及向量空间模型算法

1.boolean model 类似and这种逻辑操作符,先过滤出包含指定term的doc

query "hello world" --> 过滤 --> hello / world / hello & world

bool --> must/must not/should --> 过滤 --> 包含 / 不包含 / 可能包含

doc --> 不打分数 --> 正或反 true or false --> 为了减少后续要计算的doc的数量,提升性能

2.TF/IDF 单个term在doc中的分数

假设:

query: hello world --> doc.content

doc1: java is my favourite programming language, hello world !!!

doc2: hello java, you are very good, oh hello world!!!

则:

a. TF: term frequency

  1. 找到hello在doc1中出现了几次,这里出现1次,会根据出现的次数给个分数,
  2. 一个term在一个doc中,出现的次数越多,那么最后给的相关度评分就会越高

b. IDF:inversed document frequency

找到hello在所有的doc中出现的次数,这里3次一个term在所有的doc中,出现的次数越多,那么最后给        的相关度评分就会越低

c. length norm

hello搜索的那个field的长度,field长度越长,给的相关度评分越低; field长度越短,给的相关度评越高

最后,会将hello这个term,对doc1的分数,综合TF,IDF,length norm,计算出来一个综合性的分数

hello world --> doc1 --> hello对doc1的分数,world对doc1的分数 --> 但是最后hello world query要对doc1有一个总的分数 --> vector space model

7.1.1 vector space model

多个term对一个doc的总分数

hello world --> es会根据hello world在所有doc中的评分情况,计算出一个query vector,query向量

假设:query  'java  world '  , query vector=[2,5]

doc1 一个包含1个term:hello  其doc vector=[2,0]、

doc2 一个包含1个term: world 其doc vector=[0,5]、

doc3一个包含2个term :hello world 其doc vector=[2,5]

hello这个term,给的基于所有doc的一个评分就是2,world这个term,给的基于所有doc的一个评分就是5会给每一个doc,拿每个term计算出一个分数来,hello有一个分数,world有一个分数,再拿所有term的分数组成一个doc vector

每个doc vector计算出对query vector的弧度,最后基于这个弧度给出一个doc相对于query中多个term的总分数,弧度越大,分数越低; 弧度越小,分数越高

7.2 原理 lucene相关度分数算法

深入讲解TF/IDF算法,在lucene中,底层 Lucene 提供一个 practical scoring function,来计算一个query对一个doc的分数的公式,该函数会使用一个公式来计算。

score(q,d)  =  queryNorm(q)  //queryNorm = 1/√sumOfSquaredWeights//sumOfSquaredWeights = 所有term的IDF分数之和,开一个平方根,然后做一个平方根分之1//主要是为了将分数进行规范化 --> 开平方根,首先数据就变小了 --> 然后还用1去除以这个平方根,分数就会很小 --> 1.几 / 零点几//分数就不会出现几万,几十万,那样的离谱的分数//是用来让一个doc的分数处于一个合理的区间内,不要太离谱,举个例子,一个doc分数是10000,一个doc分数是0.1· coord(q,d)    //简单来说,匹配更多value的doc,进行一些分数上的成倍的奖励//成倍的奖励分=(匹配每个值的分数相加)*匹配值的个数/query value的个数· ∑ (          tf(t in d)   //计算每一个term对doc的分数的时候,就是TF/IDF算法· idf(t)2      //计算每一个term对doc的分数的时候,就是TF/IDF算法· t.getBoost()  //权重越大 分数变大· norm(t,d)    //field length 越长,分数越小) (t in q) //query中每个term对doc的分数,进行求和,多个term对一个doc的分数,组成一个vector space,然后计算吗,就在这一步

这个公式的最终结果,就是说是一个query(叫做q),对一个doc(叫做d)的最终的总评分

7.3实战 相关度分数优化方法

对相关度评分进行调节和优化的常见的4种方法

7.3.1 query-time boost

score(q,d)  =  queryNorm(q)  //queryNorm = 1/√sumOfSquaredWeights//sumOfSquaredWeights = 所有term的IDF分数之和,开一个平方根,然后做一个平方根分之1//主要是为了将分数进行规范化 --> 开平方根,首先数据就变小了 --> 然后还用1去除以这个平方根,分数就会很小 --> 1.几 / 零点几//分数就不会出现几万,几十万,那样的离谱的分数//是用来让一个doc的分数处于一个合理的区间内,不要太离谱,举个例子,一个doc分数是10000,一个doc分数是0.1· coord(q,d)    //简单来说,匹配更多value的doc,进行一些分数上的成倍的奖励//成倍的奖励分=(匹配每个值的分数相加)*匹配值的个数/query value的个数· ∑ (          tf(t in d)   //计算每一个term对doc的分数的时候,就是TF/IDF算法· idf(t)2      //计算每一个term对doc的分数的时候,就是TF/IDF算法· t.getBoost()  //权重越大 分数变大· norm(t,d)    //field length 越长,分数越小) (t in q) //query中每个term对doc的分数,进行求和,多个term对一个doc的分数,组成一个vector space,然后计算吗,就在这一步

7.3.2 重构查询结构

重构查询结果,在es新版本中,影响越来越小了。一般情况下,没什么必要的话,大家不用也行。

GET /forum/_search
{"query": {"bool": {"should": [{"match": {"content": "java"}},{"match": {"content": "spark"}},{"bool": {"should": [{"match": {"content": "solution"}},{"match": {"content": "beginner"}}]}}]}}
}

7.3.3 negative boost

搜索包含java,不包含spark的doc,但是这样子很死板

GET /forum/_search
{"query": {"bool": {"must": [{"match": {"content": "java"}}],"must_not": [{"match": {"content": "spark"}}]}}
}

搜索包含java,尽量不包含spark的doc,如果包含了spark,不会说排除掉这个doc,而是说将这个doc的分数降低,即包含了negative term的doc,分数乘以negative boost,分数降低

GET /forum/_search
{"query": {"boosting": {"positive": {"match": {"content": "java"}},"negative": {"match": {"content": "spark"}},"negative_boost": 0.2}}
}

7.3.4 constant_score

如果你压根儿不需要相关度评分,直接走constant_score加filter,所有的doc分数都是1,没有评分的概念了

GET /forum/_search
{"query": {"bool": {"should": [{"constant_score": {"filter": {"match": {"title": "java"}}}},{"constant_score": {"filter": {"match": {"title": "spark"}}}}]}}
}

7.4 实战用function_score自定义相关度分数算法

我们可以做到自定义一个function_score函数,自己将某个field的值,跟es内置算出来的分数进行运算,然后由自己指定的field来进行分数的增强

7.4.1 准备数据

给所有的帖子数据增加follower数量

POST /forum/_bulk
{"update":{"_id":"1"}}
{ "doc" : {"follower_num" : 5} }
{ "update": { "_id": "2"} }
{ "doc" : {"follower_num" : 10} }
{ "update": { "_id": "3"} }
{ "doc" : {"follower_num" : 25} }
{ "update": { "_id": "4"} }
{ "doc" : {"follower_num" : 3} }
{ "update": { "_id": "5"} }
{ "doc" : {"follower_num" : 60} }

将对帖子搜索得到的分数,跟follower_num进行运算,由follower_num在一定程度上增强帖子的分数

看帖子的人越多,那么帖子的分数就越高

GET /forum/_search
{"query": {"function_score": {"query": {"multi_match": {"query": "java spark","fields": ["tile","content"]}},"field_value_factor": {"field": "follower_num","modifier": "log1p","factor": 0.5},"boost_mode": "sum","max_boost": 2}}
}
  1. 如果只有field,那么会将每个doc的分数都乘以follower_num,如果有的doc follower是0,那么分数就会变为0,效果很不好。
  2. 因此一般会加个log1p函数,公式会变为,new_score = old_score * log(1 + number_of_votes),这样出来的分数会比较合理
  3. 再加个factor,可以进一步影响分数,new_score = old_score * log(1 + factor * number_of_votes)
  4. boost_mode,可以决定分数与指定字段的值如何计算,multiply(默认相乘),sum,min,max,replace
  5. max_boost,限制计算出来的分数不要超过max_boost指定的值

八 IK 分词器

8.1 docker 安装IK分词器

docker exec -it a515f73f2115 /bin/bashelasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.7.0/elasticsearch-analysis-ik-7.7.0.zip

重启es

docker ps -a | grep elasticsearch | awk '{print $1 }' | xargs docker  container  restart

8.2 物理机安装IK分词

  1. git clone https://github.com/medcl/elasticsearch-analysis-ik
  2. git checkout tags/v7.7.0
  3. mvn package
  4. 将target/releases/elasticsearch-analysis-ik-7.7.0.zip拷贝到es/plugins/ik目录下
  5. 在es/plugins/ik下对elasticsearch-analysis-ik-7.7.0.zip进行解压缩
  6. 重启es

8.3 IK 基础使用

ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;

ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

所以一般选择ik_max_word

8.3.1 准备数据

DELETE hzm_index
PUT /hzm_index
{"mappings": {"properties": {"text": {"type": "text","analyzer": "ik_max_word"}}}
}POST /hzm_index/_bulk
{"index":{"_id":"1"}}
{"text":"男子偷上万元发红包求交女友 被抓获时仍然单身"}
{"index":{"_id":"2"}}
{"text":"16岁少女为结婚“变”22岁 7年后想离婚被法院拒绝"}
{"index":{"_id":"3"}}
{"text":"深圳女孩骑车逆行撞奔驰 遭索赔被吓哭(图)"}
{"index":{"_id":"4"}}
{"text":"女人对护肤品比对男票好?网友神怼"}
{"index":{"_id":"5"}}
{"text":"为什么国内的街道招牌用的都是红黄配?"}

8.3.2 搜索测试

查看分词分析

GET /hzm_index/_analyze
{"text": "男子偷上万元发红包求交女友 被抓获时仍然单身","analyzer": "ik_max_word"
}

搜索测试

GET /hzm_index/_search
{"query": {"match": {"text": "16岁少女结婚好还是单身好?"}}
}

8.4 实战 IK 配置文件 & 自定义词库

8.4.1 配置文件了解

  1. ik配置文件地址:es/plugins/ik/config目录
  2. IKAnalyzer.cfg.xml:用来配置自定义词库
  3. main.dic:ik原生内置的中文词库,总共有27万多条,只要是这些单词,都会被分在一起
  4. quantifier.dic:放了一些单位相关的词,比如长度 重量
  5. suffix.dic:放了一些后缀
  6. surname.dic:中国的姓氏
  7. stopword.dic:英文停用词
  8. ik原生最重要的两个配置文件
  9. main.dic:包含了原生的中文词语,会按照这个里面的词语去分词
  10. stopword.dic:包含了英文的停用词
  11. stopword 停用词,比如a the and at but 一般,像停用词,会在分词的时候,直接被干掉,不会建立在倒排索引中

8.4.2 自定义词库

每年都会涌现一些特殊的流行词,网红,蓝瘦香菇,喊麦,鬼畜,一般不会在ik的原生词典里,需要自己补充自己的最新的词语,到ik的词库里面去。

自己建立词库 IKAnalyzer.cfg.xml  的属性ext_dict里配置路径,custom/mydict.dic

补充自己的词语,然后需要重启es,才能生效

自己建立停用词库:比如了,的,啥,么,我们可能并不想去建立索引,让人家搜索

custom/ext_stopword.dic,已经有了常用的中文停用词,可以补充自己的停用词,然后重启es

8.5  Ik mysql 热更新分词(未完成后面调整)

热更新 es不停机,直接我们在外部某个地方添加新的词语,es中立即热加载到这些新词语。

手动添加新词语:

  1. 每次添加完,都要重启es才能生效,非常麻烦
  2. es是分布式的,可能有数百个节点,不能每次都一个一个节点上面去修改

热更新的方案

  1. 修改ik分词器源码,然后手动支持从mysql中每隔一定时间,自动加载新的词库
  2. 基于ik分词器原生支持的热更新方案,部署一个web服务器,提供一个http接口,通过modified和tag两个http响应头,来提供词语的热更新(ik git社区官方都不建议采用,不稳定)
  3. 下载源码

https://github.com/medcl/elasticsearch-analysis-ik/tree/v7.7.0

  1. 修改源码

Dictionary类,169行:Dictionary单例类的初始化方法,在这里需要创建一个我们自定义的线程,并且启动   它

HotDictReloadThread类:就是死循环,不断调用Dictionary.getSingleton().reLoadMainDict(),去重新加载   词典

Dictionary类,389行:this.loadMySQLExtDict();

Dictionary类,683行:this.loadMySQLStopwordDict();

  1. mvn package打包代码

target\releases\elasticsearch-analysis-ik-7.7.0.zip

  1. 解压缩ik压缩包

将mysql驱动jar,放入ik的目录下

  1. 修改jdbc相关配置
  2. 重启es

观察日志,日志中就会显示我们打印的那些东西,比如加载了什么配置,加载了什么词语,什么停用词

  1. 在mysql中添加词库与停用词
  2. 分词实验,验证热更新生效

ElasticSearch 7.7.0 高级篇-搜索技术相关推荐

  1. Elasticsearch(十)【NEST高级客户端--搜索查询】

    搜索 Search API允许您执行搜索查询并获取与查询匹配的搜索匹配. Elasticsearch的搜索功能可能是您使用它的原因之一,NEST公开了所有可用的不同类型的搜索,以及一些聪明的使用Ela ...

  2. 谷粒商城-分布式高级篇[商城业务-检索服务]

    谷粒商城-分布式基础篇[环境准备] 谷粒商城-分布式基础[业务编写] 谷粒商城-分布式高级篇[业务编写]持续更新 谷粒商城-分布式高级篇-ElasticSearch 谷粒商城-分布式高级篇-分布式锁与 ...

  3. 谷粒商城-分布式高级篇【业务编写】

    谷粒商城-分布式基础篇[环境准备] 谷粒商城-分布式基础[业务编写] 谷粒商城-分布式高级篇[业务编写]持续更新 谷粒商城-分布式高级篇-ElasticSearch 谷粒商城-分布式高级篇-分布式锁与 ...

  4. 谷粒商城-分布式高级篇[商城业务-秒杀服务]

    谷粒商城-分布式基础篇[环境准备] 谷粒商城-分布式基础[业务编写] 谷粒商城-分布式高级篇[业务编写]持续更新 谷粒商城-分布式高级篇-ElasticSearch 谷粒商城-分布式高级篇-分布式锁与 ...

  5. 【博客4】缤果LabView串口调试助手V1.0 (初级篇)

    目录 超级好用的LabView串口调试助手! 目录 一.软件概要: 二.软件界面: 三.串口功能实现: 3.1 串口初始化 3.2 串口事件处理 3.2.1 打开串口 3.2.2 关闭串口 3.2.3 ...

  6. Solr vs ElasticSearch,搜索技术哪家强

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群"获取公众号专属群聊入口 Solr和ElasticSearch到底有一些什么不同? ...

  7. 一起谈.NET技术,DataTable 深入解析数据源绑定原理之高级篇

    前言 在上篇写了篇 实战系列之天气预报实时采集 ,有个别同志认为没技术含量,也许正如所说. 只是人各有看法,当我写出一篇文章时,我只是希望: 1:如果你还不懂,请看写法,了解想法. 2:如果你已懂,略 ...

  8. 【SpringBoot高级篇】SpringBoot集成Elasticsearch搜索引擎

    [SpringBoot高级篇]SpringBoot集成Elasticsearch搜索引擎 1. 什么是Elasticsearch? 2. 安装并运行Elasticsearch 2.1 拉取镜像 2.2 ...

  9. ElasticSearch高级篇

    注:该文档是网上资源,该文档通俗易懂,我已经按照文档学习完了,后期我会加入自己的实践内容. 0.学习目标 独立安装Elasticsearch 会使用Rest的API操作索引 会使用Rest的API查询 ...

  10. 学习笔记:SpringCloud 微服务技术栈_高级篇⑤_可靠消息服务

    若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 前言 学习视频链接 SpringCloud + RabbitMQ + Docker + Redis + 搜 ...

最新文章

  1. python 输入学生成绩 若成绩在90、流程图_Python习题选编
  2. SPOJ 1811. POJ 2774 . 最大公共子串
  3. why CRMFSH01 failed to return any value for my case
  4. 老司机整理对Nginx性能优化
  5. 【Java】利用递归求阶乘
  6. 关联映射 一对多 实验心得_使用影响映射来帮助您的团队进行实验
  7. 打开wmware没反应_白酒打开后能存放多久?
  8. Flink-org.apache.flink.streaming.api.windowing.windows.Window
  9. xcode工程间的引用,iOS静态库
  10. js tostring 16 java_js中toString()和String()区别详解
  11. 在Java 8中,有没有一种简洁的方法可以迭代带有索引的流?
  12. 2019.3.9日面试自我介绍
  13. GBDT(Gradient Boosting Decision Tree)
  14. vb.net使用hook技术之键盘鼠标钩子
  15. spring源码解析百度网盘下载
  16. 七大行星排列图片_八大行星排列顺序(八大行星排列顺序图)
  17. c语言编程题数的平方和,c语言问题:任意输入两个数,求两数的平方之和? , 求一个c语言问题,任意输入两个数,求出这两个数之间的所有水...
  18. 宝塔php安全模式,windows server 2016关闭IE增强安全模式方法
  19. 不租服务器,自建个人商业网站(1)
  20. 10.MySQL文件

热门文章

  1. python创建一个列表、用于存储同学姓名_python学习日记04,Python
  2. shell脚本练习题(编程题)。
  3. 跨时区存储跨时区展示时间 | js 获取当前UTC时间
  4. C++读写tif文件
  5. InsecureProgramming-master——abo3
  6. Oracle LiveLabs实验:Application Continuity Fundamentals
  7. 第26讲—项目6—存款利息计算器
  8. openstack中虚拟机CPU与内存布局设计(三)
  9. Linux操作系统Maven【The JAVA_HOME environment variable is not defined correctly】
  10. Android 系统 Bar 沉浸式完美兼容方案