深入搜尋引擎之 Elasticsearch 必知必會(一):開發視角

語言: CN / TW / HK

兩句話瞭解它是什麼

1. 搜尋引擎。提供了資料儲存、資料處理、資料查詢、聚合統計的能力。

2. 創始人說:“不要求你必須是一個數據科學家才能把它用好”

前言

Elasticsearch 是一個很有意思的產品,不同崗位的人,對它的關注維度區別比較大

主要可以分三個層面

  • 開發

    • 基本功能

    • 底層工作原理

    • 資料建模最佳實踐

  • 運維

    • 容量規劃

    • 效能優化

    • 問題診斷

    • 滾動升級

  • 搜尋結果優化

    • 查全率、查準率等指標

    • 搜尋與如何解決搜尋的相似性問題

    • 具體場景下的調優

對比傳統資料庫的區別主要在於

傳統關係型資料庫

  • 事務性

  • Join

  • 精確匹配

Elasticsearch

  • Schemaless

  • 相關性

  • 高效能全文搜尋

本文是系列第一篇,會大體講述 Elasticsearch 的開發視角下的必知必會內容,從儲存層設計,索引機制,分詞查詢的基本原理、到分散式架構設計,做一個整體梳理;後續會繼續運維關注的部署、災備等,以及查詢結果優化方面的兩塊內容梳理

起源

Elasticsearch

Elasticsearch是一個基於Lucene庫的搜尋引擎。它提供了一個分散式、支援多租戶的全文搜尋引擎,具有HTTP Web介面和無模式JSON文件。Elasticsearch是用Java開發的,並在Apache許可證下作為開源軟體釋出。官方客戶端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和許多其他語言中都是可用的。[5]根據DB-Engines的排名顯示,Elasticsearch是最受歡迎的企業搜尋引擎,其次是Apache Solr,也是基於Lucene。[維基百科]

Lucene

Lucene是一套用於全文檢索和搜尋的開放原始碼程式庫,由Apache軟體基金會支援和提供。Lucene提供了一個簡單卻強大的應用程式介面,能夠做全文索引和搜尋。Lucene是現在最受歡迎的免費Java資訊檢索程式庫。[維基百科]

Lucene 是一個基於 Java 語言開發的搜尋引擎類庫,作者是 Doug Cutting - Wikipedia 同時也是 Hadoop 之父,

Lucent 有一些侷限性如

  • 只能基於 Java 開發

  • 學習曲線陡峭

  • 不支援水平擴充套件

於是在 Lucene 的基礎上,誕生了 Elasticsearch

  • 支援分散式,可水平擴充套件

  • 降低全文檢索的學習曲線,可以被任何程式語言呼叫

Elastic Stack 生態

基本概念

作為一個數據庫,先來看看 ES 的儲存設計,有以下幾個概念:

  • Index,是文件的容器,是一類文件的集合

  • Type,現在已經已經廢棄這一層,7.0 開始每個索引只有一個 Type 且名字固定為 _doc

  • Document 文件(類比關係資料庫的記錄行)

不嚴謹地類比關係型資料庫的概念如下圖

RDBMS Elasticsearch
Table Index(Type)
Row Document
Column Field
Schema Mapping
SQL Query DSL

索引(Index)

Mapping,定義文件的 Schema,有兩種定義方式

  • Dynamic Mapping,動態化建立,即 ES 可以根據插入的文件資料自動推斷欄位型別

  • 顯式定義 Mapping

為了減少工作量和出錯的次數,可以給出顯式定義的一般建議操作流程

  • 建立一個臨時的 Index,寫入樣本資料

  • 通過 Mapping API 檢視該 Index 動態生成的 Mapping

  • 修改後,使用該 Mapping 建立真正的 Index

  • 刪除臨時 Index

ES 支援的 欄位型別 也很豐富,比較重要的,區別於一般資料庫的兩類如下

  • keyword,精確值,整體作為一個片語(Term)來儲存和被查詢

  • text,全文字,會做分詞操作

一些常見的 Mapping 欄位屬性

  • index:控制欄位是否需要索引

  • index_options:四種級別,可以控制倒排索引記錄的內容粒度

    • docs,記錄文件 ID(非 text 型別欄位預設)

    • freqs,記錄文件 ID、詞頻

    • positioins,記錄文件 ID、詞頻、位置(text 型別欄位預設)

    • offsets,記錄文件 ID、詞頻、位置、偏移量

能否修改欄位型別呢?分兩種情況

  1. 新增欄位

    1. Dynamic=true 時,有新增欄位的文件寫入,Mapping 會被修改

    2. Dynamic=false 時,有新增欄位的文件寫入,Mapping 不會被修改;新增欄位的文件無法被索引,但是資料還是會出現在 _source 中

    3. Dynamic=strict 時,文件寫入失敗

  2. 已存在欄位,一旦已經有該欄位的資料寫入,就不再允許修改 Mapping

    1. Lucene 實現的倒排索引,一旦生成就不能再被修改

注意:索引一旦建立,如果希望改變欄位型別,就必須 ReIndex

除定義資料結構的 Mapping 外,Index 還有另一個重要的熟悉 Setting,使用者定義資料的分散式屬性,如:分片數、副本數

索引的不同語義 名詞:一個 Elasticsearch 叢集中的許多索引 動詞:把一個文件儲存到 Elasticsearch 的過程(indexing),主要就是建立一個倒排索引的過程

文件(Document)

Elasticsearch 是面向文件的,文件是所有可搜尋資料的最小單位

  • 日誌中的日誌項

  • 一本書、歌曲的資訊

文件會被序列化為 JSON 儲存

  • JSON 物件由欄位組成

  • 每個欄位都有欄位型別(字串、數值、布林、日期、二進位制、範圍型別)

  • 資料型別可以自動推算,也可以前期指定 Mapping

每個文件都有一個 Unique ID,欄位名為 _id

  • 自動生成

  • 自行指定

文件的一部分元資料(基本欄位)列舉

  • _index:文件所屬的索引名

  • _type:文件所屬的型別名

  • _id:文件唯一I

  • _source:文件的原始 JSON 資料

  • _all:整合所有欄位內容到該欄位,已經廢棄

  • _version:文件版本

  • _score:相關性打分

  • _seq_no:shard 級別單調遞增,保證後寫入的文件的序號大於先寫入的文件的序號

ES 的分散式基本組成

首先為什麼需要做分散式?

相比單機的 Lucene,分散式架構的好處主要有

  • 可擴充套件,方便水平擴容,無論是資料維度還是計算資源維度

  • 高可用性,部分節點掛了,叢集還能提供服務

節點(Node)

節點就是一個 ES 的例項,本質上就是一個 Java 程序。一臺機器上可以執行多個 ES 程序,單數生產環境一般建議一臺機器上只執行一個 ES 例項

每個節點都有一個名字,也是一樣可以通過配置檔案或命令列引數指定

每個節點在啟動後,會分配一個 UID,儲存在 data 目錄下

常見節點型別

  • Master Node & Master eligible Node 每個節點啟動後,預設就是一個 Master eligible 節點(Raft 的 Follower),參與選主流程,成為 Master 節點(Raft 的 Leader);當第一個節點啟動的時候,它會將自己選舉成為 Master 節點

    每個節點上都儲存了叢集狀態,但只有 Master 節點才能修改叢集狀態資訊(如建立索引、決定分片分佈等)

    叢集狀態(Cluster State),維護了一個叢集中必要的資訊

    • 所有的節點資訊

    • 所有的索引和其相關的 Mapping 與 Setting 資訊

    • 分片的路由資訊

  • Data Node & Coordinating Node

    Data Node:可以儲存資料的節點,負責儲存分片資料。在資料擴充套件上起到至關重要的作用

    Coordinating Node:負責接受 Client 的請求,將請求分發到合適的節點,最終把結果彙集到一起;每個節點預設都是一個 Coordinating Node

  • Hot & Warm Node 不同硬體配置的 Data Node,用來實現 Hot & Warm 架構,節省部署成本

  • Machine Learning Node 負責跑機器學習的 Job

  • Tribe Node(5.3 開始使用 Cross Cluster Search) 連線不同的 ES 叢集,支援將多個叢集當成一個叢集來使用

配置節點型別的建議:

- 開發環境一個節點可以承擔多種角色

- 生產環境中,應該設定單一角色(dedicated node)

分片(Shard)

主分片(Primary Shard),解決資料水平擴充套件問題

  • 一個執行 Lucene 的例項

  • 主分片數在索引建立時指定,後續不允許修改,除非 Reindex

副本分片(Replica Shard),解決資料高可用問題

  • 是主分片的備份

  • 增加副本數,還可以在一定程度上提高服務的可用性(讀取的吞吐)

分片的設定,容量規劃考慮因素

  • 分片數設定過小

    • 後續增加節點無法實現水平擴充套件

    • 單個分片資料量太大,實現資料重新分配耗時

  • 分配數設定過大,7.0 開始,預設主分片為 1,解決了 over-sharding 的問題

    • 影響搜尋結果的相關性打分,影響統計結果的準確性

    • 單個節點上過多的分片,會導致資源浪費,同時也會影響效能

叢集(分片)的健康情況

  • Green:主分片和副本分片都正常分配

  • Yellow:主分片全部正常分配,有副本分片未能正常分配

  • Red:有主分片未能分配

文件(Document)

文件需要能均勻地分佈在所有分片上,充分利用硬體資源,避免部分機器繁忙,部分機器空閒

比較常見的路由演算法

  • 隨機、Round Robin,查詢效率是問題

  • 維護文件到分片的對映關係,當資料量大的時候對映關係維護成本也大

  • 根據規則實時計算,hash

ES 實際使用的路由演算法: shard = hash(_routing) % number_of_primary_shards

routing 預設是文件 ID,也可以自行指定雜湊欄位

這個路由規則,也正是當 Index 建立後,主分片數,不能隨意修改的根本原因

分片的內部原理

思考一些問題

在進一步瞭解分片的內部原理前,先思考一些問題:

  • 為什麼 ES 的搜尋是近實時的(常說 1 秒後才能搜到)?

  • 如何保證資料斷電不丟失?

  • 為什麼刪除文件,不會立即釋放空間?

倒排索引不可變性

首先,需要了解 Lucene 的倒排索引採用不可變設計(Immutable Design),一旦生成,不可更改

不可變帶來的好處如下

  • 無需處理併發寫檔案問題,避免了鎖帶來的效能損耗

  • 一旦讀入核心的檔案系統快取,就留在那裡,只要檔案系統有足夠的空間,大部分請求就會直接請求記憶體,而不是訪問磁碟,提升效能

  • 快取容易生成和維護,資料可以被壓縮

但不可變性帶來的問題是,如果要讓一個新的文件可以被搜尋,需要重建整個索引

Lucene Index

在 Lucene 中,單個的倒排索引被稱為 Segment。多個 Segment 彙總在一起,就被稱為 Lucene 的 Index,也就是對應 ES 中的 Shard

Lucene Index 中有一個 Commit Point ,記錄了所有 Segment 的資訊,如果有新文件插入,則會生成新的 Segment;查詢時會同時查詢所有的 Segments,並對結果彙總

另一個檔案 .del ,記錄了刪除的文件資訊;搜尋的結果還會根據該檔案中的內容,對結果進行過濾

資料入庫過程(Indexing)

每當有新 Document 要寫入時

  1. 文件會先寫入 Index Buffer,作為緩衝區

  2. 將 Index Buffer 寫入 Segment(記憶體),然後文件就可以被查詢到了

  3. 將 Index Buffer 同時寫入 Transcation Log(WAL 機制),用於斷電恢復資料

  4. 將 Segments 落盤

其中第 2、3 兩步合併成為 Refresh,預設 1 秒( index.refresh_interval )執行一次,這也就是為什麼 ES 是近實時搜尋引擎的原因(高版本的 ES 預設落盤);另外 Index Buffer 被寫滿時也會觸發,預設大小是 JVM 記憶體的 10%

其中第 4 步,其實是歸屬於 Flush 操作的一個步驟。Flush 預設 30 分鐘執行一次,或者 Transcation Log 滿(預設 512MB)也會觸發。Flush 執行包含的流程有

  1. 呼叫 Refresh

  2. 呼叫 fsync,將 Segements 落盤

  3. 清空 Transcation Log

但這裡有個問題,按這個機制,在文件寫入文件的情況下,預設每秒會產生至少一個 Segment,時間久了之後就會產生非常多的 Segment,此時需要進行 Segment 的合併操作,即 Merge 操作,該操作除了會合並多個 Segement 外,還會根據 .del 檔案的內容,在合併的過程中實際刪除已經標記刪除的文件

ES 會自動執行 Merge 操作,如果需要人為觸發,可以執行 POST index_name/_forcemerge

ES 的查詢語言 Query DSL

ES 提供的查詢介面是 HTTP 協議的,通用性很強,方便使用,主要有兩種方式

  • URI Search:URL 中直接填寫查詢引數,簡單,適合簡單場景

  • Request Body Search:功能完整,ES 提供的 JSON 格式的一套 DSL

// 幾種型別,可以選定不同的 index 範圍
/_search
/index1/_search
/index1,index2/_search
/index*/_search

// 怎麼填查詢引數?查 name=John
/_search?q=name:John

// Request Body 的方式
curl -XPOST ""
-H "Content-Type: application/json" -d
{
	"query": {
		"match_all": {}
	}
}

URL Search

URL 中直接填寫查詢引數

q:指定查詢語句 df:預設欄位,不指定時對所有欄位進行查詢 sort 排序,from、size 用於分頁 profile 可以檢視語句執行計劃

Request Body

在 Request Body 裡面寫 JSON 格式的查詢語句,語法是 ES 自己的一套 DSL,支援功能完整,專案中主要還是使用這種方式

{
  "script_fields": {
    "new_field": {
      "script": {
        "lang": "painless",

"source": "doc['order_date'].value+'_hello'" }

}
},
"query": {
"match_all": {}
}
}

聚合搜尋(Aggregation)

ES 除簡單搜尋外,還提供資料的聚合統計功能

  • Bucket aggregations,一些列滿足特定條件的文件的集合

    • 分桶(男女)

    • 巢狀(中國包含廣東包含深圳)

  • Metrics aggregations,一些數學運算,可以對文件欄位進行統計分析;除了支援在欄位上計算,也支援通過指令碼(painless script)產生的結果上進行計算

    • min

    • max

    • avg

  • Pipeline aggregations,對其它的聚合結果進行二次聚合

  • Matrix aggregations,支援對多個欄位的操作並提供一個結果矩陣

正排索引和倒排索引

文件 ID 文件內容
1 Mastering Elasticsearch
2 Elasticsearch Server
3 Elasticsearch Essentials
Term Count DocumentID:Position
Elasticsearch 3 1:1, 2:0, 3:0
Mastering 1 1:0
Server 1 2:1
Essentials 1 3:1

倒排索引的核心組成

  • 單詞詞典(Term Dictionary),記錄所有的單詞,記錄單詞到倒排列表的關聯關係(一般都比較大,常見實現演算法見下圖)

  • 倒排列表(Posting List),記錄了單詞對應的文件集合,由倒排索引項組成

    • 文件 ID

    • 詞頻 TF - 該單詞在文件中出現的次數,用於相關性打分

    • 位置(Position) - 單詞在文件中分詞的位置,用於語句搜尋(phrase query)

    • 偏移(Offset) - 記錄單詞的開始結束位置,用於實現高亮顯示

    • 倒排索引項(Posting)

資料結構 優缺點
排序列表 Array/List 二分法查詢,不平衡
HashMap/TreeMap 高效能,記憶體消耗大
Skip List 跳錶,可快速查詢詞語,在 Lucene、Redis、Hbase 等裡都有實現
Trie 字首樹,適合英文詞典,如果系統中存在大量字串且基本沒有公共字首,則相應的 Trie 樹將非常消耗記憶體
Double Array Trie 適合做中文詞典,記憶體佔用小,很多分詞工具都採用
Termary Search Tree 三叉樹,每個 Node 有 3 個節點,兼具查詢快和節省記憶體的優點
Finite State Transducers(FST) 有限狀態轉移機,Lucene 4 有開源實現,並大量使用

ES 的 JSON 文件中的每個欄位,都有自己的倒排索引,當然也可以指定對某些欄位不做索引,節省儲存空間,但也就自然而然不能搜尋了

Elasticsearch 這個 Term 在前面文件列表裡面,對應的倒排列表可能是

DocID TF Position Offset
1 1 1 <10, 23>
3 1 0 <0, 13>

分詞

Analysis,文字分析,是把全文字轉換成一系列單詞(term/token)的過程,也叫分詞

Analysis 是通過 Analyzer 分詞器來實現的,ES 內建多種分詞器,也可以按需定製分詞器

除了資料入庫的時候需要分詞,查詢語句也需要用相同的分詞器對查詢語句進行分析

分詞器(Analyzer)

分詞器主要由三部分組成

  1. Character Filters(針對原始文字處理,例如去除 HTML、正則替換等)

  2. Tokenizer(按照規切分為單詞)

  3. Token Filters(將切分的單詞進行加工,如大小寫轉換,刪除 stopwords,增加同義詞等)

ES 的內建分詞器

// 直接指定分詞器進行測試
Get /_analyze
{
	"analyzer": "standard",
	"text": "Mastering Elasticsearch, elasticsearch in Action"
}

// 指定索引的欄位進行測試
POST books/_analyze
{
	"field": "title",
	"text", "Mastering Elasticsearch"
}

// 自定義分詞器進行測試
POST /_analyze
{
	"tokenizer": "standard",
	"filter": ["lowercase"],
	"text": "Mastering Elasticsearch"
}

中文分詞

中文句子要切分成一個個詞(不是一個個字) 英文中,單詞有自然的空格作為分隔 一句中文,在不同的上下文,有不同理解(這個蘋果,不大好吃/這個蘋果,不大,好吃;全民,k歌/全民k歌)

ICU Analyzer,提供了 Unicode 的支援,更好地支援亞洲語言

安裝外掛: Elasticsearch-plugin install analysis-icu

更多的中文分詞器

  • IK:支援自定義詞庫,支援熱更新分詞字典

  • THULAC:THU Lexical Analyzer for Chinese,清華大學自然語言處理和社會人文計算實驗室的一套中文分詞器

深入搜尋

基於詞項(Term)

Term 是表達語義的最小單位

Term Level Query

  • Term Query

  • Range Query

  • Exists Query

  • Prefix Query

  • Wildcard Query

在 ES 中,Term 查詢,對輸入 不做分詞 ,會將輸入作為一個整體,在倒排索引中查詢準確的詞項,並使用相關度打分公式為每個包含該詞項的文件進行 相關性打分

可以用 Constant Score Query 將查詢轉換為一個 Filtering,避免打分,利用快取提高效能

基於全文(Text)的搜尋

查詢的時候,會對輸入的查詢進行分詞,生成一個供查詢的詞項列表,然後每個詞項進行底層查詢,最終將結果合併,並給每個文件生成一個相似度打分

  • Match Query

  • Match Phrase Query

  • Query String Query

對應的,在資料入庫 Index 階段,如果欄位型別是 text 則會分詞,keyword 型別不會分詞

結構化搜尋

結構化搜尋(Structured search)是指對結構化資料的搜尋

結構化資料顧名思義也就是遵循嚴格定義的結構的資料

  • 時間、日期、數字這類有精確格式的資料,可以對這類資料進行邏輯操作,如判斷範圍、比較大小等

  • 結構化文字,可以做精確匹配或部分匹配

結構化結果只有“是”和“否”兩個值,根據場景的需要,一樣可以控制結構化結果是否需要打分

相關性(Relevance)和相關性打分

使用者關心的幾類問題

  • 是否可以找到所有相關的內容

  • 有多少不相關的內容被返回了

  • 文件的打分是否合理

  • 結合業務需求,調整結果排名

衡量相關性 Information Retrieval

  • Precision(查準率):儘可能返回較少的無關文件

  • Recall(查全率):儘量返回較多的相關文件

  • Ranking:是否能按照相關度進行排序

  • True Positive(正確接受)

  • False Positive(錯誤接受)

  • True Negatives(正確拒絕)

  • False Negatives(錯誤拒絕)

Precision = TP / ALL

Recall = TP / (TP + FN)

調整 ES 查詢引數,可以調優這兩個引數

搜尋的相關性打分,描述了一個 文件和查詢語句匹配的程度 ,ES 會對每個匹配查詢條件的結果進行打分 _score 打分的本質是排序,需要把最符合使用者需求的文件排在最前面,ES 5 之前,預設的相關性打分演算法是 TF-IDF,現在採用 BM 25

TF-IDF

詞頻 TF,Term Frequency,檢索詞在文件中出現的頻率 本質上描述了兩個簡單的規則

  1. 某個詞在一個文件中出現越多,越相關

  2. 整個文件集合中包含某個詞的文件數量越少,這個詞越重要

舉例,輸入查詢“我的蘋果”,我在文件 1 中出現,蘋果在文件 1、2 中出現

Term Doc ID
1
蘋果 1, 2

計算一個詞的詞頻的簡單方式可以是

注意這裡“的”是一個與語義無關的停用詞,不應該考慮 TF(的) 的影響

文件頻率 DF,Document Frequency,檢索詞在所有文件中出現的頻率

  • “我”在較多文件中出現

  • “蘋果“在較少文件中出現

逆文件頻率 IDF,Inverse Document Frequency

TF-IDF 本質上就是把 TF 求和變成了加權求和

出現的文件數 總文件數 IDF
200 萬 10 億 log(500)=8.96
蘋果 5 億 10 億 log(2)=1

TF-IDF 被公認為是資訊檢索領域最重要的發明 除了資訊檢索,在文獻分類和其它相關領域有著非常廣泛的應用 IDF 的概念,最早是劍橋大學的斯巴克·瓊斯提出,1972 年 《關鍵詞特殊性的統計解釋和它在文獻檢索中的應用》,不過並沒有從理論上解釋為啥 IDF 是要用 log(全部文件數/檢索詞出現過的文件總數),也沒有進行進一步研究 1970,1980 年代薩爾頓和羅賓遜,進行了進一步的證明和研究,並用夏農資訊理論進行了證明 http://www.staff.city.ac.uk/~sbrp622/papers/foundations_bm25_review.pdf 現代搜尋引擎,對 TF-IDF 進行了許多優化

Lucene 中的 TF-IDF

q:查詢語句

d: 匹配的文件

t:分詞後的詞項

boost:權重提升,ES 查詢時可以自行指定來改變 Boosting query

norm(t,d):文件越短,相關性越高

BM 25

BM 25,相比 TF-IDF,解決了一個問題:當 TF 無限增加時,BM 25 的算分會趨於一個固定值,而不是無限增長

官方文件參考連結:https://www.elastic.co/guide/cn/elasticsearch/guide/current/pluggable-similarites.html

多語言及中文分詞檢索

當處理自然語言時,有時候儘管搜尋與原文不完全匹配,但是還是希望搜尋到一些內容

一些可採取的優化

  • 歸一化詞元:如搜尋“大”的時候也會搜尋“達”

  • 抽取詞根:清除單複數和時態的差異

  • 包含同義詞

  • 拼寫錯誤或同音異形詞

混合多語言的挑戰

  • 詞幹提取:如以色列文件,包含了希伯來語,阿拉伯語,俄語和英語

  • 不正確的文件頻率:如英語為主的文件中,德語得分高(稀有)

  • 需要判斷使用者搜尋時使用的語言,語言識別

分詞的挑戰

英文分詞:You’re 分成一個還是多個?Half-baked 要不要切分?

中文分詞:

  • 比英文分詞更復雜,如姓和名,在哈工大標準中,姓名是分開的,但在 HanLP 標準下,姓和名是整體

  • 歧義(組合型歧義,交集型歧義,真歧義)

    • 中華人民共和國(中華+人民+共和國,中+華人+民+共和+國,...)

中文分詞方法的演變

字典法(北京航空航天大學,梁元楠教授):一個句子從左到右掃描一遍,遇到有的詞就標示出來;找到複合詞,就找最長的;不認識的詞就分割成單字詞

最小詞數的分詞理論(哈爾濱工業大學,王曉龍博士)

  • 一句話應該分成數量最少的詞串

  • 遇到二義性的分割,無能為力,多種文化規則來解決都不太成功

統計語言模型(清華大學郭進博士):解決了二義性問題,將中文分詞的錯誤率降低了一個數量級,動態規劃+維特比演算法快速找到最佳分詞

基於統計的機器學習演算法:這類目前常用的演算法是 HMM、CRF、SVM、深度學習等演算法

中文分詞器現狀

中文分詞器以統計語言模型為基礎,經過幾十年發展,到今天為止可以看做一個已經解決的問題

不同分詞器的好壞,主要差別在於資料的使用和工程使用的精度

常見的分詞器都是使用機器學習演算法和詞典結合,一方面能提高分詞準確率,另一方面能改善領域適應性

ES 中提供的一些分詞器

  • HanLP:面向生產環境的自然語言處理工具包

  • IK 分詞器,支援詞典熱更新

  • Pinyin 分詞器,拼音

Search Template

Search Template,用於解耦程式和搜尋 DSL

在開發初期就能明確查詢引數,但最終的 DSL 結構無法確定,這時候可以通過 Search Template 定義一個 Contract,開發人員和搜尋優化人員可以分頭並行開發

Index Alias

Alias API,索引可以設定別名,實現零停機運維

Function Score Query 優化打分

ES 預設會以文件的相關度演算法進行排序

Function Score Query 可以在查詢結束後,對每個匹配的文件進行重新算分,根據新生成的得分進行排序

Function Score Query 提供了一些預設的打分函式

  • Weight:設定權重

  • Field Value Factor:使用該數值來修改得分,例如將“熱度”和“點贊數”作為算分的參考因素

  • Random Score:為每個使用者使用一個不同的隨機算分結果

  • 衰減函式:以某個欄位的值為標準,距離某個值越近,得分越高

  • Script Score:寫自定義指令碼,控制邏輯

因為有了 Script Score, 可以做的事情就很多了,比如實現海明距離、餘弦距離等演算法,實現 對非文字型別資料的打分查詢 ,比如指紋相似度、聲紋相似度等

自動補全與基於上下文的提示

Complete Suggerster 提供了自動完成的功能,使用者每輸入一個字元,就需要即時傳送一個查詢請求到後端查詢匹配項

對效能要求很苛刻,ES 採用了不同的資料結構,而非倒排索引來完成。ES 會將 Analyze 的資料編碼成 FST 和索引一起存放,FST 會被 ES 整個載入進記憶體,速度很快

FST:Finite StateTransducers,有窮狀態轉換器

需要啟用該特性的話,需要在建立 Mapping 時指定 "type": "completion"

另外還可以指定 context,來使用 suggerster 的基於上下文的提示

分散式搜尋的執行機制

ES 的搜尋有兩個階段

  1. Query

  2. Fetch

Query

使用者發出查詢請求到達 Coordinating 節點(預設每個節點都是),節點會在所有主、副分片中隨機出一個完整的資料分片列表組,然後將請求轉發給它們,隨後每個分片都會執行查詢請求並 排序 ,然後每個分片都會返回  From+Size 個排序後的文件 ID 和排序值給 Coordinating 節點

Fetch

Coordinating 節點會將 Query 階段,從每個分片獲取的排序後的文件 ID 列表,進行合併排序,並選取合併後列表的 [From, From+Size) 文件的 ID 子列表;接下來再以 multi get 的請求方式,到相應的分配去獲取詳細的文件資料

Query Then Fetch 存在的問題

效能方面

  • 每個分片上需要查的文件數 = From+Size

  • 最終協調節點需要處理 分片數量 * (From+Size) 這麼多的文件 ID

  • 深度分頁的時候,這裡的效能問題會很嚴重

相關性打分方面

  • 每個分片都是基於自己分片上的資料進行相關度計算,這可能會導致打分偏離的情況,特別是資料量很少的時候。相關性打分在分片之間是相互獨立的。當文件總數很少的時候,主分片數越多,相關性打分會越不準

解決方案主要由兩種

  • 控制主分片數量

    • 資料量不多的時候設定分片數為 1 即可

    • 資料量大的時候,需要儘量保證每個分片的資料量均等

  • DFS Query Then Fetch

    • 在查詢 URL 中加上引數 _search?search_type=dfs_query_then_fetch

    • 效果就是在查詢的時候,會在每個分片的詞頻和文件頻率進行採集,然後完整的進行一次相關性打分,這樣做會有效能問題,一般不建議使用

排序相關的問題

排序,也就是將查詢結果根據指定的欄位進行排序。排序是針對欄位原始內容進行的,所以倒排索引在這裡無法發揮作用,需要用到正排索引,即通過 ID 欄位快速得到欄位的原始內容

ES 有兩種實現方式

  • Field Data

  • Doc Values(列式儲存,對 Text 欄位無效)

Doc Values Field Data
建立時機 索引時,和倒排索引一起建立 搜尋時動態建立
建立位置 磁碟檔案 JVM Heap
優點 記憶體佔用少 新文件索引快,不用佔用磁碟空間
缺點 新文件索引慢 文件過多時,動態建立開銷大,佔用記憶體多
預設值 >ES 2.x ES 1.x

Doc Values 特性預設是啟用的,可以在 Index Mapping 中設定關閉,關閉可以提升新文件索引的速度,減少磁碟空間;如果明確某些欄位不需要做排序和聚合,可以設定關閉 Doc Values

深度分頁的解決方案

如前文所述,Query Then Fetch 模式存在深度分頁時的效能問題,ES 為了保護自身不被深度分頁查詢請求拖死,預設有一個限制 From+Size 不能大於 10000

而 ES 對這種深度分頁的場景,提供了兩種解決方式

  1. Search After API

  2. Scroll API

Search After API 的玩法大概就是說,首次請求時定義排序欄位,且排序欄位不能重複(可以多欄位聯合,所以可以引入 _id 欄位來確保唯一),然後每次查詢只會返回一部分結果,需要翻頁查詢下一份資料時,需要將前一次查詢的結果帶上

Scroll API 是在查詢呼叫的第一次,就建立一個快照(指定有效期),每次查詢都需要帶上上一次的 scroll ID;注意因為是快照,所以新寫入的文件,在這個快照中是無法查詢到的

不同的查詢方式的選型

  • Regular

    • 實時獲取最頂部的部分文件

  • Scroll

    • 需要遍歷全部文件

  • Pagination

    • 資料量少時,直接 From+Size

    • 資料量大時,Search After API

ES 的併發控制

在併發更新文件的場景下,ES 是採用樂觀鎖版本號的方式來實現併發控制

如前文所述,ES 的文件其實是不可變的,所以對文件的更新,其實就是先標記原文件被刪除,然後建立一個新文件,這兩個文件的版本號不同

  • 內部儲存: _seq_no + _primary_term

  • 使用外部版本號(其他資料庫作為資料主儲存): version + version_type=external

其它一些比較有用的東西

Index Template

定義 Index 的模板,並且按照一定規則,在 Index 建立的時候自動使用

可以建立多個模板,多個模板可以 merge 在一起,且 merge 過程可以指定 order 來控制生效順序

當一個索引被建立時

  1. 使用 ES 預設的 Index Template

  2. 由 order 分數從低到高逐個應用滿足規則的 Index Template,遇到衝突,後面的會覆蓋前面的

  3. 應用建立 Mapping 時主動指定的 Index Settings 和 Mappings(如果有的話)

Dynamic Template

位於某個 Index 的 Mapping 中,根據 ES 識別的資料型別,結合欄位名稱,來動態設定欄位型別

可以做到的事情比如

  • 所有欄位都設定為 keyword 型別,或者關閉 keyword 欄位

  • is 開頭的都設定為 bool 型別

參考資料:

  • Elasticsearch 官方文件

  • 極客時間《Elasticsearch核心技術與實戰》

  • 《Elasticsearch 權威指南》

  • 《深入理解Elasticsearch》

QQ音樂招聘後端開發,點選左下方“檢視原文”投遞簡歷~

也可將簡歷傳送至郵箱:[email protected]