如何用 Node.js 和 Elasticsearch 構(gòu)建搜索引擎

如何用 Node.js 和 Elasticsearch 構(gòu)建搜索引擎

相關(guān)閱讀: docker-nodejs
英文原文:Build a Search Engine with Node.js and Elasticsearch

Elasticsearch 是一款開源的搜索引擎怎燥,由于其高性能和分布式系統(tǒng)架構(gòu)而備受關(guān)注。本文將討論其關(guān)鍵特性,并手把手教你如何用它創(chuàng)建 Node.js 搜索引擎。

Elasticsearch 概述

Elasticsearch 底層使用 Apache Lucene 庫,Apache Lucene 自身是一款高性能桦山、基于文本的搜索引擎庫。 Elasticsearch 并不以提供數(shù)據(jù)存儲和檢索等類數(shù)據(jù)庫功能為核心目標(biāo),相反膘婶,它以搜索引擎(服務(wù)器端)為目標(biāo)缺前,意在提供數(shù)據(jù)索引、數(shù)據(jù)檢索悬襟、和數(shù)據(jù)實(shí)時分析功能

Elasticsearch 采用分布式架構(gòu)衅码,因而通過新增節(jié)點(diǎn)、或者部署到系統(tǒng)已有節(jié)點(diǎn)上即可實(shí)現(xiàn)水平擴(kuò)展脊岳。Elasticsearch 可以在數(shù)以百計的服務(wù)器上處理 PB級別的數(shù)據(jù)逝段。水平擴(kuò)展同時也意味著高可用性,如果有節(jié)點(diǎn)異常割捅,數(shù)據(jù)可重新被調(diào)度執(zhí)行奶躯。

一旦數(shù)據(jù)導(dǎo)入完成,即可被檢索亿驾。Elasticsearch 提供無模式巫糙、JSON 格式文件存儲、數(shù)據(jù)結(jié)構(gòu)和類型自動檢檢測等功能颊乘。

Elasticsearch 同時采用完全 API 驅(qū)動参淹,這意味著:幾乎所有的操作都可在 HTTP 上通過使用符合 JSON 數(shù)據(jù)格式的Restful API 完成。Elasticsearch 提供多種程序語言的客戶端 lib乏悄,包括 Node.js浙值。本文檔將使用 the official client library

Elasticsearch 對軟硬件要求比較靈活檩小。雖然官方建議線上環(huán)境采用 64GB 內(nèi)存开呐,和盡可能多 CPU 系統(tǒng)配置,但其在一個資源受限的系統(tǒng)中依然可以很好地運(yùn)行(前提是你的數(shù)據(jù)集不大)规求。如本文中的示例筐付,2GB 內(nèi)存,單核 cpu 系統(tǒng)即可阻肿。

Elasticsearch 支持主流操作系統(tǒng)瓦戚,如 Linux、Mac os 和 Windows丛塌,只需安裝最新版的 Java 運(yùn)行時環(huán)境(參考 Elasticsearch 安裝章節(jié))较解。對于本文中的示例,還需要安裝 Node.js (v0.11.0 之后的版本都可) 和 npm赴邻。

Elasticsearch術(shù)語

Elasticsearch使用自己的術(shù)語印衔,在某些情況下和典型的數(shù)據(jù)庫系統(tǒng)中使用的術(shù)語不同。下面列出了Elasticsearch中常用的一些術(shù)語及其含義姥敛。

索引: 在Elasticsearch環(huán)境中奸焙,該術(shù)語有兩個含義。第一個含義是添加數(shù)據(jù)的操作。當(dāng)添加數(shù)據(jù)時与帆,文本會被拆分成分詞(token)(例如:單詞)金顿,每個分詞都會被索引。然而鲤桥,一個索引也指的是所有索引數(shù)據(jù)的存儲位置。通常渠概,當(dāng)我們導(dǎo)入數(shù)據(jù)時茶凳,數(shù)據(jù)會被索引成一個index。每次需要對數(shù)據(jù)執(zhí)行任何操作時播揪,都必須指定它的索引名贮喧。

類型:Elasticsearch在一個索引中對文檔提供了更詳細(xì)的分類,稱為類型猪狈。一個索引中的每個文檔還必須有一個類型箱沦。例如,我們可以定義一個圖書館(library)索引雇庙,然后再將數(shù)據(jù)索引成多種類型谓形,比如,文章(article)疆前、書(book)寒跳、報告(report)和演示(presentation)。由于索引幾乎有固定的開銷竹椒,所以建議使用較少的索引和較多的類型童太,而不是較多的索引和較少的類型。

檢索: 如字面意思胸完,你可以檢索不同的索引和類型數(shù)據(jù)书释。Elasticsearch 提供了多種類型的檢索關(guān)鍵字,如term赊窥、phrase爆惧、range、fuzzy锨能,甚至還提供了地理數(shù)據(jù)的查詢詞检激。

過濾: Elasticsearch 支持過濾功能。根據(jù)不同的過濾規(guī)則過濾檢索結(jié)果腹侣,以便進(jìn)一步縮小檢索結(jié)果集叔收。Elasticsearch 依據(jù)相關(guān)性對文檔進(jìn)行排序。如果你為舊文檔新增查詢詞傲隶,可能會觸發(fā)文檔的相關(guān)性排序饺律,使得舊文檔順序發(fā)生變化。但如果只是新增過濾詞跺株,舊文檔的順序保持不變复濒。

聚合: 可在不同類型的聚合數(shù)據(jù)上展開統(tǒng)計分析脖卖,比如minimum, maximum, average, summation, histograms, 等等.

建議: 針對文本輸入,Elasticsearch 提供不同的建議類型巧颈,這些建議類型可以是一個單詞畦木、短語,甚至是完整的語句砸泛。

安裝Elasticsearch

Elasticsearch 受Apache 2許可證保護(hù)十籍,可以被下載,使用唇礁,免費(fèi)修改勾栗。安裝Elasticsearch 之前你需要先確保在你的電腦上安裝了Java Runtime Environment (JRE) ,Elasticsearch 是使用java實(shí)現(xiàn)的并且依賴java庫運(yùn)行盏筐。你可以使用下面的命令行來檢測你是否安裝了java

java -version

推薦使用java最新的穩(wěn)定版本(寫這篇文章的時候是1.8)围俘。你可以在這里找到在你系統(tǒng)上安裝java的指導(dǎo)手冊。

接下來是下載最新版本的Elasticsearch (寫這篇文章的時候是2.3.5)琢融,去下載頁下載ZIP 文件界牡。Elasticsearch 不需要安裝,一個zip文件就包含了可在所有支持的系統(tǒng)上運(yùn)行的文件漾抬。解壓下載的文件欢揖,就完成了。有幾種其他的方式運(yùn)行Elasticsearch 奋蔚,比如:獲得TAR 文件或者為不同Linux發(fā)行版本的包(看這里)她混。

如果你使用的是Mac操作系統(tǒng)并且安裝了 Homebrew ,你就可以使用這行命令安裝Elasticsearch brew install elasticsearch.Homebrew 會自動添加executables 到你的系統(tǒng)并且安裝所需的服務(wù)泊碑。它也可以使用一行命令幫你更新應(yīng)用:brew upgrade elasticsearch.

想在Windows上運(yùn)行Elasticsearch 坤按,可以在解壓的文件夾里,通過命令行運(yùn)行bin\elasticsearch.bat 馒过。對于其他系統(tǒng)臭脓,可以從終端運(yùn)行 ./bin/elasticsearch.這時候,Elasticsearch 就應(yīng)該可以在你的系統(tǒng)上運(yùn)行了腹忽。

就像我之前提到的来累,你可以使用Elasticsearch的幾乎所有的操作,都可以通過RESTful APIs完成窘奏。Elasticsearch 默認(rèn)使用9200 端口嘹锁。為了確保你正確的運(yùn)行了Elasticsearch。在你的瀏覽器中打開http://localhost:9200/ 着裹,將會顯示一些關(guān)于你運(yùn)行的實(shí)例的基本信息领猾。

如果想深入閱讀關(guān)于安裝和故障排除,可以訪問文檔.

圖形用戶界面

Elasticsearch不須圖形用戶界面,只通過REST APIs就提供了幾乎所有的功能摔竿。然而如果我不介紹怎么通過APIs和 Node.js執(zhí)行所有所需的操作面粮,你可以通過幾個提供了索引和數(shù)據(jù)的可視化信息GUI工具來完成,這些工具甚至含有一些高水平的分析继低。

Kibana, 是同一家公司開發(fā)的工具熬苍, 它提供了數(shù)據(jù)的實(shí)時概要,并提供了一些可視化定制和分析選項(xiàng)袁翁。Kibana 是免費(fèi)的柴底,這是詳細(xì)文檔

還有一些是社區(qū)開發(fā)的工具,如 elasticsearch-head, Elasticsearch GUI, 甚至谷歌瀏覽器的擴(kuò)展組件ElasticSearch Toolbox.這些工具可以幫你在瀏覽器中查看你的索引和數(shù)據(jù)梦裂,甚至可以試運(yùn)行不同的搜索和匯總查詢。所有這些工具提供了安裝和使用的攻略盖淡。

創(chuàng)建一個Node.js環(huán)境

彈性搜索為Node.js提供一個官方模塊年柠,稱為elasticsearch。首先褪迟,你需要添加模塊到你的工程目錄下冗恨,并且保存依賴以備以后使用。

npm install elasticsearch --save

然后味赃,你可以在腳本里導(dǎo)入模塊掀抹,如下所示:

const elasticsearch = require('elasticsearch');

最終,你需要創(chuàng)建客戶端來處理與彈性搜索的通訊心俗。在這種情況下傲武,我假設(shè)你正在運(yùn)行彈性搜索的本地機(jī)器IP地址是127.0.0.1,端口是9200(默認(rèn)設(shè)置)城榛。

const esClient = new elasticsearch.Client({
  host: '127.0.0.1:9200',
  log: 'error'
  });</pre>

日志選項(xiàng)確保所有錯誤被打印出來揪利。在本篇文章末處,我將使用相同的esClient對象與Elasticsearch進(jìn)行通訊狠持。這里提供Node模塊的復(fù)雜文檔說明疟位。

注意:這篇導(dǎo)讀的所有源代碼都可以在GitHub下載查看。最簡單的查看方式是在你的PC機(jī)上克隆倉庫喘垂,并且從那里運(yùn)行示例代碼:

git clone https://github.com:sitepoint-editors/node-elasticsearch-tutorial.git
cd node-elasticsearch-tutorial
npm install

在本教程中甜刻,我將使用 1000 篇學(xué)術(shù)論文里的內(nèi)容,這些內(nèi)容是根據(jù)隨機(jī)算法逐一生成的正勒,并以 JSON 格式提供得院,其中的數(shù)據(jù)格式如下所示:

    "_id": "57508457f482c3a68c0a8ab3",
    "title": "Nostrud anim proident cillum non.",
    "journal": "qui ea",
    "volume": 54,
    "number": 11,
    "pages": "109-117",
    "year": 2014,
    "authors": [
      {
        "firstname": "Allyson",
        "lastname": "Ellison",
        "institution": "Ronbert",
        "email": "Allyson@Ronbert.tv"
      },
      ...
    ],
    "abstract": "Do occaecat reprehenderit dolore ...",
    "link": "http://mollit.us/57508457f482c3a68c0a8ab3.pdf",
    "keywords": [
      "sunt",
      "fugiat",
      ...
    ],
    "body": "removed to save space"
  }

JSON 格式中的每個字段如字面意思,無需多余解釋章贞,但值得注意的是:由于<body>包含隨機(jī)生成的文章的全部的內(nèi)容(大概有100~200個段落)尿招,所以并未展示,若要獲取完整數(shù)據(jù),請訪問這里.

雖然 Elasticsearch 提供了索引(indexing)就谜,更新(updating)怪蔑、刪除(deleting)單個數(shù)據(jù)的方法,但我們采用批量(bulk)接口導(dǎo)入數(shù)據(jù)丧荐,因?yàn)榕拷涌谠诖笮蛿?shù)據(jù)集上執(zhí)行操作的效率更高缆瓣。

// index.jsconst bulkIndex = function bulkIndex(index, type, data) {
  let bulkBody = [];

  data.forEach(item => {
    bulkBody.push({
      index: {
        _index: index,
        _type: type,
        _id: item.id      }
    });

    bulkBody.push(item);
  });

  esClient.bulk({body: bulkBody})
  .then(response => {
    console.log('here');
    let errorCount = 0;
    response.items.forEach(item => {
      if (item.index && item.index.error) {
        console.log(++errorCount, item.index.error);
      }
    });
    console.log(
      `Successfully indexed ${data.length - errorCount}
       out of ${data.length} items`
    );
  })
  .catch(console.err);};const test = function test() {
  const articlesRaw = fs.readFileSync('data.json');
  bulkIndex('library', 'article', articles);};

這里,我們調(diào)用函數(shù)bulkIndex建立索引虹统,并傳入 3 個參數(shù)弓坞,分別是:索引名 library,類型名library车荔,JSON 數(shù)據(jù)格式變量 articles渡冻。bulkIndex函數(shù)自身則通過調(diào)用esClient對象的bulk接口實(shí)現(xiàn),bulk 方法包含一個body屬性的對象參數(shù)忧便,并且每個body屬性值是一個包含 2 種操作實(shí)體的數(shù)組對象族吻。第一個實(shí)體是 JSON 格式的操作類型對象,該對象中的index屬性決定了操作的類型(本例子是文件索引)珠增、索引名超歌、文件ID。第二個實(shí)體則是文件對象本身蒂教。

注意巍举,后續(xù)可采用同樣的方式,為其他類型文件(如書籍或者報告)添加索引凝垛。我們還可以有選擇的每個文件分配一個唯一的ID懊悯,如果不體統(tǒng)唯一的ID,Elasticsearch 將主動為每個文件分配一個隨機(jī)的唯一ID梦皮。

假設(shè)你已經(jīng)從代碼庫中下載了 Elasticsearch 項(xiàng)目代碼定枷,在項(xiàng)目根目錄下執(zhí)行如下命令,即可將數(shù)據(jù)導(dǎo)入至Elasticsearch中:

$ node index.js
1000 items parsed from data file
Successfully indexed 1000 out of 1000 items

檢查數(shù)據(jù)的索引是否準(zhǔn)確

Elasticsearch 最大的特性是接近實(shí)時檢索届氢,這意味著欠窒,一旦文檔索引建立完成,1 秒內(nèi)就可被檢索(見這里)退子。索引一旦建立完成岖妄,則可通過運(yùn)行 indice.js 檢查索引信息的準(zhǔn)確性(源碼鏈接):

// indices.js

const indices = function indices() {
  return esClient.cat.indices({v: true})
  .then(console.log)
  .catch(err => console.error(`Error connecting to the es client: ${err}`));
};

client 中的cat 對象方法提供當(dāng)前運(yùn)行實(shí)例的各種信息。其中的 indices 方法列出所有的索引信息寂祥,包括每個索引的健康狀態(tài)荐虐、以及占用的磁盤大小。 而其中的 v 選項(xiàng)為 cat方法新增頭部響應(yīng)丸凭。

當(dāng)運(yùn)行上面代碼段福扬,您會發(fā)現(xiàn)腕铸,集群的健康狀態(tài)被不同的顏色標(biāo)示。其中铛碑,紅色表示為正常運(yùn)行的有問題集群狠裹;黃色表示集群可運(yùn)行,但存在告警汽烦;綠色表示集群正常運(yùn)行涛菠。在本地運(yùn)行上面的代碼段,您極有可能(取決于您的配置)看到集群的健康狀態(tài)顏色是黃色撇吞,這是因?yàn)槟J(rèn)的集群設(shè)置包含 5 個節(jié)點(diǎn)俗冻,但本地運(yùn)行只有 1 個實(shí)例正常運(yùn)行。鑒于本教程的目的僅局限于 Elasticsearch 指導(dǎo)學(xué)習(xí)牍颈,黃色即可迄薄。但在線上環(huán)境中,你必須確保集群的健康狀態(tài)顏色是綠色的煮岁。

$ node indices.js
elasticsearch indices information:
health status index   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   library   5   1       1000            0     41.2mb         41.2mb 

動態(tài)和自定義映射

如前所述, Elasticsearch 無模式(schema-free)讥蔽,這意味著,在數(shù)據(jù)導(dǎo)入之前人乓,您無需定義數(shù)據(jù)的結(jié)構(gòu)(類似于SQL數(shù)據(jù)庫需要預(yù)先定義表結(jié)構(gòu))勤篮,Elasticsearch 會主動檢測都毒。盡管 Elasticsearch 被定義為無模式,但數(shù)據(jù)結(jié)構(gòu)上仍有些限制。

Elasticsearch 以映射的方式引用數(shù)據(jù)結(jié)構(gòu)袋马。當(dāng)數(shù)據(jù)索引建立完成后非凌,如果映射不存在,Elasticsearch 會依次檢索 JSON 數(shù)據(jù)的每個字段瀑焦,然后基于被字段的類型(type)自動生成映射(mapping)腌且。如果存在該字段的映射,則會確保按照同樣的映射規(guī)則新增數(shù)據(jù)榛瓮。否則直接報錯铺董。

比如:如果{"key1": 12} 已經(jīng)存在,Elasticsearch 自動將字段 key1 映射為長整型≠飨現(xiàn)在如果你嘗試通過{"key1": "value1", "key2": "value2"} 檢索, 則會直接報錯精续,因?yàn)橄到y(tǒng)預(yù)期字段 key1 為長整型。同時粹懒,如果通過 {"key1": 13, "key2": "value2"} 檢索則不會報錯重付,并為字段 key2 新增 string 類型。

映射不能超出文本的范圍凫乖,大都數(shù)情況下确垫,系統(tǒng)自動生成的映射都可正常運(yùn)行弓颈。如果想深入了解映射,建議查閱Elasticsearch 文檔删掀。

構(gòu)建查詢引擎

數(shù)據(jù)索引一旦成功建立翔冀,就可立馬構(gòu)建查詢引擎。 Elasticsearch 提供了一種構(gòu)建在 JSON 格式之上爬迟,直觀橘蜜、完整的檢索查詢結(jié)構(gòu),稱之為查詢DSL(Query DSL),來定義查詢付呕。Elasticsearch支持多種類型的檢索詞计福,但本文只說明幾個比較常見的類型檢查詞,更詳細(xì)的查詢DSL(Query DSL)文檔,請參考這里

請記住徽职,每個示例代碼我都提供對應(yīng)的鏈接象颖。環(huán)境配置完成和測試數(shù)據(jù)索引成功建立后,你可以通過從代碼庫 克隆代碼到本地姆钉,其中的任何示例都可成功執(zhí)行说订,只需要在命令中運(yùn)行 node filename.js

以一個或者多個索引的形式返回所有文件內(nèi)容

我們將使用client 端提供的各種檢索方法執(zhí)行檢索操作。最簡單的檢索方式是:match_all潮瓶,該方法講所有文件內(nèi)容以一個或者多個索引的形式返回陶冷。如何以一個索引的方式返回所有文檔內(nèi)容,請看下面示例:(源碼鏈接).

//search_all.js

const search = function search(index, body) {
  return esClient.search({index: index, body: body});
};

const test = function test() {
  let body = {
    size: 20,
    from: 0,
    query: {
      match_all: {}
    }
  };

  search('library', body)
  .then(results => {
    console.log(`found ${results.hits.total} items in ${results.took}ms`);
    console.log(`returned article titles:`);
    results.hits.hits.forEach(
      (hit, index) => console.log(
        `\t${body.from + ++index} - ${hit._source.title}`
      )
    )
  })
  .catch(console.error);
};

主要的檢索詞被包含在 query 對象中毯辅。稍后我們還可以為該 query 對象新增其他不同類型的檢索詞埂伦。需要為每個 query 對象添加一個關(guān)鍵檢索類型(本例是 match_all),且值是一個包含檢索選項(xiàng)對象思恐。在這個例子中沾谜,由于我們需要返回所有文件的索引,所以沒設(shè)置可選項(xiàng)胀莹。

除了 query 對象之外基跑,檢索主體還可以包含其他可選屬性,比如 size(大小)和 from(出處)描焰。 size(大小)屬性表明響應(yīng)中包含的文檔的數(shù)量媳否,如果不設(shè)置該屬性值,默認(rèn)返回10個文檔荆秦。from(出處)屬性表明待返回文檔的起始索引的位置篱竭,用于分頁場景。

理解查詢API的返回結(jié)果

如果你打印搜索API返回結(jié)果(上面例子的結(jié)果)日志萄凤。由于它包含了很多信息室抽,剛開始看起來無所適從。

{ took: 6,
  timed_out: false,
  _shards: { total: 5, successful: 5, failed: 0 },
  hits:
   { total: 1000,
     max_score: 1,
     hits:
      [ [Object],
        [Object],
    ...
        [Object] ] } }

在最高級別日志輸出里靡努,返回結(jié)果中含有took 屬性坪圾,該屬性值表示查找結(jié)果所用的毫秒數(shù)晓折,timed_out只有在最大允許時間內(nèi)沒有找到結(jié)果時為true,_shards 是不同節(jié)點(diǎn)的狀態(tài)的信息(如果部署的是節(jié)點(diǎn)集群)兽泄,hits是查詢結(jié)果漓概。

hits的屬性值是一個含有下列屬性的對象:

  • total —表示匹配的條目的總數(shù)量

  • max_score — 找到的條目的最大分?jǐn)?shù)

  • hits — 找到的條目的數(shù)組,在hits數(shù)組里的每一天記錄病梢,都有索引胃珍,類型,文檔蜓陌,ID觅彰,分?jǐn)?shù),和記錄本身(在_source元素內(nèi))钮热。

這十分復(fù)雜填抬,但是好消息是一旦你實(shí)現(xiàn)了一個提取結(jié)果的方法,不管你的搜索查詢結(jié)果時什么隧期,你都可以使用相同的格式獲取結(jié)果飒责。

還需要注意的是Elasticsearch 有一個好處是它自動地給每一個匹配記錄分配分?jǐn)?shù),這個分?jǐn)?shù)用來量化文件的關(guān)聯(lián)性仆潮,返回結(jié)果的順序默認(rèn)的按鈕分?jǐn)?shù)倒排宏蛉。在例子中我們使用match_all取回了所有的記錄,分?jǐn)?shù)是沒有意義的性置,所有的分?jǐn)?shù)都被計算為1.

匹配含指定字段值的文檔

現(xiàn)在我們看幾個更加有趣的例子. 我們可以通過使用 match 關(guān)鍵字查詢文檔是否與指定的字段值匹配拾并。一個最簡單的包含 match 關(guān)鍵字的檢索主體代碼如下所示:(源碼鏈接).

// search_match.js

{
  query: {
    match: {
      title: {
        query: 'search terms go here'
      }
    }
  }
}

如上文所述, 首先通過為查詢對象新增一個條目,并指定檢索類型蚌讼,上面示例給的是 match 辟灰。然后再檢索類型對象里面个榕,申明待檢索的文檔對象篡石,本例是 title 文檔對象。然后再文檔對象里面西采,提供相關(guān)檢索數(shù)據(jù)凰萨,和 query 屬性。我希望你測試過上述示例之后械馆,驚訝于 Elasticsearch 的檢索效率胖眷。

上述示例執(zhí)行成功后,將返回title(標(biāo)題)字段與任一 query 屬性詞匹配的所有文檔信息霹崎。同時還可以參考如下示例珊搀,為查詢對象附加最小匹配數(shù)量條件:

// search_match.js

...
match: {
  title: {
    query: 'search terms go here',
    minimum_should_match: 3
  }
}
...

與該查詢匹配的文檔 title(標(biāo)題)字段至少包含上訴指定的 3 個關(guān)鍵詞。如果查詢關(guān)鍵詞少于 3個尾菇,那么匹配文檔的 title(標(biāo)題)字段必須包含所有的查詢詞境析。Elasticsearch 的另一個有用的功能是 fuzziness(模糊匹配).這對于用戶輸入錯誤的查詢詞將非常有用囚枪,因?yàn)閒uzzy(模糊匹配)將發(fā)現(xiàn)拼寫錯誤并給出最接近詞供選擇。對于字符串類型劳淆,每個關(guān)鍵字的模糊匹配值是根據(jù)算法 Levenshtein distance 算出的最大允許值链沼。fuzziness(模糊匹配)示例如下所示:

match: {
  title: {
    query: 'search tems go here',
    minimum_should_match: 3,
    fuzziness: 2
  }
}

多個字段搜索

如果你想在多個字段中搜索,可以使用multi_match搜索類型沛鸵。除了Query對象中的fields屬性外括勺,它同match有點(diǎn)類似。fields屬性是需要搜索的字段的集合曲掰。這里我們將在title,authors.firstname, 和authors.lastname 字段中搜索(源碼)疾捍。

// search_multi_match

multi_match: {
  query: 'search terms go here',
  fields: ['title', 'authors.firstname',  'authors.lastname']
}

multi_match查詢支持其他搜索屬性,如minimum_should_match 和fuzziness栏妖。Elasticsearch支持使用通配符(如)匹配字段拾氓,那么我們可以使用['title', 'authors.name']把上面的例子變得更短些。

匹配一個完整的句子

Elasticsearch也支持精確的匹配一個輸入的句子底哥,而不是在單詞級別咙鞍。這個查詢是在普通的match 查詢上擴(kuò)展而來,叫做 match_phrase趾徽。下面是一個match_phrase的例子(源碼

// match_phrase.js

match: {
  title: {
    query: 'search phrase goes here',
    type: 'phrase'
  }}

聯(lián)合多個查詢

到目前為止续滋,在例子中我們每次請求只使用了單個查詢。然而Elasticsearch允許你聯(lián)合多個查詢孵奶。最常用的復(fù)合查詢是bool疲酌,bool查詢接受4種關(guān)鍵類型must, should, must_not, 和filter. 像它們的名字表示的那樣,在查詢結(jié)果的數(shù)據(jù)里必須匹配must里的查詢了袁,必須不匹配must_not里的查詢朗恳,如果哪個數(shù)據(jù)匹配should里的查詢,它就會獲得高分载绿。每一個提到的元素可以使用查詢數(shù)組格式接受多個搜索查詢粥诫。

下面,我們使用bool查詢及一個新的叫做query_string的查詢類型崭庸。它允許你使用 AND 或 OR寫一些比較高級的查詢怀浆。在這里可以看到 query_string語法的所有文檔。另外怕享,我們使用了 range查詢(文檔在這里)执赡,它可以讓我們通過給定的范圍的方式去限制一個字段。(源碼

// search_bool.js

{
  bool: {
    must: [
      {
        query_string: {
          query: '(authors.firstname:term1 OR authors.lastname:term2) AND (title:term3)'
        }
      }
    ],
    should: [
      {
        match: {
          body: {
            query: 'search phrase goes here',
            type: 'phrase'
          }
        }
      }
    ],
    must_not: [
      {
        range: {
          year: {
            gte: 2011,
            lte: 2013
          }
        }
      }
    ]
  }
}

在上面的例子中函筋,查詢返回的數(shù)據(jù)沙合,作者的名包含term1 或它們的姓包含term2,并且它們的title含有term3跌帐,而且它們不在2011,2012或2013年出版的首懈,還有在body字段里含有給定句子數(shù)據(jù)將獲得高分芳来,并被排列到結(jié)果的前面(由于在should從句中的match 查詢)。

過濾猜拾,聚合即舌,和建議

除了它先進(jìn)的搜索功能外,Elasticsearch 還提供了其他的功能挎袜。接下來顽聂,我們再看看其他三個比較常用的功能。

過濾

也許盯仪,你經(jīng)常想使用特定的條件凝縮查詢結(jié)果紊搪。Elasticsearch通過filters 提供了這樣的功能。在我們的文章數(shù)據(jù)里全景,假設(shè)你的查詢返回了幾個文章耀石,這些文章是你選擇的在5個具體年份發(fā)布的文章。你可以簡單的從搜索結(jié)果中過濾出那些不匹配條件的數(shù)據(jù)爸黄,而不改變查詢結(jié)果的順序滞伟。

在bool 查詢的must 從句中,過濾和相同查詢之間的不同之處在于炕贵,過濾不會影響搜索分?jǐn)?shù)梆奈,而must 查詢會。當(dāng)查詢結(jié)果返回并且用戶使用給定的條件過濾時称开,他們不想改變結(jié)果的順序亩钟,相反地,他們只想從結(jié)果中移除不相關(guān)的數(shù)據(jù)鳖轰。過濾與搜索的格式一樣清酥,但在通常情況下,他們在有明確值的字段上定義蕴侣,而不是文本字符串上焰轻。Elasticsearch 推薦通過bool復(fù)合查詢的filter從句添加過濾。

繼續(xù)看上面的例子睛蛛,假設(shè)我們想把搜索結(jié)果限制在在2011到2015年之間發(fā)布的文章里鹦马。這樣做胧谈,我們只需要在一般搜索查詢的filter 部分添加range 查詢忆肾。這將會從結(jié)果中移除那些不匹配的數(shù)據(jù)。下面是一個過濾查詢的例子(源碼)菱肖。

// filter.js

{
  bool: {
    must: [
      {
        match: {
          title: 'search terms go here'
        }
      }
    ],
    filter: [
      {
        range: {
          year: {
            gte: 2011,
            lte: 2015
          }
        }
      }
    ]
  }
}

聚合

聚合框架會基于一次搜索查詢客冈,提供各種聚合數(shù)據(jù)和統(tǒng)計信息。兩個主要的聚合類型是度量和分塊, 度量聚合會對一個文檔的集合進(jìn)行持續(xù)的跟蹤并計算度量稳强,而分塊聚合則會進(jìn)行塊的構(gòu)建场仲,每個塊都會跟一個鍵和一個文檔查詢條件關(guān)聯(lián)起來和悦。度量聚合的示例有平均值,最小值渠缕,最大值鸽素,加總值還有計數(shù)值。分塊聚合的示例有范圍亦鳞、日期范圍馍忽、直方圖以及主題項(xiàng)。對聚合器更加深入的描述可以在 這里 找到燕差。

聚合可以放置在一個 aggregations 對象里面遭笋,而對象自己則是被直接放到 search 對象體中。在 aggregations 對象里面徒探,每一個鍵都是由用戶賦予一個聚合器的名稱瓦呼。聚合器的類型和其它選項(xiàng)都應(yīng)該是作為這個鍵的值而放置的。接下來我們要來看看兩個不同類型的聚合器测暗,一個是度量的央串,一個塊的。我們會用度量聚合器來嘗試找出數(shù)據(jù)集合中最小的年份值(也就是最久遠(yuǎn)的文章)碗啄,而使用塊集合器我要做的就是嘗試找出每一個關(guān)鍵詞各自出現(xiàn)了多少次蹋辅。(源代碼鏈接)

// aggregations.js{
  aggregations: {
    min_year: {
      min: {field: 'year'}
    },
    keywords: {
      terms: {field: 'keywords'}
    }
  }}

在上述示例中,我們將度量聚合器命名為 min_year (也可以是其它名稱), 也就是 year 這個域上的 min 類型挫掏。塊聚合器責(zé)備命名為 keywords, 就是 keywords 這個域上的 terms 類型侦另。聚合操作的結(jié)果被裝在了響應(yīng)消息里的 aggregations 元素里面,更深入一點(diǎn)會發(fā)現(xiàn)里面包含了每一個聚合器(這里是 min_year 和 keywords)以及它們的聚合操作結(jié)果尉共。 如下是來自這個示例響應(yīng)消息中的部分內(nèi)容褒傅。

{
...
  "aggregations": {
    "keywords": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 2452,
      "buckets": [
        {
          "key": "pariatur",
          "doc_count": 88
        },
        {
          "key": "adipisicing",
          "doc_count": 75
        },
        ...
      ]
    },
    "min_year": {
      "value": 1970
    }
  }
}

響應(yīng)消息中默認(rèn)最多會有10個塊返回。你可以在請求中 filed 的邊上加入一個size鍵來規(guī)定返回的塊的最大數(shù)量袄友。如果你想要接收到所有的塊殿托,可以將這個值設(shè)置為 0。

建議

Elasticsearch 提供了多種可以對輸入內(nèi)容提供替換和補(bǔ)全的關(guān)聯(lián)項(xiàng)推薦器(見文檔)剧蚣。下面將介紹術(shù)語和短語推薦器支竹。術(shù)語推薦器為每個輸入文本中的術(shù)語提供關(guān)聯(lián)推薦(如果有的話),而短語推薦器將整個輸入文本看做一個短語(與將其拆分成術(shù)語對比)鸠按,然后提供其他短語的推薦(如果有的話)礼搁。使用推薦API時,需要調(diào)用Node.js client的suggest方法目尖。如下為術(shù)語推薦器的示例馒吴。(見源碼

// suggest_term.js

esClient.suggest({
  index: 'articles',
  body: {
    text: 'text goes here',
    titleSuggester: {
      term: {
        field: 'title',
        size: 5
      }
    }
  }
}).then(...)

與其他client的方法相同,在請求體中包含一個index字段指明采用的索引。在body字段中添加查詢推薦的文本饮戳,然后給每個推薦器一個(包含了聚合對象的)名稱(本例中的titleSuggester)豪治。其值指明了推薦器的類型和配置。這里扯罐,為title字段使用了術(shù)語推薦器负拟,限制最大建議的數(shù)量是每個token最多5個(size: 5)。

建議API返回的數(shù)據(jù)中包含了對應(yīng)請求中每一個建議器的key歹河,其值是一個與你輸入文本中術(shù)語數(shù)量相同的一個數(shù)組齿椅。對于數(shù)組中的每一個元素,包含一個options數(shù)組启泣,其每個對象的text字段中包含了推薦的文本涣脚。如下是上面例子中返回數(shù)據(jù)的一部分。

"titleSuggester": [
  {
    "text": "term",
    "offset": 0,
    "length": 4,
    "options": [
      {
        "text": "terms",
        "score": 0.75,
        "freq": 120
      },
      {
        "text": "team",
        "score": 0.5,
        "freq": 151
      }
    ]
  },
  ...
]

獲取短語推薦的時候寥茫,采用與上文相同的格式并替換推薦器的類型字段即可遣蚀。如下的例子中,返回數(shù)據(jù)將與上例格式相同纱耻。(見源碼

// suggest_phrase.js

esClient.suggest({
  index: 'articles',
  body: {
    text: 'phrase goes here',
    bodySuggester: {
      phrase: {
        field: 'body'
      }
    }
  }
}).then(...).catch(...)

進(jìn)一步閱讀

Elasticsearch 提供了許多特性芭梯,這些特性遠(yuǎn)遠(yuǎn)超出了這一篇文章所能討論的范圍。在這篇文章中弄喘,我試圖站在一個很高的層次上來解釋它的特性玖喘,并為你提供可用來進(jìn)一步學(xué)習(xí)的合適資源。Elasticsearch是非衬⒅荆可靠的累奈,并且有著出色的表現(xiàn)(我希望你在運(yùn)行范例時已經(jīng)注意到了這一點(diǎn))。再加之不斷增長的社區(qū)支持急但,使得Elasticsearch在工業(yè)中的應(yīng)用也在不斷增加澎媒,尤其是對于需要處理實(shí)時數(shù)據(jù)或大數(shù)據(jù)的公司。

學(xué)習(xí)完這里提供的例子之后波桩,我強(qiáng)烈建議你閱讀一些相關(guān)文檔戒努。文檔有兩個主要來源,一是Elasticsearch參考及其特性镐躲,另外還有一個指南储玫,它更多關(guān)注的是具體實(shí)現(xiàn),使用案例以及最佳實(shí)踐萤皂。你也可以在這里找到Node.js客戶端的詳細(xì)文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撒穷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子敌蚜,更是在濱河造成了極大的恐慌桥滨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弛车,死亡現(xiàn)場離奇詭異齐媒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)纷跛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門喻括,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贫奠,你說我怎么就攤上這事唬血。” “怎么了唤崭?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵拷恨,是天一觀的道長。 經(jīng)常有香客問我谢肾,道長腕侄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任芦疏,我火速辦了婚禮冕杠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酸茴。我一直安慰自己分预,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布薪捍。 她就那樣靜靜地躺著笼痹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酪穿。 梳的紋絲不亂的頭發(fā)上与倡,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機(jī)與錄音昆稿,去河邊找鬼纺座。 笑死,一個胖子當(dāng)著我的面吹牛溉潭,可吹牛的內(nèi)容都是我干的净响。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼喳瓣,長吁一口氣:“原來是場噩夢啊……” “哼馋贤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起畏陕,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤配乓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犹芹,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崎页,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腰埂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片飒焦。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屿笼,靈堂內(nèi)的尸體忽然破棺而出牺荠,到底是詐尸還是另有隱情,我是刑警寧澤驴一,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布休雌,位于F島的核電站,受9級特大地震影響肝断,放射性物質(zhì)發(fā)生泄漏杈曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一孝情、第九天 我趴在偏房一處隱蔽的房頂上張望鱼蝉。 院中可真熱鬧,春花似錦箫荡、人聲如沸魁亦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洁奈。三九已至,卻和暖如春绞灼,著一層夾襖步出監(jiān)牢的瞬間利术,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工低矮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留印叁,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓军掂,卻偏偏與公主長得像轮蜕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝗锥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,947評論 2 89
  • 博客原文一博客原文二 翻譯作品跃洛,水平有限,如有錯誤终议,煩請留言指正汇竭。原文請見 官網(wǎng)英文文檔 起步 Elasticse...
    rabbitGYK閱讀 3,261評論 0 68
  • 短促人生轉(zhuǎn)眼間, 貧窮富貴總歡顏找颓。 恩恩怨怨紅塵戲合愈, 慢步娑婆若等閑叮贩。 文/農(nóng)夫
    農(nóng)夫_bacf閱讀 814評論 44 33
  • 故事結(jié)束時击狮,他還不滿七歲。從那以后他開始上學(xué)益老,把所有念頭放在課本上彪蓬。這樣做并不是為了成績,那時在他的世界里還沒有成...
    張不退閱讀 301評論 0 0
  • 不知到哪兒學(xué)到的捺萌,崽崽經(jīng)常用手附著我耳朵悄悄地說档冬,媽媽,我告訴你一個秘密桃纯,請你不要告訴別人酷誓,好嗎?通常他的秘...
    春天里的紫蘇閱讀 279評論 0 0