實戰:Nodejs+Mongodb+Elasticsearch 實現簡單的搜尋

天府雲創發表於2017-07-06
在網站建立初期,我們提供的搜尋服務很多都是基於資料庫的模糊搜尋,在效能和可用性上多少會有所缺失,所以在網站發展壯大後,就不得不增強搜尋功能。elasticsearch 的基本功能就已經足夠一般的搜尋需求。本文將介紹,如何使用 nodejs + mongodb + es 實現一個簡單而強大的全文搜尋功能,以提高網站搜尋體驗。


基本架構圖


  1. 資料儲存在 mongodb

  2. 使用 elasticsearch 作為搜尋資料庫

  3. nodejs 用官方客戶端 elasticsearch-js 請求 elasticsearch

  4. 使用 mongo-connector 同步 mongodb 的資料到 elasticsearch

  5. 前端隨便請求一下


elasticsearch

本文使用的是 elasticsearch-rtf,這個庫使用的是 elasticsearch 5.1 版本,並且預裝了一些常用外掛,比如常用分詞器。

在安裝完 java 環境後,就可以直接執行 elasticsearch 目錄裡的 bin/elasticsearh。不過在虛擬機器中,會有各種像記憶體不足,檔案描述符限制等問題。所以在虛擬機器中,我們需要修改一些配置:

  1. 修改 config/jvm.options 裡的 jvm 記憶體為 512m

    # Xms represents the initial size of total heap space
    # Xmx represents the maximum size of total heap space
    
    #-Xms2g
    #-Xmx2g
    
    -Xms512m
    -Xmx512m
  2. 修改檔案描述符上限,需要 root 許可權,然後執行

    ulimit -n 65536
  3. 為了讓別的機器能連上 elasitcsearch 機器,需要把 config/elasticsearch.yml 裡的 network.host 修改為 0.0.0.0。另外,還需要執行如下命令

    sysctl -w vm.max_map_count=262144

    不然會報 vm.max_map_count 不足的錯誤。


mongodb

安裝就好,沒有坑


mongo-connector

mongo-connector 是一個 python 編寫的,用來複制 mongodb 中資料到各種搜尋資料庫的工具,支援 elasticsearch。

使用這個庫前,要先在 mongodb 中設定 replSet,具體可以檢視這裡。

另外,在執行中需要用到 elastic_doc_manager 這個庫,需要另外安裝,在他的 README 裡沒有明確指明需要另外安裝,具體可以檢視這裡。

最後,執行:

mongo-connector -m localhost:27017 -t localhost:9200 -d elastic_doc_manager

mongo-connector 會把 mongodb 裡的資料同步到 elasticsearch。


測試資料

測試資料使用 Faker-zh-cn.js 生成。這個庫是 faker.js 的中文版。程式碼如下:

const { MongoClient } = require('mongodb')
const faker = require('faker-zh-cn')
const DB_URL = 'mongodb://192.168.134.125:27017/test'

faker.locale = 'zh_CN'

const insertData = function(db, callback) {

  let collection = db.collection('data')

  let data = []

  for (let i = 0; i <= 1000; i++) {
    data.push({
      "name": faker.Name.findName(),
      "address": `${faker.Address.city()},${faker.Address.streetName()},${faker.Address.streetAddress()}`,
      "description": faker.Lorem.paragraph()
    })
  }

  collection.insert(data, function(err, result) {
    if(err) {
      console.log('Error:'+ err)
      return
    }
    console.log('success')
    callback && callback(result)
  })
}

MongoClient.connect(DB_URL, function (err, db) {
  if (err) {
    return console.log(err)
  }
  console.log('連線成功')
  insertData(db)
})


替換分詞器

生成測試資料後,使用 elasticsearch 的 rest api 測試搜尋,發現搜尋結果並不理想。比如使用查詢條件 q=王者農藥,出來的結果除了王者、農藥相關的結果,連包含王、者、農或藥的結果都出來了,說明搜尋預設對中文以字為單位分詞,我們需要替換分詞器。

elasticsearch-analysis-ik 是一個流行的中文分詞庫,在 elasticsearch-rtf 中已經整合,可直接使用。

更換分詞器需要對 elasticsearch 的對映重定義。使用 elasticsearch 的 rest api 進行設定,會報錯,大概意思就是已經存在資料的索引的對映不能被修改,所以要另闢蹊徑。

後來查到,只需要根據舊的索引和型別,重新建立一個索引,然後把想要修改的索引,reindex 到新索引即可。新索引的對映帶上了 analyzer: "ik_smart" 的宣告,就可以用上這個分詞器了。

最後,搜尋結果變得正常了一點,只會出現王者和農藥相關的結果了,說明分詞器起效了。


nodejs 程式碼

用的 express 隨便意思一下

const { Client } = require('elasticsearch')
const INDEX = 'index'
const express = require('express')
const app = express()

let client = new Client({
  host: '192.168.134.125:9200',
  log: 'trace'
})

app.use(express.static(__dirname + '/public'))

app.get('/', (req, res) => {
  res.send('Hello World')
})

app.get('/search/:keyword?', (req, res) => {
  client.search({
    q: req.params.keyword,
    index: INDEX,
    size: 999
  }).then(function (body) {
    var hits = body.hits.hits
    res.send(hits)
  }, function (error) {
    console.trace(error.message)
  })
})

console.log('listening port: 3000')
app.listen(3000)


結果

前端就是一個簡單的頁面,呼叫 nodejs 的介面,看起來是這樣的


最後

本人才疏學淺,如有紕漏,還望指正。


參考 :

https://segmentfault.com/a/1190000003773614

相關文章