Elasticsearch7學(xué)習(xí)筆記(中)

Elasticsearch7學(xué)習(xí)筆記(上)
Elasticsearch7學(xué)習(xí)筆記(中)
Elasticsearch7學(xué)習(xí)筆記(下)
Elasticsearch7學(xué)習(xí)筆記(實(shí)戰(zhàn))

七、內(nèi)核原理

7.1 倒排索引組成結(jié)構(gòu)以及其索引不可變?cè)?/h3>

倒排索引,是適合用于進(jìn)行搜索的

倒排索引的結(jié)構(gòu):

  1. 包含這個(gè)關(guān)鍵詞的document list
  2. 包含這個(gè)關(guān)鍵詞的所有document的數(shù)量:IDF(inverse document frequency)
  3. 這個(gè)關(guān)鍵詞在每個(gè)document中出現(xiàn)的次數(shù):TF(term frequency)
  4. 這個(gè)關(guān)鍵詞在這個(gè)document中的次序
  5. 每個(gè)document的長(zhǎng)度:length norm
  6. 包含這個(gè)關(guān)鍵詞的所有document的平均長(zhǎng)度
word        doc1        doc2

dog          *           *
hello        *
you                      *

倒排索引不可變的好處

  1. 不需要鎖,提升并發(fā)能力,避免鎖的問題
  2. 數(shù)據(jù)不變,一直保存在os cache中,只要cache內(nèi)存足夠
  3. filter cache一直駐留在內(nèi)存,因?yàn)閿?shù)據(jù)不變
  4. 可以壓縮,節(jié)省cpu和io開銷

倒排索引不可變的壞處:每次都要重新構(gòu)建整個(gè)索引

7.2 圖解剖析document寫入原理(buffer,segment,commit)

基本流程

  1. 數(shù)據(jù)寫入buffer
  2. commit point
  3. buffer中的數(shù)據(jù)寫入新的index segment
  4. 等待在os cache中的index segment被fsync強(qiáng)制刷到磁盤上
  5. 新的index sgement被打開,供search使用
  6. buffer被清空

每次commit point時(shí),會(huì)有一個(gè).del文件,標(biāo)記了哪些segment中的哪些document被標(biāo)記為deleted;
搜索的時(shí)候,會(huì)依次查詢所有的segment,從舊的到新的,比如被修改過的document,在舊的segment中,會(huì)標(biāo)記為deleted,在新的segment中會(huì)有其新的數(shù)據(jù)

優(yōu)化后的流程

在基礎(chǔ)流程中通常寫入磁盤是比較耗時(shí),因此無法實(shí)現(xiàn)NTR近實(shí)時(shí)的查詢。主要瓶頸在于fsync實(shí)際發(fā)生磁盤IO寫數(shù)據(jù)進(jìn)磁盤,是很耗時(shí)的。

寫入流程別改進(jìn)如下:

(1)數(shù)據(jù)寫入buffer
(2)每隔一定時(shí)間,buffer中的數(shù)據(jù)被寫入segment文件,但是先寫入os cache
(3)只要segment寫入os cache,那就直接打開供search使用,不立即執(zhí)行commit

數(shù)據(jù)寫入os cache,并被打開供搜索的過程,叫做refresh,默認(rèn)是每隔1秒refresh一次。
也就是說,每隔一秒就會(huì)將buffer中的數(shù)據(jù)寫入一個(gè)新的index segment file,先寫入os cache中。
所以,es是近實(shí)時(shí)的,數(shù)據(jù)寫入到可以被搜索,默認(rèn)是1秒。

POST /index_demo/_refresh,可以手動(dòng)refresh,一般不需要手動(dòng)執(zhí)行,沒必要,讓es自己搞就可以了

比如現(xiàn)在的時(shí)效性要求,比較低,只要求一條數(shù)據(jù)寫入es,一分鐘以后才讓我們搜索到就可以了,那么就可以調(diào)整refresh interval

PUT /index_demo
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

最終優(yōu)化流程

  1. 數(shù)據(jù)寫入buffer緩沖和translog日志文件
  2. 每隔一秒鐘,buffer中的數(shù)據(jù)被寫入新的segment file,并進(jìn)入os cache,此時(shí)segment被打開并供search使用
  3. buffer被清空
  4. 重復(fù)1~3,新的segment不斷添加,buffer不斷被清空,而translog中的數(shù)據(jù)不斷累加
  5. 當(dāng)translog長(zhǎng)度達(dá)到一定程度的時(shí)候,commit操作發(fā)生

5-1. buffer中的所有數(shù)據(jù)寫入一個(gè)新的segment,并寫入os cache,打開供使用
5-2. buffer被清空
5-3. 一個(gè)commit ponit被寫入磁盤,標(biāo)明了所有的index segment
5-4. filesystem cache中的所有index segment file緩存數(shù)據(jù),被fsync強(qiáng)行刷到磁盤上
5-5. 現(xiàn)有的translog被清空,創(chuàng)建一個(gè)新的translog

基于translog和commit point,如何進(jìn)行數(shù)據(jù)恢復(fù)

fsync+清空translog,就是flush,默認(rèn)每隔30分鐘flush一次,或者當(dāng)translog過大的時(shí)候,也會(huì)flush

POST /index_demo/_flush,一般來說別手動(dòng)flush,讓它自動(dòng)執(zhí)行就可以了

translog,每隔5秒被fsync一次到磁盤上。在一次增刪改操作之后,當(dāng)fsync在primary shard和replica shard都成功之后,那次增刪改操作才會(huì)成功

但是這種在一次增刪改時(shí)強(qiáng)行fsync translog可能會(huì)導(dǎo)致部分操作比較耗時(shí),也可以允許部分?jǐn)?shù)據(jù)丟失,設(shè)置異步fsync translog

PUT /index_demo/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}

最后優(yōu)化寫入流程實(shí)現(xiàn)海量磁盤文件合并(segment merge,optimize)

每秒一個(gè)segment file,文件過多,而且每次search都要搜索所有的segment,很耗時(shí)

默認(rèn)會(huì)在后臺(tái)執(zhí)行segment merge操作,在merge的時(shí)候,被標(biāo)記為deleted的document也會(huì)被徹底物理刪除

每次merge操作的執(zhí)行流程

  1. 選擇一些有相似大小的segment,merge成一個(gè)大的segment
  2. 將新的segment flush到磁盤上去
  3. 寫一個(gè)新的commit point,包括了新的segment,并且排除舊的那些segment
  4. 將新的segment打開供搜索
  5. 將舊的segment刪除

POST /index_demo/_optimize?max_num_segments=1,盡量不要手動(dòng)執(zhí)行,讓它自動(dòng)默認(rèn)執(zhí)行就可以了

八、Java API初步使用

CRUD

老版本(下面的方法都是過期的,在es8開始將會(huì)被移除)

引入maven依賴:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>7.8.1</version>
</dependency>

添加日志依賴(可選):

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.13.3</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.13.3</version>
</dependency>

代碼測(cè)試

public static void main(String[] args) throws Exception {

    // 構(gòu)建client
    Settings settings = Settings.builder()
            .put("cluster.name", "docker-cluster")
            .build();
    TransportClient client = new PreBuiltTransportClient(settings)
            .addTransportAddress(new TransportAddress(InetAddress.getByName("192.168.111.40"), 9300));

    //addDoc(client);
    //getDoc(client);
    //updateDoc(client);
    delDoc(client);
    
    client.close();
}

/**
 * 添加
 */
public static void addDoc(TransportClient client) throws IOException {
    IndexResponse response = client.prepareIndex("employee", "_doc", "1")
            .setSource(XContentFactory.jsonBuilder()
                    .startObject()
                    .field("user", "tom")
                    .field("age", 18)
                    .field("position", "scientist")
                    .field("country", "China")
                    .field("join_data", "2020-01-01")
                    .field("salary", 10000)
                    .endObject())
            .get();
    System.out.println(response.getResult());
}

/**
 * 查詢
 */
public static void getDoc(TransportClient client){
    GetResponse documentFields = client.prepareGet("employee", "_doc", "1").get();
    System.out.println(documentFields.getSourceAsString());
}

/**
 * 更新
 */
public static void updateDoc(TransportClient client) throws IOException {
    UpdateResponse response = client.prepareUpdate("employee", "_doc", "1")
            .setDoc(XContentFactory.jsonBuilder()
                    .startObject()
                    .field("salary", 1000000)
                    .endObject())
            .get();
    System.out.println(response.getResult());
}

/**
 * 刪除
 */
public static void delDoc(TransportClient client){
    DeleteResponse response = client.prepareDelete("employee", "_doc", "1").get();
    System.out.println(response);
}

/***
 * 查詢職位中包含scientist,并且年齡在28到40歲之間
 */
public static void search(TransportClient client){
    SearchResponse response = client.prepareSearch("employee")
            .setQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("position", "scientist"))
                    .filter(QueryBuilders.rangeQuery("age").gte(28).lte(40))).setFrom(0).setSize(2).get();
    System.out.println(response);
}

/***
 * 聚合查詢(需要重建mapping)
 */
public static void search2(TransportClient client){
    SearchResponse response = client.prepareSearch("employee")
            .addAggregation(AggregationBuilders.terms("group_by_country")
                    .field("country")
                    .subAggregation(AggregationBuilders.dateHistogram("group_by_join_date")
                            .field("joinDate")
                            .dateHistogramInterval(DateHistogramInterval.YEAR)
                            .subAggregation(AggregationBuilders.avg("avg_salary").field("salary")))
            ).execute().actionGet();

    System.out.println(response);
}

重建mapping語句:

PUT /employee
{
  "mappings": {
    "properties": {
      "age": {
        "type": "long"
      },
      "country": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        },
        "fielddata": true
      },
      "joinData": {
        "type": "date"
      },
      "name": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "position": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "salary": {
        "type": "long"
      }
    }
  }
}

新版本

添加maven依賴:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.8.1</version>
</dependency>

代碼測(cè)試

public static void main(String[] args) throws IOException {
    HttpHost[] httpHost = {HttpHost.create("192.168.111.40:9200")};
    RestHighLevelClient restHighLevelClient = new RestHighLevelClient(RestClient.builder(httpHost));
    // addDoc(restHighLevelClient);
    // getDoc(restHighLevelClient);
    // updateDoc(restHighLevelClient);
    delDoc(restHighLevelClient);

    restHighLevelClient.close();
}

/**
 * 添加
 */
public static void addDoc(RestHighLevelClient client) throws IOException {
    IndexRequest request = new IndexRequest("employee");
    request.id("1");
    request.source(XContentFactory.jsonBuilder()
            .startObject()
            .field("user", "tom")
            .field("age", 18)
            .field("position", "scientist")
            .field("country", "China")
            .field("join_data", "2020-01-01")
            .field("salary", 10000)
            .endObject());
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    System.out.println(response.getResult());
}

/**
 * 查詢
 */
public static void getDoc(RestHighLevelClient client) throws IOException {
    // 通過ID來查詢
    GetRequest request = new GetRequest("employee","1");
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 更豐富的查詢條件
    /// SearchRequest searchRequest = new SearchRequest();
    /// client.search(searchRequest, RequestOptions.DEFAULT);

    System.out.println(response.getSourceAsString());
}

/**
 * 更新
 */
public static void updateDoc(RestHighLevelClient client) throws IOException {
    UpdateRequest request = new UpdateRequest("employee", "1");
    request.doc(XContentFactory.jsonBuilder()
            .startObject()
            .field("salary", 1000000)
            .endObject());
    UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
    System.out.println(response.getResult());
}

/**
 * 刪除
 */
public static void delDoc(RestHighLevelClient client) throws IOException {
    DeleteRequest request = new DeleteRequest("employee", "1");
    DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(response);
}  

 /**
 * 查詢職位中包含scientist,并且年齡在28到40歲之間
 */
 public static void search(RestHighLevelClient client) throws IOException {
    SearchRequest request = new SearchRequest("employee");
    request.source(SearchSourceBuilder.searchSource()
            .query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("position", "scientist"))
                    .filter(QueryBuilders.rangeQuery("age").gte("28").lte("28"))
            ).from(0).size(2)
    );
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    System.out.println(JSONObject.toJSONString(search.getHits()));
 }

九、深度探索搜索技術(shù)

9.1 使用term filter來搜索數(shù)據(jù)

準(zhǔn)備測(cè)試數(shù)據(jù)

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

查看mapping

GET /forum/_mapping

查詢結(jié)果:

{
  "forum": {
    "mappings": {
      "article": {
        "properties": {
          "articleID": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "hidden": {
            "type": "boolean"
          },
          "postDate": {
            "type": "date"
          },
          "userID": {
            "type": "long"
          }
        }
      }
    }
  }
}

type=text,默認(rèn)會(huì)設(shè)置兩個(gè)field,一個(gè)是field本身,比如articleID,就是分詞的;還有一個(gè)的就是field.keyword,articleID.keyword,默認(rèn)不分詞,會(huì)最多保留256個(gè)字符

根據(jù)用戶ID搜索帖子

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

term filter/query:對(duì)搜索文本不分詞,直接拿去倒排索引中匹配,你輸入的是什么,就去匹配什么;
比如如果對(duì)搜索文本進(jìn)行分詞的話,“helle world” --> 直接去倒排索引中匹配“hello world”;而不會(huì)去分詞后再匹配。

搜索沒有隱藏的帖子

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

根據(jù)發(fā)帖日期搜索帖子

GET /forum/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "postDate" : "2020-09-09"
                }
            }
        }
    }
}

根據(jù)帖子ID搜索帖子

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

上面那個(gè)查詢不得任何結(jié)果

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

第一個(gè)為什么查詢不到結(jié)果?前面講了對(duì)應(yīng)類型是text的es會(huì)建立2次索引,一個(gè)是分詞一個(gè)不分詞(在keyword中);
使用term 進(jìn)行查詢時(shí)不會(huì)對(duì)其進(jìn)行分詞就開始查詢,此時(shí)直接通過字段查詢是取匹配分詞的倒排索引自然也就匹配不到了;因此需要使用articleID.keyword去匹配。

articleID.keyword,是es最新版本內(nèi)置建立的field,就是不分詞的。所以一個(gè)articleID過來的時(shí)候,會(huì)建立兩次索引,一次是自己本身,是要分詞的,分詞后放入倒排索引;
另外一次是基于articleID.keyword,不分詞,最多保留256個(gè)字符,直接一個(gè)字符串放入倒排索引中。
term filter,對(duì)text過濾,可以考慮使用內(nèi)置的field.keyword來進(jìn)行匹配。但是有個(gè)問題,默認(rèn)就保留256個(gè)字符,如果超過了就GG了。
所以盡可能還是自己去手動(dòng)建立索引,指定not_analyzed。在最新版本的es中,不需要指定not_analyzed也可以,將type設(shè)為keyword即可。

查看分詞

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

默認(rèn)是analyzed的text類型的field,建立倒排索引的時(shí)候,會(huì)對(duì)所有的articleID分詞,分詞以后,原本的articleID就沒有了,只有分詞后的各個(gè)word存在于倒排索引中。
term,是不對(duì)搜索文本分詞的,但是articleID建立索引為 xhdk,a,1293,fj3,自然直接搜索也就沒得結(jié)果了。

重建索引

DELETE /forum

PUT /forum
{
  "mappings": {
      "properties": {
        "articleID": {
          "type": "keyword"
        }
      }
    }
}

執(zhí)行上面的初始化數(shù)據(jù)語句,再次直接查詢即可查詢到結(jié)果

term filter:根據(jù)exact value進(jìn)行搜索,數(shù)字、boolean、date天然支持
相當(dāng)于SQL中的單個(gè)where條件

9.2 filter執(zhí)行原理深度剖析(bitset機(jī)制與caching機(jī)制)

  • (1)在倒排索引中查找搜索串,獲取document list;
  • (2)為每個(gè)在倒排索引中搜索到的結(jié)果(doc list),構(gòu)建一個(gè)bitset,就是一個(gè)二進(jìn)制的數(shù)組,數(shù)組每個(gè)元素都是0或1,用來標(biāo)識(shí)一個(gè)doc對(duì)一個(gè)filter條件是否匹配,如果匹配就是1,不匹配就是0,類似這樣:[0, 0, 0, 1, 0, 1];
    這樣盡可能用簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)去實(shí)現(xiàn)復(fù)雜的功能,可以節(jié)省內(nèi)存空間,提升性能;
  • (3)遍歷每個(gè)過濾條件對(duì)應(yīng)的bitset,優(yōu)先從最稀疏的開始搜索,查找滿足所有條件的document

一次性其實(shí)可以在一個(gè)search請(qǐng)求中,發(fā)出多個(gè)filter條件,每個(gè)filter條件都會(huì)對(duì)應(yīng)一個(gè)bitset;
遍歷每個(gè)filter條件對(duì)應(yīng)的bitset,先從最稀疏的開始遍歷

[0, 0, 0, 1, 0, 0]:比較稀疏
[0, 1, 0, 1, 0, 1]

先遍歷比較稀疏的bitset,就可以先過濾掉盡可能多的數(shù)據(jù);

遍歷所有的bitset,找到匹配所有filter條件的doc;就可以將document作為結(jié)果返回給client了

  • (4)caching bitset,跟蹤query,在最近256個(gè)query中超過一定次數(shù)的過濾條件,緩存其bitset。對(duì)于小segment(<1000,或<3%),不緩存bitset。

比如條件為postDate=2017-01-01,生成的bitset為[0, 0, 1, 1, 0, 0],可以緩存在內(nèi)存中,這樣下次如果再有這個(gè)條件過來的時(shí)候,就不用重新掃描倒排索引,反復(fù)生成bitset,可以大幅度提升性能。

在最近的256個(gè)filter中,有某個(gè)filter超過了一定的次數(shù),這個(gè)次數(shù)不固定,就會(huì)自動(dòng)緩存這個(gè)filter對(duì)應(yīng)的bitset。

segment(分片),filter針對(duì)小segment獲取到的結(jié)果,可以不緩存,segment記錄數(shù)<1000,或者segment大小<index總大小的3%。

segment數(shù)據(jù)量很小時(shí),哪怕是掃描也很快;同時(shí)segment會(huì)在后臺(tái)自動(dòng)合并,小segment很快就會(huì)跟其他小segment合并成大segment,此時(shí)緩存也沒有什么意義,因?yàn)檫@些小segment合并后很快就消失了。

filter比query的好處就在于會(huì)caching,實(shí)際上并不是一個(gè)filter返回的完整的doc list數(shù)據(jù)結(jié)果。而是filter bitset緩存完整的doc list數(shù)據(jù)結(jié)果。下次不用掃描倒排索引了。

  • (5)filter大部分情況下來說,在query之前執(zhí)行,先盡量過濾掉盡可能多的數(shù)據(jù)

query:是會(huì)計(jì)算doc對(duì)搜索條件的relevance score,還會(huì)根據(jù)這個(gè)score去排序

filter:只是簡(jiǎn)單過濾出想要的數(shù)據(jù),不計(jì)算relevance score,也不排序

  • (6)如果document有新增或修改,那么cached bitset會(huì)被自動(dòng)更新;
    即當(dāng)document有新增或修改時(shí),會(huì)自動(dòng)更新到相關(guān)filter的bitset中緩存中。

  • (7)以后只要是有相同的filter條件的,會(huì)直接來使用這個(gè)過濾條件對(duì)應(yīng)的cached bitset即可快速將數(shù)據(jù)過濾出來返回。

9.3 基于bool組合多個(gè)filter條件來搜索數(shù)據(jù)

bool中可以通過must,must_not,should來組合多個(gè)過濾條件;bool可以嵌套,類似SQL中的and

搜索發(fā)帖日期為2020-09-09,或者帖子ID為XHDK-A-1293-#fJ3的帖子,同時(shí)要求帖子的發(fā)帖日期絕對(duì)不為2020-09-09

類似SQL如下:

SELECT
    * 
FROM
    forum.article 
WHERE
    ( post_date = '2020-09-09' OR article_id = 'XHDK-A-1293-#fJ3' ) 
    AND post_date != '2020-09-10'

es查詢語句

GET /forum/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {"term": { "postDate": "2020-09-09" }},
            {"term": {"articleID": "XHDK-A-1293-#fJ3"}}
          ],
          "must_not": {
            "term": {
              "postDate": "2020-09-10"
            }
          }
        }
      }
    }
  }
}

must 必須匹配 ,should 可以匹配其中任意一個(gè)即可,must_not 必須不匹配

搜索帖子ID為XHDK-A-1293-#fJ3,或者是帖子ID為JODL-X-1937-#pV7而且發(fā)帖日期為2020-09-09的帖子

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": "2020-09-09"
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}

9.4 使用terms搜索多個(gè)值以及多值搜索結(jié)果優(yōu)化

term: {"field": "value"}
terms: {"field": ["value1", "value2"]}

sql中的in

select * from tbl where col in ("value1", "value2")

為帖子數(shù)據(jù)增加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"]} }

搜索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"]
                }
            }
        }
    }
}

優(yōu)化搜索結(jié)果,僅僅搜索tag只包含java的帖子

現(xiàn)有的數(shù)據(jù)結(jié)構(gòu)無法完成要求,因此我們添加一個(gè)標(biāo)識(shí)字段

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} }

執(zhí)行查詢語句

GET /forum/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "must": [
            {
              "term": {
                "tag_cnt": 1
              }
            },
            {
              "terms": {
                "tag": ["java"]
              }
            }
          ]
        }
      }
    }
  }
}    

9.5 基于range filter來進(jìn)行范圍過濾

為帖子數(shù)據(jù)增加瀏覽量的字段

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} }

搜索瀏覽量在30~60之間的帖子

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

搜索發(fā)帖日期在最近1個(gè)月的帖子

準(zhǔn)備示例數(shù)據(jù)

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

執(zhí)行查詢語句

GET /forum/_search 
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "postDate": {
            "lt": "2020-10-10||-30d"
          }
        }
      }
    }
  }
}

GET /forum/_search 
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "postDate": {
            "gt": "now-30d"
          }
        }
      }
    }
  }
}

range相當(dāng)于sql中的between,做范圍過濾

9.6 手動(dòng)控制全文檢索結(jié)果的精準(zhǔn)度

全文檢索的時(shí)候,進(jìn)行多個(gè)值的檢索,有兩種做法,match query;should;

控制搜索結(jié)果精準(zhǔn)度:and operator,minimum_should_match

為帖子數(shù)據(jù)增加標(biāo)題字段

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"} }

搜索標(biāo)題中包含java或elasticsearch的blog

這個(gè)和之前的那個(gè)term query不一樣。不是搜索exact value,是進(jìn)行全文檢索(full text)。
負(fù)責(zé)進(jìn)行全文檢索的是match query。當(dāng)然,如果要檢索的field,是not_analyzed類型的,那么match query也相當(dāng)于term query。

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

搜索標(biāo)題中包含java和elasticsearch的

搜索結(jié)果精準(zhǔn)控制的第一步:靈活使用and關(guān)鍵字,如果你是希望所有的搜索關(guān)鍵字都要匹配的,那么就用and,可以實(shí)現(xiàn)單純match query無法實(shí)現(xiàn)的效果。

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

搜索包含java,elasticsearch,spark,hadoop,4個(gè)關(guān)鍵字中,至少3個(gè)

控制搜索結(jié)果的精準(zhǔn)度的第二步:指定一些關(guān)鍵字中,必須至少匹配其中的多少個(gè)關(guān)鍵字,才能作為結(jié)果返回

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

用bool組合多個(gè)搜索條件,來搜索title

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

bool組合多個(gè)搜索條件,如何計(jì)算relevance score?

must和should搜索對(duì)應(yīng)的分?jǐn)?shù),加起來,除以must和should的總數(shù)

排名第一:java,同時(shí)包含should中所有的關(guān)鍵字,hadoop,elasticsearch
排名第二:java,同時(shí)包含should中的elasticsearch
排名第三:java,不包含should中的任何關(guān)鍵字

should是可以影響相關(guān)度分?jǐn)?shù)的

must是確保誰必須有這個(gè)關(guān)鍵字,同時(shí)會(huì)根據(jù)這個(gè)must的條件去計(jì)算出document對(duì)這個(gè)搜索條件的relevance score
在滿足must的基礎(chǔ)之上,should中的條件,不匹配也可以,但是如果匹配的更多,那么document的relevance score就會(huì)更高

搜索java,hadoop,spark,elasticsearch,至少包含其中3個(gè)關(guān)鍵字

默認(rèn)情況下,should是可以不匹配任何一個(gè)的,比如上面的搜索中,this is java blog,就不匹配任何一個(gè)should條件
但是有個(gè)例外的情況,如果沒有must的話,那么should中必須至少匹配一個(gè)才可以
比如下面的搜索,should中有4個(gè)條件,默認(rèn)情況下,只要滿足其中一個(gè)條件,就可以匹配作為結(jié)果返回

但是可以精準(zhǔn)控制,should的4個(gè)條件中,至少匹配幾個(gè)才能作為結(jié)果返回

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

9.7 基于term+bool實(shí)現(xiàn)的multiword搜索底層原理剖析

普通match如何轉(zhuǎn)換為term+should

{
    "match": { "title": "java elasticsearch"}
}

使用諸如上面的match query進(jìn)行多值搜索的時(shí)候,es會(huì)在底層自動(dòng)將這個(gè)match query轉(zhuǎn)換為bool的語法。
bool should,指定多個(gè)搜索詞,同時(shí)使用term query

{
  "bool": {
    "should": [
      { "term": { "title": "java" }},
      { "term": { "title": "elasticsearch"   }}
    ]
  }
}

and match如何轉(zhuǎn)換為term+must

{
    "match": {
        "title": {
            "query":    "java elasticsearch",
            "operator": "and"
        }
    }
}

轉(zhuǎn)化為:

{
  "bool": {
    "must": [
      { "term": { "title": "java" }},
      { "term": { "title": "elasticsearch"   }}
    ]
  }
}

minimum_should_match如何轉(zhuǎn)換

{
    "match": {
        "title": {
            "query": "java elasticsearch hadoop spark",
            "minimum_should_match": "75%"
        }
    }
}

轉(zhuǎn)化為

{
  "bool": {
    "should": [
      { "term": { "title": "java" }},
      { "term": { "title": "elasticsearch"   }},
      { "term": { "title": "hadoop" }},
      { "term": { "title": "spark" }}
    ],
    "minimum_should_match": 3 
  }
}

9.8 基于boost的細(xì)粒度搜索條件權(quán)重控制

需求:

搜索標(biāo)題中包含java的帖子,同時(shí)呢,如果標(biāo)題中包含hadoop或elasticsearch就優(yōu)先搜索出來,
同時(shí)呢,如果一個(gè)帖子包含java hadoop,一個(gè)帖子包含java elasticsearch,包含hadoop的帖子要比elasticsearch優(yōu)先搜索出來

知識(shí)點(diǎn):

搜索條件的權(quán)重,boost,可以將某個(gè)搜索條件的權(quán)重加大,此時(shí)當(dāng)匹配這個(gè)搜索條件和匹配另一個(gè)搜索條件的document,
計(jì)算relevance score時(shí),匹配權(quán)重更大的搜索條件的document,relevance score會(huì)更高,當(dāng)然也就會(huì)優(yōu)先被返回回來。
默認(rèn)情況下,搜索條件的權(quán)重是相同的,都是1

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

9.9 多shard場(chǎng)景下relevance score不準(zhǔn)確問題

多shard場(chǎng)景下relevance score不準(zhǔn)確問題

如果你的一個(gè)index有多個(gè)shard的話,可能搜索結(jié)果會(huì)不準(zhǔn)確


如何解決該問題?

  • (1)生產(chǎn)環(huán)境下,數(shù)據(jù)量大,盡可能實(shí)現(xiàn)均勻分配

數(shù)據(jù)量很大的話,其實(shí)一般情況下,在概率學(xué)的背景下,es都是在多個(gè)shard中均勻路由數(shù)據(jù)的,路由的時(shí)候根據(jù)_id,負(fù)載均衡
比如說有10個(gè)document,title都包含java,一共有5個(gè)shard,那么在概率學(xué)的背景下,如果負(fù)載均衡的話,其實(shí)每個(gè)shard都應(yīng)該有2個(gè)doc,title包含java
如果數(shù)據(jù)分布均勻的話,其實(shí)就沒有剛才說的那個(gè)問題了

  • (2)測(cè)試環(huán)境下,將索引的primary shard設(shè)置為1個(gè),number_of_shards=1,index settings

如果只有一個(gè)shard,所有的document都在這個(gè)shard里面,也就沒有這個(gè)問題了

  • (3)測(cè)試環(huán)境下,搜索附帶search_type=dfs_query_then_fetch參數(shù),會(huì)將local IDF取出來計(jì)算global IDF

計(jì)算一個(gè)doc的相關(guān)度分?jǐn)?shù)的時(shí)候,就會(huì)將所有shard對(duì)local IDF計(jì)算一下獲取出來,然后在本地進(jìn)行g(shù)lobal IDF分?jǐn)?shù)的計(jì)算,之后將所有shard的doc作為上下文來進(jìn)行計(jì)算,也能確保準(zhǔn)確性。
但是production生產(chǎn)環(huán)境下,不推薦這個(gè)參數(shù),因?yàn)樾阅芎懿睢?/p>

9.10 基于dis_max實(shí)現(xiàn)best fields策略進(jìn)行多字段搜索

為帖子數(shù)據(jù)增加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"} }

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

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

搜索結(jié)果分析

期望的是doc5,結(jié)果是doc2,doc4排在了前面

計(jì)算每個(gè)document的relevance score:每個(gè)query的分?jǐn)?shù),乘以matched query數(shù)量,除以總query數(shù)量

  • 算一下doc4的分?jǐn)?shù)
{ "match": { "title": "java solution" }},針對(duì)doc4,是有一個(gè)分?jǐn)?shù)的
{ "match": { "content":  "java solution" }},針對(duì)doc4,也是有一個(gè)分?jǐn)?shù)的

所以是兩個(gè)分?jǐn)?shù)加起來,比如說,1.1 + 1.2 = 2.3;matched query數(shù)量 = 2;總query數(shù)量 = 2;即:2.3 * 2 / 2 = 2.3

  • 算一下doc5的分?jǐn)?shù)
{ "match": { "title": "java solution" }},針對(duì)doc5,是沒有分?jǐn)?shù)的
{ "match": { "content":  "java solution" }},針對(duì)doc5,是有一個(gè)分?jǐn)?shù)的

只有一個(gè)query是有分?jǐn)?shù)的,比如2.3;matched query數(shù)量 = 1;總query數(shù)量 = 2;即:2.3 * 1 / 2 = 1.15

doc5的分?jǐn)?shù) = 1.15 < doc4的分?jǐn)?shù) = 2.3

best fields策略,dis_max

best fields策略,搜索到的結(jié)果,應(yīng)該是某一個(gè)field中匹配到了盡可能多的關(guān)鍵詞,被排在前面;而不是盡可能多的field匹配到了少數(shù)的關(guān)鍵詞,排在了前面

dis_max語法,直接取多個(gè)query中,分?jǐn)?shù)最高的那一個(gè)query的分?jǐn)?shù)即可

{ "match": { "title": "java solution" }},針對(duì)doc4,是有一個(gè)分?jǐn)?shù)的,1.1
{ "match": { "content":  "java solution" }},針對(duì)doc4,也是有一個(gè)分?jǐn)?shù)的,1.2

取最大分?jǐn)?shù),1.2

{ "match": { "title": "java solution" }},針對(duì)doc5,是沒有分?jǐn)?shù)的
{ "match": { "content":  "java solution" }},針對(duì)doc5,是有一個(gè)分?jǐn)?shù)的,2.3

取最大分?jǐn)?shù),2.3

然后doc4的分?jǐn)?shù) = 1.2 < doc5的分?jǐn)?shù) = 2.3,所以doc5就可以排在更前面的地方,符合我們的需要

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

9.11 基于tie_breaker參數(shù)優(yōu)化dis_max搜索效果

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

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

可能在實(shí)際場(chǎng)景中出現(xiàn)的一個(gè)情況是這樣的:

  • (1)某個(gè)帖子,doc1,title中包含java,content不包含java beginner任何一個(gè)關(guān)鍵詞
  • (2)某個(gè)帖子,doc2,content中包含beginner,title中不包含任何一個(gè)關(guān)鍵詞
  • (3)某個(gè)帖子,doc3,title中包含java,content中包含beginner
  • (4)最終搜索,可能出來的結(jié)果是,doc1和doc2排在doc3的前面,而不是我們期望的doc3排在最前面

dis_max,只是取分?jǐn)?shù)最高的那個(gè)query的分?jǐn)?shù)而已

dis_max只取某一個(gè)query最大的分?jǐn)?shù),完全不考慮其他query的分?jǐn)?shù)

使用tie_breaker將其他query的分?jǐn)?shù)也考慮進(jìn)去

tie_breaker參數(shù)的意義,在于將其他query的分?jǐn)?shù),乘以tie_breaker,然后綜合與最高分?jǐn)?shù)的那個(gè)query的分?jǐn)?shù),綜合在一起進(jìn)行計(jì)算;
除了取最高分以外,還會(huì)考慮其他的query的分?jǐn)?shù);tie_breaker的值,在0~1之間,是個(gè)小數(shù),就ok

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

9.12 基于multi_match語法實(shí)現(xiàn)dis_max+tie_breaker

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": {
            "body": {
              "query": "java beginner",
              "minimum_should_match": "30%"
            }
          }
        }
      ],
      "tie_breaker": 0.3
    }
  } 
}

minimum_should_match,主要是用來干嘛的?

去長(zhǎng)尾 long tail,什么是長(zhǎng)尾,比如你搜索5個(gè)關(guān)鍵詞,但是很多結(jié)果只匹配1個(gè)關(guān)鍵詞,其實(shí)跟你想要的結(jié)果相差甚遠(yuǎn),這些結(jié)果就是長(zhǎng)尾;
minimum_should_match,控制搜索結(jié)果的精準(zhǔn)度,只有匹配一定數(shù)量的關(guān)鍵詞的數(shù)據(jù),才能返回

9.13 基于multi_match+most fiels策略進(jìn)行multi-field搜索

從best-fields換成most-fields策略

best-fields策略,主要是將某一個(gè)field匹配盡可能多的關(guān)鍵詞的doc優(yōu)先返回回來

most-fields策略,主要是盡可能返回更多field匹配到某個(gè)關(guān)鍵詞的doc,優(yōu)先返回回來

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": "learning courses"
    }
  }
}

sub_title用的是enligsh analyzer,所以還原了單詞

為什么,因?yàn)槿绻覀冇玫氖穷愃朴趀nglish analyzer這種分詞器的話,就會(huì)將單詞還原為其最基本的形態(tài),stemmer

learning --> learn
learned --> learn
courses --> course


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

與best_fields的區(qū)別

  • (1)best_fields,是對(duì)多個(gè)field進(jìn)行搜索,挑選某個(gè)field匹配度最高的那個(gè)分?jǐn)?shù),同時(shí)在多個(gè)query最高分相同的情況下,在一定程度上考慮其他query的分?jǐn)?shù)。
    簡(jiǎn)單來說,你對(duì)多個(gè)field進(jìn)行搜索,就想搜索到某一個(gè)field盡可能包含更多關(guān)鍵字的數(shù)據(jù)。

優(yōu)點(diǎn):通過best_fields策略,以及綜合考慮其他field,還有minimum_should_match支持,可以盡可能精準(zhǔn)地將匹配的結(jié)果推送到最前面。

缺點(diǎn):除了那些精準(zhǔn)匹配的結(jié)果,其他差不多大的結(jié)果,排序結(jié)果不是太均勻,沒有什么區(qū)分度了。

實(shí)際的例子:百度之類的搜索引擎,最匹配的到最前面,但是其他的就沒什么區(qū)分度了

  • (2)most_fields,綜合多個(gè)field一起進(jìn)行搜索,盡可能多地讓所有field的query參與到總分?jǐn)?shù)的計(jì)算中來,此時(shí)就會(huì)是個(gè)大雜燴,出現(xiàn)類似best_fields案例最開始的那個(gè)結(jié)果,結(jié)果不一定精準(zhǔn),
    某一個(gè)document的一個(gè)field包含更多的關(guān)鍵字,但是因?yàn)槠渌鹍ocument有更多field匹配到了,所以排在了前面;
    因此需要建立類似sub_title.std這樣的field,盡可能讓某一個(gè)field精準(zhǔn)匹配query string,貢獻(xiàn)更高的分?jǐn)?shù),將更精準(zhǔn)匹配的數(shù)據(jù)排到前面

優(yōu)點(diǎn):將盡可能匹配更多field的結(jié)果推送到最前面,整個(gè)排序結(jié)果是比較均勻的;

缺點(diǎn):可能那些精準(zhǔn)匹配的結(jié)果,無法推送到最前面

實(shí)際的例子:wiki,明顯的most_fields策略,搜索結(jié)果比較均勻,但是的確要翻好幾頁才能找到最匹配的結(jié)果

9.14 使用most_fields策略進(jìn)行cross-fields search弊端

cross-fields搜索,一個(gè)唯一標(biāo)識(shí),跨了多個(gè)field。
比如一個(gè)人,標(biāo)識(shí),是姓名;一個(gè)建筑,它的標(biāo)識(shí)是地址。姓名可以散落在多個(gè)field中,比如first_name和last_name中,地址可以散落在country,province,city中。

跨多個(gè)field搜索一個(gè)標(biāo)識(shí),比如搜索一個(gè)人名,或者一個(gè)地址,就是cross-fields搜索

初步來說,如果要實(shí)現(xiàn),可能用most_fields比較合適。因?yàn)閎est_fields是優(yōu)先搜索單個(gè)field最匹配的結(jié)果,cross-fields本身就不是一個(gè)field的問題了。

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"} }

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

Peter Smith,匹配author_first_name,匹配到了Smith,這時(shí)候它的分?jǐn)?shù)很高,為什么?。???

因?yàn)镮DF分?jǐn)?shù)高,IDF分?jǐn)?shù)要高,那么這個(gè)匹配到的term(Smith),在所有doc中的出現(xiàn)頻率要低,author_first_name field中,Smith就出現(xiàn)過1次

Peter Smith這個(gè)人,doc 1,Smith在author_last_name中,但是author_last_name出現(xiàn)了兩次Smith,所以導(dǎo)致doc 1的IDF分?jǐn)?shù)較低

問題1:只是找到盡可能多的field匹配的doc,而不是某個(gè)field完全匹配的doc

問題2:most_fields,沒辦法用minimum_should_match去掉長(zhǎng)尾數(shù)據(jù),就是匹配的特別少的結(jié)果

問題3:TF/IDF算法,比如Peter Smith和Smith Williams,搜索Peter Smith的時(shí)候,由于first_name中很少有Smith的,所以query在所有document中的頻率很低,得到的分?jǐn)?shù)很高,可能Smith Williams反而會(huì)排在Peter Smith前面

9.15 使用copy_to定制組合field解決cross-fields搜索弊端

上一講,我們說了用most_fields策略,去實(shí)現(xiàn)cross-fields搜索,有3大弊端,而且搜索結(jié)果也顯示出了這3大弊端

第一個(gè)辦法:用copy_to,將多個(gè)field組合成一個(gè)field

問題其實(shí)就出在有多個(gè)field,有多個(gè)field以后,就很尷尬,我們要想辦法將一個(gè)標(biāo)識(shí)跨在多個(gè)field的情況,合并成一個(gè)field。
比如說,一個(gè)人名,本來是first_name,last_name,現(xiàn)在合并成一個(gè)full_name,這樣就直接查full_name 就ok了。

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"
      }
  }
}

用了這個(gè)copy_to語法之后,就可以將多個(gè)字段的值拷貝到一個(gè)字段中,并建立倒排索引

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

GET /forum/_search
{
  "query": {
    "match": {
      "new_author_full_name": "Peter Smith"
    }
  }
}

問題1:只是找到盡可能多的field匹配的doc,而不是某個(gè)field完全匹配的doc --> 解決,最匹配的document被最先返回

問題2:most_fields,沒辦法用minimum_should_match去掉長(zhǎng)尾數(shù)據(jù),就是匹配的特別少的結(jié)果 --> 解決,可以使用minimum_should_match去掉長(zhǎng)尾數(shù)據(jù)

問題3:TF/IDF算法,比如Peter Smith和Smith Williams,搜索Peter Smith的時(shí)候,由于first_name中很少有Smith的,所以query在所有document中的頻率很低,得到的分?jǐn)?shù)很高,可能Smith Williams反而會(huì)排在Peter Smith前面 --> 解決,Smith和Peter在一個(gè)field了,所以在所有document中出現(xiàn)的次數(shù)是均勻的,不會(huì)有極端的偏差

9.16 使用原生cross-fiels技術(shù)解決搜索弊端

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

問題1:只是找到盡可能多的field匹配的doc,而不是某個(gè)field完全匹配的doc --> 解決,要求每個(gè)term都必須在任何一個(gè)field中出現(xiàn)

問題2:most_fields,沒辦法用minimum_should_match去掉長(zhǎng)尾數(shù)據(jù),就是匹配的特別少的結(jié)果 --> 解決,既然每個(gè)term都要求出現(xiàn),長(zhǎng)尾肯定被去除掉了

問題3:TF/IDF算法,比如Peter Smith和Smith Williams,搜索Peter Smith的時(shí)候,由于first_name中很少有Smith的,
所以query在所有document中的頻率很低,得到的分?jǐn)?shù)很高,可能Smith Williams反而會(huì)排在Peter Smith前面 --> 計(jì)算IDF的時(shí)候,
將每個(gè)query在每個(gè)field中的IDF都取出來,取最小值,就不會(huì)出現(xiàn)極端情況下的極大值了

9.17 掌握phrase matching搜索技術(shù)

如果我們要盡量讓java和spark離的很近的document優(yōu)先返回,要給它一個(gè)更高的relevance score,這就涉及到了proximity match,近似匹配

需求:

  1. java spark,就靠在一起,中間不能插入任何其他字符,就要搜索出來這種doc
  2. java spark,但是要求,java和spark兩個(gè)單詞靠的越近,doc的分?jǐn)?shù)越高,排名越靠前

要實(shí)現(xiàn)上述兩個(gè)需求,用match做全文檢索,是搞不定的,必須得用proximity match,近似匹配

phrase match,proximity match:短語匹配,近似匹配

使用match_phrase來查詢包含java and elasticsearch的數(shù)據(jù)

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

使用match_phrase來查詢包含java and elasticsearch的數(shù)據(jù)

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

match_phrase的基本原理

這里舉個(gè)簡(jiǎn)單例子來說明;有如下2個(gè)文檔內(nèi)容,我們需要 match_phrase匹配的是java elasticsearch

doc1 : hello, java elasticsearch
doc2 : hello, elasticsearch java

首先對(duì)文檔內(nèi)容建立類似如下的倒排索引,在value中保存了term(單詞)的position(位置)

hello -------------- [doc1(1), doc2(1)]  
java --------------- [doc1(2), doc2(3)]
elasticsearch ------ [doc1(3), doc2(2)]

這樣在查詢時(shí)先對(duì)查詢的內(nèi)容java elasticsearch進(jìn)行分詞得到java、elasticsearch然后根據(jù)倒排索引進(jìn)行查詢得到匹配的文檔doc1,doc2;

現(xiàn)在對(duì)數(shù)據(jù)進(jìn)一步做匹配處理:

doc1-->> java-doc1(2),elasticsearch-doc1(3);elasticsearch的position剛好比java的大1,符合實(shí)際的順序,doc1 符合條件;

doc2-->> java-doc2(3),elasticsearch-doc2(2);elasticsearch的position比java的小1,不符合實(shí)際的順序,doc2 不符合條件;

最終只有doc1符合條件。

9.18 基于slop參數(shù)實(shí)現(xiàn)近似匹配以及原理剖析和相關(guān)實(shí)驗(yàn)

GET /forum/_search
{
    "query": {
        "match_phrase": {
            "content": {
                "query": "java elasticsearch",
                "slop":  1
            }
        }
    }
}

slop的含義:query string,搜索文本中的幾個(gè)term,要經(jīng)過幾次移動(dòng)才能與一個(gè)document匹配,這個(gè)移動(dòng)的次數(shù),就是slop;
這里設(shè)置slop的意思就是在匹配的過程中最多可以移動(dòng)多少次;

其實(shí),加了slop的phrase match,就是proximity match,近似匹配

9.19 混合使用match和近似匹配實(shí)現(xiàn)召回率與精準(zhǔn)度的平衡

召回率:搜索一個(gè)java elasticsearch,總共有100個(gè)doc,能返回多少個(gè)doc作為結(jié)果,就是召回率(recall)

精準(zhǔn)度:搜索一個(gè)java elasticsearch,能不能盡可能讓包含java elasticsearch,或者是java和elasticsearch離的很近的doc,排在最前面,就是精準(zhǔn)度(precision)

直接用match_phrase短語搜索,會(huì)導(dǎo)致必須所有term都在doc field中出現(xiàn),而且距離在slop限定范圍內(nèi),才能匹配上

match phrase和proximity match要求doc必須包含所有的term,才能作為結(jié)果返回;如果某一個(gè)doc可能就是有某個(gè)term沒有包含,那么就無法作為結(jié)果返回

近似匹配的時(shí)候,召回率比較低,精準(zhǔn)度太高了

但是有時(shí)我們希望的是匹配到幾個(gè)term中的部分,就可以作為結(jié)果出來,這樣可以提高召回率。
同時(shí)我們也希望用上match_phrase根據(jù)距離提升分?jǐn)?shù)的功能,讓幾個(gè)term距離越近分?jǐn)?shù)就越高,優(yōu)先返回。
就是優(yōu)先滿足召回率意思,比如搜索java elasticsearch,包含java的也返回,包含elasticsearch的也返回,包含java和elasticsearch的也返回;
同時(shí)兼顧精準(zhǔn)度,就是包含java和elasticsearch,同時(shí)java和elasticsearch離的越近的doc排在最前面

此時(shí)可以用bool組合match query和match_phrase query一起,來實(shí)現(xiàn)上述效果

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

在match query中java或elasticsearch或java elasticsearch,java和elasticsearch靠前,但是沒法區(qū)分java和elasticsearch的距離,也許java和elasticsearch靠的很近,但是沒法排在最前面
match_phrase在slop以內(nèi),如果java elasticsearch能匹配上一個(gè)doc,那么就會(huì)對(duì)doc貢獻(xiàn)自己的relevance score,如果java和elasticsearch靠的越近,那么就分?jǐn)?shù)越高

9.20 使用rescoring機(jī)制優(yōu)化近似匹配搜索的性能

match和phrase match(proximity match)區(qū)別

match: 只要簡(jiǎn)單的匹配到了一個(gè)term,就可以理解將term對(duì)應(yīng)的doc作為結(jié)果返回,掃描倒排索引,掃描到了就ok

phrase match :首先掃描到所有term的doc list; 找到包含所有term的doc list; 然后對(duì)每個(gè)doc都計(jì)算每個(gè)term的position,是否符合指定的范圍; slop,需要進(jìn)行復(fù)雜的運(yùn)算,來判斷能否通過slop移動(dòng),匹配一個(gè)doc

match query的性能比phrase match和proximity match(有slop)要高很多。因?yàn)楹髢烧叨家?jì)算position的距離。
match query比phrase match的性能要高10倍,比proximity match的性能要高20倍。

但是別太擔(dān)心,因?yàn)閑s的性能一般都在毫秒級(jí)別,match query一般就在幾毫秒,或者幾十毫秒,而phrase match和proximity match的性能在幾十毫秒到幾百毫秒之間,所以也是可以接受的。

優(yōu)化proximity match的性能,一般就是減少要進(jìn)行proximity match搜索的document數(shù)量。
主要思路就是,用match query先過濾出需要的數(shù)據(jù),然后再用proximity match來根據(jù)term距離提高doc的分?jǐn)?shù),
同時(shí)proximity match只針對(duì)每個(gè)shard的分?jǐn)?shù)排名前n個(gè)doc起作用,來重新調(diào)整它們的分?jǐn)?shù),這個(gè)過程稱之為重計(jì)分(rescoring)。
因?yàn)橐话阌脩魰?huì)分頁查詢,只會(huì)看到前幾頁的數(shù)據(jù),所以不需要對(duì)所有結(jié)果進(jìn)行proximity match操作。

用我們剛才的說法,match + proximity match同時(shí)實(shí)現(xiàn)召回率和精準(zhǔn)度

默認(rèn)情況下,match也許匹配了1000個(gè)doc,proximity match全都需要對(duì)每個(gè)doc進(jìn)行一遍運(yùn)算,判斷能否slop移動(dòng)匹配上,然后去貢獻(xiàn)自己的分?jǐn)?shù)
但是很多情況下,match出來也許1000個(gè)doc,其實(shí)用戶大部分情況下是分頁查詢的,所以可能最多只會(huì)看前幾頁,比如一頁是10條,最多也許就看5頁,就是50條
proximity match只要對(duì)前50個(gè)doc進(jìn)行slop移動(dòng)去匹配,去貢獻(xiàn)自己的分?jǐn)?shù)即可,不需要對(duì)全部1000個(gè)doc都去進(jìn)行計(jì)算和貢獻(xiàn)分?jǐn)?shù)

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

9.21 實(shí)戰(zhàn)前綴搜索、通配符搜索、正則搜索等技術(shù)

前綴搜索

GET /forum/_search
{
  "query": {
    "prefix": {
      "articleID.keyword": {
        "value": "X"
      }
    }
  }
}

前綴搜索的原理:prefix query不計(jì)算relevance score,與prefix filter唯一的區(qū)別就是,filter會(huì)cache bitset;掃描整個(gè)倒排索引。前綴越短,要處理的doc越多,性能越差,盡可能用長(zhǎng)前綴搜索

前綴搜索,它是怎么執(zhí)行的?性能為什么差呢?

根據(jù)前綴掃描完整個(gè)的倒排索引,一個(gè)個(gè)匹配將結(jié)果返回,這就是為什么性能差

通配符搜索

跟前綴搜索類似,使用通配符去表達(dá)更加復(fù)雜的模糊搜索的語義,功能更加強(qiáng)大

5?-*5:5個(gè)字符 D 任意個(gè)字符5

GET /forum/_search
{
  "query": {
    "wildcard": {
      "articleID": {
        "value": "X?K*5"
      }
    }
  }
}

?:任意字符; *:0個(gè)或任意多個(gè)字符

性能一樣差,必須掃描整個(gè)倒排索引

正則搜索

GET /forum/_search
{
  "query": {
    "regexp": {
      "articleID": "X[0-9].+"
    }
  }
}

wildcard和regexp,與prefix原理一致,都會(huì)掃描整個(gè)索引,性能很差

9.22 實(shí)戰(zhàn)match_phrase_prefix實(shí)現(xiàn)search-time搜索推薦

GET /forum/_search
{
  "query": {
    "match_phrase_prefix": {
      "content": "java e"
    }
  }
}

原理跟match_phrase類似,唯一的區(qū)別,就是把最后一個(gè)term作為前綴去搜索

大致流程:

  1. 搜索java e會(huì)先分詞為java、e;
  2. 然后java會(huì)進(jìn)行match搜索對(duì)應(yīng)的doc;
  3. e會(huì)作為前綴,去掃描整個(gè)倒排索引,找到所有w開頭的doc;
  4. 然后找到所有doc中,即包含java,又包含e開頭的字符的doc; 根據(jù)你的slop去計(jì)算,看在slop范圍內(nèi),能不能讓java e,
    正好跟doc中的java和e開頭的單詞的position相匹配;也可以指定slop,但是只有最后一個(gè)term會(huì)作為前綴。

max_expansions:指定prefix最多匹配多少個(gè)term,超過這個(gè)數(shù)量就不繼續(xù)匹配了,限定性能

默認(rèn)情況下,前綴要掃描所有的倒排索引中的term,去查找e打頭的單詞,但是這樣性能太差。可以用max_expansions限定,e前綴最多匹配多少個(gè)term,就不再繼續(xù)搜索倒排索引了。

盡量不要用,因?yàn)椋詈笠粋€(gè)前綴始終要去掃描大量的索引,性能可能會(huì)很差

9.23 實(shí)戰(zhàn)通過ngram分詞機(jī)制實(shí)現(xiàn)index-time搜索推薦

ngram和index-time搜索推薦原理

什么是ngram?按詞語可以拆分的長(zhǎng)度進(jìn)行處理,下面舉例說明:

quick,5種長(zhǎng)度下的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?固定首字母,然后依次疊加詞;下面舉例說明:

quick,根據(jù)首字母后進(jìn)行ngram

q
qu
qui
quic
quick

使用edge ngram將每個(gè)單詞都進(jìn)行進(jìn)一步的分詞切分,用切分后的ngram來實(shí)現(xiàn)前綴搜索推薦功能

搜索的時(shí)候,不用再根據(jù)一個(gè)前綴,然后掃描整個(gè)倒排索引了; 直接拿前綴去倒排索引中匹配即可,如果匹配上了,那么就好了。

ngram示例

設(shè)置ngram

PUT /ngram-demo
{
    "settings": {
        "analysis": {
            "filter": {
                "autocomplete_filter": { 
                    "type":     "edge_ngram",
                    "min_gram": 1,
                    "max_gram": 20
                }
            },
            "analyzer": {
                "autocomplete": {
                    "type":      "custom",
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "autocomplete_filter" 
                    ]
                }
            }
        }
    }
}

查看分詞

GET /ngram-demo/_analyze
{
  "analyzer": "autocomplete",
  "text": "quick brown"
}

_mapping設(shè)置

PUT /ngram-demo/_mapping
{
  "properties": {
      "title": {
          "type":     "text",
          "analyzer": "autocomplete",
          "search_analyzer": "standard"
      }
  }
}

添加測(cè)試數(shù)據(jù)

POST /ngram-demo/_bulk
{ "index": { "_id": 1 }}
{ "title" : "hello wiki", "userID" : 1, "hidden": false }

數(shù)據(jù)查詢

GET /ngram-demo/_search
{
  "query": {
    "match_phrase": {
      "title": "hello w"
    }
  }
}

如果用match,只有hello的也會(huì)出來,全文檢索,只是分?jǐn)?shù)比較低;
推薦使用match_phrase,要求每個(gè)term都有,而且position剛好靠著1位,符合我們的期望的

9.24 深入揭秘TF&IDF算法以及向量空間模型算法

boolean model:

類似and這種邏輯操作符,先過濾出包含指定term的doc;比如:

query "hello world" --> 過濾 --> hello / world / hello & world
bool --> must/must not/should --> 過濾 --> 包含 / 不包含 / 可能包含
doc --> 不打分?jǐn)?shù) --> 正或反 true or false --> 為了減少后續(xù)要計(jì)算的doc的數(shù)量,提升性能

單個(gè)term在doc中的分?jǐn)?shù)

  • TF/IDF:

一個(gè)term在一個(gè)doc中,根據(jù)出現(xiàn)的次數(shù)給個(gè)分?jǐn)?shù),出現(xiàn)的次數(shù)越多,那么最后給的相關(guān)度評(píng)分就會(huì)越高

  • IDF:inversed document frequency

一個(gè)term在所有的doc中,出現(xiàn)的次數(shù)越多,那么最后給的相關(guān)度評(píng)分就會(huì)越低

  • length norm:搜索的那個(gè)field內(nèi)容的長(zhǎng)度,field長(zhǎng)度越長(zhǎng),給的相關(guān)度評(píng)分越低; field長(zhǎng)度越短,給的相關(guān)度評(píng)分越高

最后,會(huì)將這個(gè)term,對(duì)doc1的分?jǐn)?shù),綜合TF,IDF,length norm,計(jì)算出來一個(gè)綜合性的分?jǐn)?shù)

  • vector space model:多個(gè)term對(duì)一個(gè)doc的總分?jǐn)?shù)

es會(huì)根據(jù)搜索詞語在所有doc中的評(píng)分情況,計(jì)算出一個(gè)query vector(query向量);
會(huì)給每一個(gè)doc,拿每個(gè)term計(jì)算出一個(gè)分?jǐn)?shù)來,再拿所有term的分?jǐn)?shù)組成一個(gè)doc vector;

畫在一個(gè)圖中,取每個(gè)doc vector對(duì)query vector的弧度,給出每個(gè)doc對(duì)多個(gè)term的總分?jǐn)?shù)

每個(gè)doc vector計(jì)算出對(duì)query vector的弧度,最后基于這個(gè)弧度給出一個(gè)doc相對(duì)于query中多個(gè)term的總分?jǐn)?shù)
弧度越大,分?jǐn)?shù)月底; 弧度越小,分?jǐn)?shù)越高

如果是多個(gè)term,那么就是線性代數(shù)來計(jì)算,無法用圖表示

9.25 實(shí)戰(zhàn)掌握四種常見的相關(guān)度分?jǐn)?shù)優(yōu)化方法

query-time boost

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

重構(gòu)查詢結(jié)構(gòu)

重構(gòu)查詢結(jié)果,在es新版本中,影響越來越小了。一般情況下,沒什么必要的話,大家不用也行。

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

negative boost降低相關(guān)度

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

negative的doc,會(huì)乘以negative_boost,降低分?jǐn)?shù)

constant_score

如果你壓根兒不需要相關(guān)度評(píng)分,直接走constant_score加filter,所有的doc分?jǐn)?shù)都是1,沒有評(píng)分的概念了

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

9.26 實(shí)戰(zhàn)用function_score自定義相關(guān)度分?jǐn)?shù)算法

我們可以做到自定義一個(gè)function_score函數(shù),自己將某個(gè)field的值,跟es內(nèi)置算出來的分?jǐn)?shù)進(jìn)行運(yùn)算,然后由自己指定的field來進(jìn)行分?jǐn)?shù)的增強(qiáng)

數(shù)據(jù)準(zhǔn)備

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} }

將搜索得到的分?jǐn)?shù),跟follower_num進(jìn)行運(yùn)算,由follower_num在一定程度上增強(qiáng)其分?jǐn)?shù);follower_num越大,那么分?jǐn)?shù)就越高

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
    }
  }
}

如果只有field,那么會(huì)將每個(gè)doc的分?jǐn)?shù)都乘以follower_num,如果有的doc follower是0,那么分?jǐn)?shù)就會(huì)變?yōu)?,效果很不好。
因此一般會(huì)加個(gè)log1p函數(shù),公式會(huì)變?yōu)椋琻ew_score = old_score * log(1 + number_of_votes),這樣出來的分?jǐn)?shù)會(huì)比較合理;
再加個(gè)factor,可以進(jìn)一步影響分?jǐn)?shù),new_score = old_score * log(1 + factor * number_of_votes);
boost_mode,可以決定分?jǐn)?shù)與指定字段的值如何計(jì)算,multiply,sum,min,max,replace;
max_boost,限制計(jì)算出來的分?jǐn)?shù)不要超過max_boost指定的值

9.27 實(shí)戰(zhàn)掌握誤拼寫時(shí)的fuzzy模糊搜索技術(shù)

搜索的時(shí)候,可能輸入的搜索文本會(huì)出現(xiàn)誤拼寫的情況

fuzzy搜索技術(shù) --> 自動(dòng)將拼寫錯(cuò)誤的搜索文本,進(jìn)行糾正,糾正以后去嘗試匹配索引中的數(shù)據(jù)

實(shí)際想要hello,但是少寫個(gè)o

GET /forum/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "hell",
        "fuzziness": 2
      }
    }
  }
}

fuzziness 指定的修訂最大次數(shù),默認(rèn)為2

GET /forum/_search
{
  "query": {
    "match": {
      "title": {
        "query": "helio",
        "fuzziness": "AUTO",
        "operator": "and"
      }
    }
  }
}

十、IK中文分詞器

安裝

從github上下載安裝包(或者自己編譯):

https://github.com/medcl/elasticsearch-analysis-ik

將解壓后的文件放到es的docker容器中(也可以做個(gè)文件目錄的映射):

docker cp /home/ik es7:/usr/share/elasticsearch/plugins/

ik目錄就是解壓后的文件目錄
如果不確定plugins目錄在哪兒,可以通過docker exec -it es7 /bin/bash命令進(jìn)入容器內(nèi)查看

然后重啟es

docker restart es7

ik分詞器基礎(chǔ)知識(shí)

兩種analyzer,根據(jù)自己的需要選擇,但是一般是選用ik_max_word

ik_max_word: 會(huì)將文本做最細(xì)粒度的拆分,比如會(huì)將“中華人民共和國(guó)國(guó)歌”拆分為“中華人民共和國(guó),中華人民,中華,華人,人民共和國(guó),人民,人,民,共和國(guó),共和,和,國(guó)國(guó),國(guó)歌”,會(huì)窮盡各種可能的組合;

ik_smart: 會(huì)做最粗粒度的拆分,比如會(huì)將“中華人民共和國(guó)國(guó)歌”拆分為“中華人民共和國(guó),國(guó)歌”。

ik分詞器的使用

配置mapping:

PUT /news
{
  "mappings": {
    "properties": {
      "content":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

IK分詞器配置文件講解以及自定義詞庫

IKAnalyzer.cfg.xml:用來配置自定義詞庫
main.dic:ik原生內(nèi)置的中文詞庫,總共有27萬多條,只要是這些單詞,都會(huì)被分在一起
quantifier.dic:放了一些單位相關(guān)的詞
suffix.dic:放了一些后綴
surname.dic:中國(guó)的姓氏
stopword.dic:英文停用詞

ik原生最重要的兩個(gè)配置文件

main.dic:包含了原生的中文詞語,會(huì)按照這個(gè)里面的詞語去分詞

stopword.dic:包含了英文的停用詞,停用詞,stopword

一般,像停用詞,會(huì)在分詞的時(shí)候,直接被干掉,不會(huì)建立在倒排索引中

自定義詞庫

(1)自己建立詞庫:每年都會(huì)涌現(xiàn)一些特殊的流行詞,網(wǎng)紅,藍(lán)瘦香菇,喊麥,鬼畜,一般不會(huì)在ik的原生詞典里

自己補(bǔ)充自己的最新的詞語,到ik的詞庫里面去

在IKAnalyzer.cfg.xml中配置自定義的詞,ext_dict,custom/mydict.dic

補(bǔ)充自己的詞語,然后需要重啟es,才能生效

(2)自己建立停用詞庫:比如了,的,啥,么,我們可能并不想去建立索引,讓人家搜索

custom/ext_stopword.dic,已經(jīng)有了常用的中文停用詞,可以補(bǔ)充自己的停用詞,然后重啟es

修改IK分詞器源碼來基于mysql熱更新詞庫

熱更新

每次都是在es的擴(kuò)展詞典中,手動(dòng)添加新詞語,很坑
(1)每次添加完,都要重啟es才能生效,非常麻煩
(2)es是分布式的,可能有數(shù)百個(gè)節(jié)點(diǎn),你不能每次都一個(gè)一個(gè)節(jié)點(diǎn)上面去修改

es不停機(jī),直接我們?cè)谕獠磕硞€(gè)地方添加新的詞語,es中立即熱加載到這些新詞語

熱更新的方案

(1)修改ik分詞器源碼,然后手動(dòng)支持從mysql中每隔一定時(shí)間,自動(dòng)加載新的詞庫
(2)基于ik分詞器原生支持的熱更新方案,部署一個(gè)web服務(wù)器,提供一個(gè)http接口,通過modified和tag兩個(gè)http響應(yīng)頭,來提供詞語的熱更新

用第一種方案,第二種,ik git社區(qū)官方都不建議采用,覺得不太穩(wěn)定

十一、ICU分詞器

ICU Analysis插件是一組將Lucene ICU模塊集成到Elasticsearch中的庫。
本質(zhì)上,ICU的目的是增加對(duì)Unicode和全球化的支持,以提供對(duì)亞洲語言更好的文本分割分析,還有大量對(duì)除英語外其他語言進(jìn)行正確匹配和排序所必須的分詞過濾器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容