一直以來,爲了優化本博客站內搜索效果和速度,我使用 bing 的 site: 站內搜索作爲數據源,在服務端獲取、解析、處理並緩存搜索結果,直接輸出 HTML。這個方案惟一的問題是時效性難以保證,儘管我能夠在發佈和修改文章時主動告訴 bing,但它何時更新索引則徹底不受我控制。javascript
本着不折騰就渾身不自在的原則,我最終仍是使用 Elasticsearch 搭建了本身的搜索服務。Elasticsearch 是一個基於 Lucene 構建的開源、分佈式、RESTful 搜索引擎,不少大公司都在用,程序員的好夥伴 Github 的搜索也用的是它。本文記錄我使用 Elasticsearch 搭建站內搜索的過程,目前支持中文分詞、同義詞、標題匹配優先等常見策略,能夠點擊這裏體驗。html
部署 Elasticsearch 最簡單的方法是使用 Elasticsearch Dockerfile 。爲了更完全地折騰,我沒有使用 Docker,好在手動安裝過程也不復雜。java
個人虛擬機和線上環境都是 Ubuntu 14.04.3 LTS,Elasticsearch 用的是目前最新的 2.1.1。一切開始以前,先要檢查機器上是否裝有 java 環境,若是沒有能夠經過如下命令安裝:git
sudo apt-get install openjdk-7-jre-headless
下載 Elasticsearch 2.1.1 壓縮包並解壓:程序員
wget -c https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/zip/elasticsearch/2.1.1/elasticsearch-2.1.1.zip unzip elasticsearch-2.1.1.zip
我將解壓獲得的 elasticsearch-2.1.1 目錄重命名爲 ~/es_root (名稱及位置沒有限制,能夠將它挪到你認爲合適的任何位置)。Elasticsearch 無需安裝,直接能夠運行:github
cd ~/es_root/bin/ chmod a+x elasticsearch ./elasticsearch
若是屏幕上沒有打印錯誤信息,說明 Elasticsearch 服務已經成功啓動。新建一個終端,用 curl 驗證下:docker
curl -XGET http://127.0.0.1:9200/?pretty { "name" : "Goblyn", "cluster_name" : "elasticsearch", "version" : { "number" : "2.1.1", "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71", "build_timestamp" : "2015-12-15T13:05:55Z", "build_snapshot" : false, "lucene_version" : "5.3.1" }, "tagline" : "You Know, for Search" }
若是看到以上信息,說明一切正常,不然請根據屏幕上的錯誤信息查找緣由。儘管 Elasticsearch 自己是用 java 寫的,但它對外能夠經過 RESTful 接口交互,十分方便。npm
默認狀況下 Elasticsearch 的 RESTful 服務只有本機才能訪問,也就是說沒法從主機訪問虛擬機中的服務。爲了方便調試,能夠修改 ~/es_root/config/elasticsearch.yml 文件,加入如下兩行:api
network.bind_host: "0.0.0.0" network.publish_host: _non_loopback:ipv4_
但線上環境切忌不要這樣配置,不然任何人均可以經過這個接口修改你的數據。promise
Elasticsearch 自帶的分詞器會粗暴地把每一個漢字直接分開,沒有根據詞庫來分詞。爲了處理中文搜索,還須要安裝中文分詞插件。我使用的是 elasticsearch-analysis-ik ,支持自定義詞庫。
首先,下載與 Elasticsearch 2.1.1 匹配的 elasticsearch-analysis-ik 插件。根據文檔,當前須要使用 master 版:
wget -c https://github.com/medcl/elasticsearch-analysis-ik/archive/master.zip unzip master.zip
解壓後,進入插件源碼目錄編譯:
sudo apt-get install maven cd elasticsearch-analysis-ik-master/ mvn package
若是一切順利,在 target/releases/ 目錄下能夠找到編好的文件。將其解壓並拷到 ~/es_root 對應目錄:
mkdir -p ~/es_root/plugins/ik/ unzip target/releases/elasticsearch-analysis-ik-1.6.2.zip -d ~/es_root/plugins/ik/
再將 elasticsearch-analysis-ik 的配置也拷貝到 ~/es_root 對應目錄:
mkdir -p ~/es_root/config/ik cp -r config/ik/* ~/es_root/config/ik/
elasticsearch-analysis-ik 的配置文件中不少都是詞表,直接用文本編輯器打開就能夠修改,改完記得保存爲 utf-8 格式。
如今再啓動 Elasticsearch 服務,若是看到相似下面這樣的信息,說明 IK Analysis 插件已經裝好了:
[plugins] [Libra] loaded [elasticsearch-analysis-ik]
Elasticsearch 自帶一個名爲 synonym 的同義詞 filter。爲了能讓 IK 和 synonym 同時工做,咱們須要定義新的 analyzer,用 IK 作 tokenizer,synonym 作 filter。聽上去很複雜,實際上要作的只是加一段配置。
打開 ~/es_root/config/elasticsearch.yml 文件,加入如下配置:
index: analysis: analyzer: ik_syno: type: custom tokenizer: ik_max_word filter: [my_synonym_filter] ik_syno_smart: type: custom tokenizer: ik_smart filter: [my_synonym_filter] filter: my_synonym_filter: type: synonym synonyms_path: analysis/synonym.txt
以上配置定義了 ik_syno 和 ik_syno_smart 這兩個新的 analyzer,分別對應 IK 的 ik_max_word 和 ik_smart 兩種分詞策略。根據 IK 的文檔,兩者區別以下:
ik_syno 和 ik_syno_smart 都會使用 synonym filter 實現同義詞轉換。爲了方便後續測試,建議建立 ~/es_root/config/analysis/synonym.txt 文件,輸入一些同義詞並存爲 utf-8 格式。例如:
ua,user-agent,userAgent js,javascript
經過前面的示例,咱們知道經過 curl 或者 Chrome 的 Postman 擴展能輕鬆地與 Elasticsearch 服務交互。爲了更好與已有系統集成,咱們還可使用 Elasticsearch Client。Elasticsearch Client 只是將 RESTful 接口包裝了一層,常見語言都有對應的實現( 查看官方 Client ),本身寫一套也不難。
個人博客系統是 Node.js 寫的,在項目裏直接 npm install elasticsearch --save 就能夠安裝 Elasticsearch 的 Node.js 包。
不管進行什麼操做,首先都須要實例化 Elasticsearch Client 對象:
var elasticsearch = require('elasticsearch'); var client = new elasticsearch.Client({ host: '10.211.55.23:9200', //服務 IP 和端口 log: 'trace' //輸出詳細的調試信息 });
而後就能夠調用 client 對象提供的各類方法了,client 對象擁有大量方法,請查看 官方文檔 。這個庫支持兩種調用方式:callback 和 promise:
//callback client.info({}, function(err, data) { if(!err) { console.log('result:', data); } else { console.log('error:', err); } }); //promise client.info({}).then(function(data) { console.log('result:', data); }, function(err) { console.log('error:', err); });
爲了節約篇幅,本文後續貼出的代碼都採用 promise 寫法,而且省略 then 函數。
到如今爲止,全部準備工做都已經完成,立刻就要大功告成了。在進行下一步以前,先簡單介紹一下 Elasticsearch 幾個名詞:
Elasticsearch 集羣能夠包含多個索引(Index),每一個索引能夠包含多個類型(Type),每一個類型能夠包含多個文檔(Document),每一個文檔能夠包含多個字段(Field)。如下是 MySQL 和 Elasticsearch 的術語類比圖,幫助理解:
MySQL | Elasticsearch |
---|---|
Database | Index |
Table | Type |
Row | Document |
Column | Field |
Schema | Mappping |
Index | Everything Indexed by default |
SQL | Query DSL |
就像使用 MySQL 必須指定 Database 同樣,要使用 Elasticsearch 首先須要建立 Index:
client.indices.create({index : 'test'});
這樣就建立了一個名爲 test 的 Index。Type 不用單首創建,在建立 Mapping 時指定就能夠。Mapping 用來定義 Document 中每一個字段的類型、所使用的 analyzer、是否索引等屬性,很是關鍵。建立 Mapping 的代碼示例以下:
client.indices.putMapping({ index : 'test', type : 'article', body : { article: { properties: { title: { type: 'string', term_vector: 'with_positions_offsets', analyzer: 'ik_syno', search_analyzer: 'ik_syno', }, content: { type: 'string', term_vector: 'with_positions_offsets', analyzer: 'ik_syno', search_analyzer: 'ik_syno', }, tags: { type: 'string', term_vector: 'no', analyzer: 'ik_syno', search_analyzer: 'ik_syno', }, slug: { type: 'string', term_vector: 'no', }, update_date: { type : 'date', term_vector: 'no', index : 'no', } } } } });
以上代碼爲 test 索引下的 article 類型指定了字段特徵: title 、 content 和 tags 字段使用 ik_syno 作爲 analyzer,說明它使用 ik_max_word 作爲分詞,而且應用 synonym 同義詞策略; slug 字段沒有指定 analyzer,說明它使用默認分詞;而 update_date 字段則不會被索引。
接着,寫入測試數據並索引:
client.index({ index : 'test', type : 'article', id : '100', body : { title : '什麼是 JS?', slug :'what-is-js', tags : ['JS', 'JavaScript', 'TEST'], content : 'JS 是 JavaScript 的縮寫!', update_date : '2015-12-15T13:05:55Z', } })
id 參數若是不指定,系統會自動生成一個並返回,後續在更新、刪除時都要用到它。至於如何更新、刪除,這裏就不寫了,請自行 查看文檔 。
搜一下試試:
client.search({ index : 'test', type : 'article', q : 'JS', }).then(function(data) { console.log('result:'); console.log(JSON.stringify(data)); }, function(err) { console.log('error:'); console.log(err); });
沒有問題,能夠搜出來!查詢結果數量和具體內容都在 hits 字段中:
result: {"took":50,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":1,"max_score":0.076713204,"hits":[{"_index":"test","_type":"article","_id":"100","_score":0.076713204,"_source":{"title":"什麼是 JS?","slug":"what-is-js","tags":["JS","JavaScript","TEST"],"content":"JS 是 JavaScript 的縮寫!","update_date":"2015-12-15T13:05:55Z"}}]}}
若是要實現更復雜的查詢策略該怎麼辦?那就要請出前面表格中與 SQL 對應的 Query DSL 了。例如如下是本博客站內搜索所使用的 Query DSL:
{ index : 'test', type : 'article', from : start, body : { query : { dis_max : { queries : [ { match : { title : { query : keyword, minimum_should_match : '50%', boost : 6, } } }, { match : { content : { query : keyword, minimum_should_match : '75%', boost : 4, } } }, { match : { tags : { query : keyword, minimum_should_match : '100%', boost : 2, } } }, { match : { slug : { query : keyword, minimum_should_match : '100%', boost : 1, } } } ], tie_breaker : 0.3 } }, highlight : { pre_tags : ['<b>'], post_tags : ['</b>'], fields : { title : {}, content : {}, } } } }
from 參數指定從開始跳過多少條結果,用來實現分頁。這份複雜的 Query DSL 搜出來的結果以下:
result: {"took":108,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":1,"max_score":0.29921508,"hits":[{"_index":"test","_type":"article","_id":"100","_score":0.29921508,"_source":{"title":"什麼是 JS?","slug":"what-is-js","tags":["JS","JavaScript","TEST"],"content":"JS 是 JavaScript 的縮寫!","update_date":"2015-12-15T13:05:55Z"},"highlight":{"content":["<b>JS</b> 是 <b>JavaScript</b> 的縮寫!"],"title":["什麼是 <b>JS</b>?"]}}]}}
能夠看到,同義詞策略和關鍵詞高亮功能都正常。跑通 Elasticsearch 基本流程,剩餘工做就是導入更多數據、配置更多詞表和嘗試不一樣策略了,略過不寫。
我接觸 Elasticsearch 一共才幾小時,個人出發點也很簡單,只是爲了給博客加上站內搜索,故本文既不全面也不深刻,甚至還包含各類錯誤,僅供參考。Elasticsearch 功能十分強大和複雜,遠遠不是花幾個小時就能玩明白的。最後推薦「 Elasticsearch 權威指南(中文版) 」這本書,很是細緻和全面,我對 Elasticsearch 僅有的一點了解都來自於這本書和官方文檔。
本文連接: https://imququ.com/post/elasticsearch.html , 參與評論 。
-- EOF --
http://www.open-open.com/lib/view/open1452046497511.html