讀書筆記:從Lucene到Elasticsearch:全文檢索實戰

公司項目的的日誌埋點是發到 Elasticsearch 上的,有時開發會去在 kibana 上查找相關的日誌信息,用於診斷用戶的問題。可是 kibana 太太重量級,查找起來比較慢。因而學習了下 Elasticsearch 的用法,此文章我的也只是點到爲止。工做時夠用就好了,全部有些地方可能並無詳細說明。只能充當入門讀物吧。javascript

當前的筆記只介紹 Elasticsearch 的搜索部分。java

文章中的搜索都是在 kibanaDev tools 進行查詢的。python

準備工做

須要安裝 Elasticsearchkibanaelasticsearch-analysis-ikgit

具體的安裝方式,這裏就再也不闡述了。(安裝完,記得重啓 Elasticsearchgithub

重啓完成後,打開 kibanaDev tools,輸入下面的DSL代碼,並運行:正則表達式

PUT books
{
  "settings": {
    "number_of_replicas": 1,
    "number_of_shards": 3
  },
  "mappings": {
    "IT": {
      "properties": {
        "id": {
          "type": "long"
        },
        "title": {
          "type": "text",
          "analyzer": "ik_max_word"
        },
        "language": {
          "type": "keyword"
        },
        "author": {
          "type": "keyword"
        },
        "price": {
          "type": "double"
        },
        "year": {
          "type": "date",
          "format": "yyyy-MM-dd"
        },
        "description": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      }
    }
  }
}

複製代碼

運行好後,下載 books.json 文件,並進行導入。若是你安裝的 Elasticsearch 版本小於6.0,使用下面的命令進行導入 books.json數據庫

curl -XPOST "http://localhost:9200/_bulk?pretty" --data-binary @books.json
複製代碼

若是你的 Elasticsearch 版本大於6.0,則使用下面的命令進行導入:編程

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/_bulk?pretty" --data-binary @books.json
複製代碼

基本搜索

返回指定index的全部文檔

GET books/_search
{
  "query": {
    "match_all": {}
  }
}
複製代碼

能夠簡寫爲:json

GET books/_search
複製代碼

查找指定字段中包含給定單詞的文檔

使用term來進行查詢,term查詢不會被解析,只有查詢的詞和文檔中的詞精確匹配纔會被搜索到,應用場景爲:查詢人名、地名等須要精準匹配的需求。數組

查詢title字段中含有思想的書籍

GET books/_search
{
  "query": {
    "term": {
      "title": "思想"
    }
  }
}
複製代碼

返回以下:

Imgur

對查詢結果進行分頁

有時查詢時,會返回成千上萬的數據,這種狀況下,分頁的做用就出來了。

分頁有兩個屬性,分別是fromsize

  • from: 從何處開始
  • size: 返回的文檔最大數量

能夠理解爲:我從from位置把剩下的文檔所有返回,而後size限制了返回的數量。

用js代碼來詮釋就是:

const from = 100 - 1; // 數組從0開始,須要減一
const size = 10;
const data = [1, 2, 3, ..., 999, 1000];

const fromDate = data.splice(from);
const result = fromData.splice(0, size);
console.log(result) //=> [100, 101, 102, 103, 104, 105, 106, 107, 108, 109]
複製代碼

限制返回字段

通常咱們查詢時,都是爲了觀察某一個字段,而不是想看所有的字段。而若是是默認狀況下,Elasticsearch 會返回的文檔的所有字段信息。會對工做形成必定的影響。因而,Elasticsearch 提供了一個接口,用於限制返回的字段。假設我只須要 titleauthor 字段:

GET books/_search
{
  "_source": ["title", "author"],
  "query": {
    "term": {
      "title": "java"
    }
  }
}
複製代碼

結果如圖:

Imgur

基於最小評分過濾

由於 Elasticsearch 在作普通的搜索時,是採用相關性進行搜索的,而相關性是由評分 取決的。因此當咱們進行模糊搜索時,Elasticsearch 可能會返回一些相關性不那麼高的文檔。因此咱們能夠經過 Elasticsearch 提供的接口,來設置一個評分最低標準,低於這個標準的文檔,將不會出如今結果頁中。

好比,我想搜索 title 裏包含 java 的文檔,而且評分不低於0.7

GET books/_search
{
  "min_score": 0.7,
  "query": {
    "term": {
      "title": "java"
    }
  }
}
複製代碼

結果如圖:

Imgur

高亮關鍵字

有時,咱們會把 Elasticsearch 結果直接導入到網頁中,這個時候須要高亮關鍵字,讓用戶更加清楚本身想要的東西,Elasticsearch 已經提供了一個接口,好比我想讓搜索出來的結果中的關鍵字高亮:

GET books/_search
{
  "_source": ["title"],
  "min_score": 0.7,
  "query": {
    "term": {
      "title": "java"
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    }
  }
}
複製代碼

結果如圖:

Imgur

默認的標籤是<em></em>,若是你想自定義,可使用: pre_tagspost_tags。最終查詢代碼爲:

GET books/_search
{
  "_source": ["title"],
  "min_score": 0.7,
  "query": {
    "term": {
      "title": "java"
    }
  },
  "highlight" : {
    "pre_tags" : ["<h1>"],
    "post_tags" : ["</h1>"],
    "fields" : {
      "title" : {}
    }
  }
}
複製代碼

結果如圖:

Imgur

全文查詢

上節基本都是以 term 進行搜索,但其實 Elasticsearch 提供了不少搜索方法,本章就是介紹 Elasticsearch 有哪些搜索方法、分別起的做用。

本章對 common_terms queryquery_string querysimple_query_string query 沒有解釋說明,由於使用起來較少,並且解釋起來較爲麻煩。若是想了解,能夠參考網上的文章。這裏就不在闡述了。

match query

咱們先使用 term 進行一次查詢:

GET books/_search
{
  "_source": ["title", "author"],
  "query": {
    "term": {
      "title": "java編程"
    }
  }
}
複製代碼

你會發現,其結果爲空(可是數據庫裏是有這個數據的),如圖:

Imgur

這是由於 term 是匹配分詞後的詞項來進行查詢的。好比剛剛咱們查的 java編程 ,在 Elasticsearch 進行分詞時,會把 java編程 分爲:java編程 。致使匹配不起來。

用代碼詮釋的話就是:

const keyword = 'java編程';
const data = ['java', '編程'];
const result = data.includes(keyword);
console.log(result) //=> false
複製代碼

如今咱們把 term 換成 match 來嘗試下:

GET books/_search
{
  "_source": ["title", "author"],
  "query": {
    "match": {
      "title": "java編程"
    }
  }
}
複製代碼

結果如圖:

Imgur

能夠發現,已經有結果了,可是爲何會有兩個呢?

緣由是由於 match 會對你的關鍵字進行分詞,而後去匹配文檔分詞後的結果,只要文檔裏的詞項能匹配關鍵字分詞後的任何一個,都會返回到結果裏。

代碼詮釋:

const data = ['java', '編程', '思想']; // 分詞後的文檔裏的數據
const keywords = ['java', '編程', '思想']; // 分詞後的關鍵字

const result = (() => {
  for (let x = 0; x < data.length; x++) {
    const dataItem = data[x];
    for (let y = 0; y < keywords.length; y++) {
      const keywordItem = keywords[y];
      if (dataItem === keywordItem) {
        return true;
      }
    }
  }
  return false;
})()
複製代碼

若是我只想讓它返回一個呢,而且只能用 match 來作,能夠麼?

是能夠的,match 提供了一個屬性:operator。能夠用這個來幫助完成這個需求:

GET books/_search
{
  "_source": ["title", "author"],
  "query": {
    "match": {
      "title": {
        "query": "java編程",
        "operator": "and"
      }
    }
  }
}
複製代碼

最終的結果如圖:

Imgur

原理是由於 operator 屬性的值爲 and,這樣的話,就告訴 Elasticsearch 我要讓個人關鍵字都能和文檔裏的詞項匹配上。有一個沒匹配上,我都不要。

若是 operator 屬性的值爲 or,那結果就和以前是同樣的了。

match_phrase query

你能夠把這個方法理解爲自帶了 operator 屬性的值爲 andmatch

這個方法有兩個限制條件,只有都知足,纔會在結果中顯示出:

  • 分詞後的全部詞項都在該字段中,至關於 operator: "and"
  • 順序要一致

順序一致指的是什麼呢?

假設你使用 match 來匹配: 編程java,那麼結果仍是和上面同樣。因此若是你須要要求順序一致性,那麼你就可使用 match_phrase 來作。

若是使用 編程java 來搜索:

Imgur

若是使用 java編程

Imgur

match_phrase_prefix query

這個方法和 match_phrase 方法相似,不過這個方法能夠能夠把最後一個詞項做爲前綴進行匹配,想象一下:用戶在搜索欄中搜索 辣雞UZ,而後下面列表中出現了 辣雞UZI

首先 match_phrase_prefix 會先分詞爲: 辣雞,而後找了一個文檔,再而後匹配 辣雞 後面的字符串是否以 UZ 開頭的。這個時候文檔知足條件,就返回出結果。能夠假想後面一直有一個(.*)的通配符,如:辣雞UZ(.*)

知道原理了,咱們如今寫一個查詢語句:

GET books/_search
{
  "_source": ["title", "author"],
  "query": {
    "match_phrase_prefix": {
      "title": "java編"
    }
  }
}
複製代碼

結果如圖:

Imgur

multi_match query

multi_matchmatch 的升級方法,能夠用來搜索多個字段。

好比我不想只在 title 裏搜索 java編程,我還想在 description 裏進行搜索。那應該怎麼作呢?

Elasticsearch 已經提供了 multi_match 專門用來處理這件事情:

GET books/_search
{
  "_source": ["title", "description"],
  "query": {
    "multi_match": {
      "query": "java編程",
      "fields": ["title", "description"]
    }
  }
}
複製代碼

最終結果如圖:

Imgur

而且 multi_match 還支持通配符。上面的查詢語句,能夠寫成:

GET books/_search
{
  "_source": ["title", "description"],
  "query": {
    "multi_match": {
      "query": "java編程",
      "fields": ["title", "*tion"]
    }
  }
}
複製代碼

詞項查詢

上一章是全文查詢,這一章是詞項查詢。他們倆的區別在於:

  • 全文查詢:會對查詢語句(query)進行分詞,而後匹配文檔裏分詞後的數據
  • 詞項查詢:不會對查詢語句進行分詞

term query

第一章節已經介紹過了,這裏就再也不闡述了。

terms query

termsterm 查詢的升級版本,能夠用來查詢文檔中某一字段,是否包含了其關鍵字。好比,我想查詢 title 字段中包含了 優化 或者 基礎 的文檔:

GET books/_search
{
  "_source": ["title"],
  "query": {
    "terms": {
      "title": ["優化", "基礎"]
    }
  }
}
複製代碼

其結果如圖:

Imgur

range query

從名字就能猜想出 range 是範圍匹配。能夠匹配 numberdatestring (字符串範圍查詢比較特殊,比較少用,就再也不闡述了)

range 支持如下查詢參數:

  • gt: 大於
  • gte: 大於等於
  • lt: 小於
  • lte: 小於等於

number 範圍查詢

如今我想查詢價格低於70,並大於等於50的書籍。僞代碼既:(price >= 50 && price < 70)

GET books/_search
{
  "_source": ["title", "price"], 
  "query": {
    "range": {
      "price": {
        "gte": 50,
        "lt": 70
      }
    }
  }
}
複製代碼

其結果如圖:

Imgur

date 範圍查詢

若是我想查詢,出版日期在 2016-1-12016-12-31 之間的書籍,那麼DSL查詢語句就如同如下這樣:

GET books/_search
{
  "_source": ["title", "publish_time"],
  "query": {
    "range": {
      "publish_time": {
        "gte": "2016-1-1",
        "lte": "2016-12-31",
        "format": "yyyy-MM-dd"
      }
    }
  }
}
複製代碼

其結果如圖:

Imgur

exists query

匹配有這個屬性的文檔。好比我想找到存在 title 字段的文檔:

GET books/_search
{
  "_source": "title", 
  "query": {
    "exists": {
      "field": "title"
    }
  }
}
複製代碼

結果會返回全部的文檔。那麼如何定義 有這個屬性 呢?

定義的規則以下:

  • {"title": "js"}: 存在
  • {"title": ""}: 存在
  • {"title": ["js"]}: 存在
  • {"title": ["js", null]}: 存在(有一個值不爲空就行)
  • {"title": null}: 不存在
  • {"title": []} 不存在
  • {"title": [null]} 不存在
  • {"foo": "bar"}: 不存在

perfix query

用來匹配文檔分詞後的詞項中的前綴。咱們先寫個DSL進行匹配下:

GET books/_search
{
  "_source": "description", 
  "query": {
    "prefix": {
      "description": "wi"
    }
  }
}
複製代碼

其結果如圖:

Imgur

爲什麼 wi 能夠匹配到這個呢?由於 Elasticsearch 會對 description 進行分詞,其中會把 winPython 分爲 win Python。那麼這兩個就是文檔分詞後的詞項,而 prefix 匹配每一個詞項的開頭是否匹配,至關於js的 startsWith 方法。用代碼詮釋的話就是:

const dataItem = ['win', 'python'];
const prefixKeyword = 'wi';

const result = dataItem.some(item => item.startsWith(prefixKeyword));

console.log(result); //=> true
複製代碼

wildcard query

wildcard 爲通配符查詢。不過目前只支持 *?。所表明的含義爲:

  • *: 零個或多個
  • ?: 一個或多個

注意:wildcard 不是匹配全文,仍是會對文檔的字段進行分詞,而後應用於每一個詞項

好比,我如今想查詢 wi* 的文檔:

GET books/_search
{
  "_source": "description", 
  "query": {
    "wildcard": {
      "description": "wi*"
    }
  }
}
複製代碼

其結果如圖:

Imgur

首先 Elasticsearch 會先對 description 進行分詞爲:winpython。而後 wi* 會應用到每一個詞項裏,其中 win 符合規則,則顯示在結果中。

若是我用 win?,則不會有任何的結果,由於 ? 表明的是一個或多個。那麼匹配到 win 的時候,後面沒有字符串了,則結果爲空。

regexp query

其爲正則表達式查詢,原理同 wildcard,這裏就不在闡述了。

fuzzy query

能夠把 fuzzy 理解爲模糊查詢。好比用戶輸入關鍵字時,一不當心輸入錯了,變成了 javascrpit,那麼 fuzzy 的做用就出來了。它仍能夠搜索到 javascript:

GET books/_search
{
  "_source": "description", 
  "query": {
    "fuzzy": {
      "description": "javascrpit"
    }
  }
}
複製代碼

其結果如圖:

Imgur

複合查詢

複合查詢就是把簡單的查詢組合在一塊兒,從而實現更加複雜的查詢。而且複合查詢還能夠控制另外一個查詢的行爲。

constant_score query

不太經常使用,用於對返回結果的文檔進行打分。

這裏就不在闡述了,若是感興趣,可見:[Elasticsearch] 控制相關度 (四) - 忽略TF/IDF

bool query

這個查詢方法,仍是很是重要的。這個方法提供瞭如下操做方法:

  • must: 文檔必須知足 must 下面的查詢條件,至關於AND 或者 &&
  • should: 文檔能夠匹配 should 下的查詢條件,匹配不出來也沒事。至關於 OR 或者 ||
  • must_not: 和 must 相反,必須不知足 must_not 下面的查詢條件,至關於 !==
  • filter: 其功能和 must 同樣,可是不會打分,也就說不會影響文檔的 _score 字段

如今,咱們想要查詢:書籍做者(author)是 葛一鳴,書籍名稱(title)裏包含 java 的書籍,價格(price)不能高於 70 低於 40,而且書籍描述(description)能夠包含或者不包含 虛擬機 的書籍。

GET books/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "author": "葛一鳴"
        }
      },
      "must": [
        {
          "match": {
            "title": "java"
          }
        }
      ],
      "should": [
        {
          "match": {
            "description": "虛擬機"
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 70,
              "lt": 40
            }
          }
        }
      ]
    }
  }
}
複製代碼

其結果如圖:

Imgur

dis_max query、function_score query、boosting query

這三個就不在闡述了,其主要做用是關係到 _score,也就是關係到查詢的結果的評分。感興趣的,能夠在網上搜下。

結尾

此文章是《從Lucene到Elasticsearch:全文檢索實戰》一書的讀書筆記,若是形成侵權,可與我聯繫,我將刪除此文。

這本書是十分棒的,若是你們有興趣想深刻了解的話,能夠去進行購買此書。

做者信息

Black-Hole: 158blackhole@gmail.com

Blog: www.bugs.cc

Github: github.com/BlackHole1

其餘

我司(愛樂奇)招人,感興趣的小夥伴能夠來投簡歷呀。

彈性工做制、每日水果、同事都特別nice、96五、團建、五險一金...

地點上海浦軟大廈

相關文章
相關標籤/搜索