使用Elasticsearch快速實現社群/部落格文章全文搜尋

龍心發表於2018-04-17

這是一篇結合實戰教同學快速入門使用Elasticsearch 解決實際業務場景的問題.

如果你還在用 sql like %xxx%的方式進行內容的全文搜尋,很可能DBA或者老大就要找你去聊天了, 以mysql innodb引擎為例, 這種寫法將會進行十分低效全文檢索,而且不會使用索引.

接下來全文將 elasticsearch 簡稱為ES.

那麼,本文將通過將之前在Flask框架上使用的社群搜尋介面從like %xxx%的寫法改成ES做全文索引,觀察是否會存在顯著的效能提升(如果不能提升,是否還有其他改進的方式),先看下從0-1怎麼快速實現.

看了其他的講實現的文章中都是把相關元件或者ES的文件程式碼直接貼過來,個人認為這些都是會存在升級以及文件變更的可能,為了使用最新的api以及特性還是建議讀者花幾分鐘讀完文章中的外鏈文件地址.

  • ES是什麼? 怎麼玩?
  • 地域問題,中文文章的分詞怎麼整?
  • 業務的實現
  • 線上環境的部署與監控(服務的穩定性)

##ES是什麼? 怎麼玩? ES簡單的中文入門,在上線之前需要現在本地以及測試環境跑的溜,筆者本機是MacOS系統,所以採用比較快捷的Homebrew的安裝方式
brew install elasticsearch 再根據文件安裝了瀏覽器級的control工具sense,按著教程稍微跑一下教程之後就可以進入了正題. 有些朋友想吐槽ES快速入門太簡單了,畢竟是實戰導向的快速入門,把重點放在ES與業務結合的實現.

##地域問題,中文文章的分詞怎麼整 如果筆者讀了上段寫的中文入門教程,會發現ES已經提供英文搜尋模糊匹配的功能,至於ES是怎麼實現的,感興趣的讀者可以再深入查閱資料.這時候為了支援中文搜尋我們需要安裝外掛到ES目錄中的plugins資料夾中$ES_HOME/plugins/ . ik外掛的github地址,按著文件安裝之後可以跑一下里面的測試請求,驗證中國的分詞查詢是有效的.ik外掛的在analyzer和search_analyzer上有兩種配置:

  • ik_max_word: 會將文字做最細粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,中華人民,中華,華人,人民共和國,人民,人,民,共和國,共和,和,國國,國歌”,會窮盡各種可能的組合;
  • ik_smart: 會做最粗粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,國歌”。

核心的分詞問題就這樣解決了,在大批量的文章中是否會存在效能問題需要在實戰中驗證,接下來我們要做的事情就是將之前有的文章匯入到ES中以及為新的文章建立文件,這些要做的事情都將通過呼叫ES提供的api完成.

##業務的實現 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,這邊我們為社群文章建立index: forum-index,以及type:post

ES提供了一套增刪改查的api,我們可以在SENSE模板中使用簡化API來測試驗證有效性,但是在後端開發中根據不同語言以及框架一般有已經封裝好的庫可以直接配置使用,筆者這邊使用的是python的庫elasticsearch-dsl-py,如果您使用的是其他語言或者框架,上github上搜尋一下有沒有封裝過的框架(記得要看是否版本匹配).在進行下一步之前,我們捋一下我們要做的事情,將原有的社群文章匯入type,根據提供給客戶端以及後臺的查詢功能加入對應匹配的欄位,新增文章時需要加入ES中,編輯文章後需要修改ES中對應文件的內容,以及文章下架後要將文章從ES中移除.增刪改這些額外的ES操作會有額外的時間消耗以及失敗的可能,為了不影響介面可以將操作封裝放入佇列非同步執行,保證最終一致執行便可.ES查詢效能與穩定性的優化等先調通基礎功能與介面後再考慮.

這是匯入原有帖子資料後在sense console上執行以下模糊查詢語句與對應的返回:

GET /forum-index/post/_search
{
   "query": {
    "bool": {
      "should": [
        { "match": { "content":  "客戶端" }},
         { "match": { "title":  "客戶端" }},
        { "match": { "summary": "客戶端"   }}
      ]
    }
  }
}
複製程式碼
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 19,
    "max_score": 25.630516,
    "hits": [
      {
        "_index": "forum-index",
        "_type": "post",
        "_id": "60000000012672",
        "_score": 25.630516,
        "_source": {
          "user_id": 10144,
          "title": "客戶端發帖測試",
          "summary": "客戶端發帖測試內容",
          "content": """
客戶端發帖測試內容
<img data-src="3f2b7dd51ddcb7d66b22f0f06645f16e.png" src="http://img-cn-hangzhou.aliyuncs.com/nb-imgs/3f2b7dd51ddcb7d66b22f0f06645f16e.png">
 <br/>
</img>
""",
          "last_modify": 1480665329,
          "id": 60000000012672
        }
      },
...
複製程式碼

返回json格式的資料中,took,_score,...等等很多欄位這個是什麼意思呢? 可以參考官文說明.這邊也簡單說明下:

  • took – time in milliseconds for Elasticsearch to execute the search
  • timed_out – tells us if the search timed out or not
  • _shards – tells us how many shards were searched, as well as a count of the successful/failed searched shards
  • hits – search results
  • hits.total – total number of documents matching our search criteria
  • hits.hits – actual array of search results (defaults to first 10 documents)
  • hits.sort - sort key for results (missing if sorting by score)
  • hits._score and max_score - ignore these fields for now

這邊可以看到,在本地環境5000+的帖子文件分詞查詢,消耗時間為4ms,算是一個非常不錯的結果了.將複雜的查詢功能使用ES來實現,可以有效減少資料庫的請求壓力,接下來我們可以致力於優化ES的使用以及查詢.按目前評估ES可以勝任生產級別的全文檢索服務.

線上環境的部署與監控(服務的穩定性)

由於工作呼叫的原因筆者很遺憾的沒有去實現完成最後這一步,但是可以大致說一下思路,線上部署配置後,可以使用supervisor等服務守護程式去保證服務的穩定性,並做好效能壓測,為保證服務的持續可用建議部署上ES的叢集.單個節點異常的時候也能保證線上服務的持續.

##總結 通過這次的學習發現ES是個強大好用的工具,目前比較流行的有用ELK進行日誌分析(ElasticSearch + Logstash + Kibana),這邊僅僅是為讀者們開啟了冰山的一角,繼續學習,與君共勉!

相關文章