Elasticsearch 億級資料檢索效能最佳化案例實戰!

石杉的架構筆記發表於2022-12-08

一、前言

資料平臺已迭代三個版本,從頭開始遇到很多常見的難題,終於有片段時間整理一些已完善的文件,在此分享以供所需朋友實現參考,少走些彎路,在此篇幅中偏重於ES的最佳化,關於HBase,Hadoop的設計最佳化估計有很多文章可以參考,不再贅述。

二、需求說明

專案背景:

在一業務系統中,部分表每天的資料量過億,已按天分表,但業務上受限於按天查詢,並且DB中只能保留3個月的資料(硬體高配),分庫代價較高。

改進版本目標:

1. 資料能跨月查詢,並且支援1年以上的歷史資料查詢與匯出。

2. 按條件的資料查詢秒級返回。

三、ES 檢索原理

 3.1 關於ES和Lucene基礎結構

談到最佳化,必須能瞭解元件的基本原理,才容易找到瓶頸所在,以免走多種彎路,先從ES的基礎結構說起(如下圖):

Elasticsearch 億級資料檢索效能最佳化案例實戰!

一些基本概念:

  • Cluster 包含多個Node的叢集

  • Node 叢集服務單元

  • Index 一個ES索引包含一個或多個物理分片,它只是這些分片的邏輯名稱空間

  • Type 一個index的不同分類,6.x後只能配置一個type,以後將移除

  • Document 最基礎的可被索引的資料單元,如一個JSON串

  • Shards 一個分片是一個底層的工作單元,它僅儲存全部資料中的一部分,它是一個Lucence例項 (一個lucene索引最大包含2,147,483,519 (= Integer.MAX_VALUE - 128)個文件數量)

  • Replicas 分片備份,用於保障資料安全與分擔檢索壓力

ES依賴一個重要的元件Lucene,關於資料結構的最佳化通常來說是對Lucene的最佳化,它是叢集的一個儲存於檢索工作單元,結構如下圖:

Elasticsearch 億級資料檢索效能最佳化案例實戰!

在Lucene中,分為索引(錄入)與檢索(查詢)兩部分,索引部分包含 分詞器、過濾器、字元對映器 等,檢索部分包含 查詢解析器 等。

一個Lucene索引包含多個segments,一個segment包含多個文件,每個文件包含多個欄位,每個欄位經過分詞後形成一個或多個term。

透過Luke工具檢視ES的lucene檔案如下,主要增加了_id和_source欄位:

Elasticsearch 億級資料檢索效能最佳化案例實戰!

3.2 Lucene索引實現

Lucene 索引檔案結構主要的分為:詞典、倒排表、正向檔案、DocValues等,如下圖:

Elasticsearch 億級資料檢索效能最佳化案例實戰!


Elasticsearch 億級資料檢索效能最佳化案例實戰!

注:整理來源於lucene官方: 

Lucene 隨機三次磁碟讀取比較耗時。其中.fdt檔案儲存資料值損耗空間大,.tim和.doc則需要SSD儲存提高隨機讀寫效能。

另外一個比較消耗效能的是打分流程,不需要則可遮蔽。

 關於DocValues: 

倒排索引解決從詞快速檢索到相應文件ID, 但如果需要對結果進行排序、分組、聚合等操作的時候則需要根據文件ID快速找到對應的值。

透過倒排索引代價卻很高:需迭代索引裡的每個詞項並收集文件的列裡面 token。這很慢而且難以擴充套件:隨著詞項和文件的數量增加,執行時間也會增加。

Solr docs對此的解釋如下:

For other features that we now commonly associate with search, such as sorting, faceting, and highlighting, this approach is not very efficient. The faceting engine, for example, must look up each term that appears in each document that will make up the result set and pull the document IDs in order to build the facet list. In Solr, this is maintained in memory, and can be slow to load (depending on the number of documents, terms, etc.)

在lucene 4.0版本前透過FieldCache,原理是透過按列逆轉倒排表將(field value ->doc)對映變成(doc -> field value)對映,問題為逐步構建時間長並且消耗大量記憶體,容易造成OOM。

DocValues是一種列儲存結構,能快速透過文件ID找到相關需要排序的欄位。

在ES中,預設開啟所有(除了標記需analyzed的字串欄位)欄位的doc values,如果不需要對此欄位做任何排序等工作,則可關閉以減少資源消耗。

 3.3 關於ES索引與檢索分片

ES中一個索引由一個或多個lucene索引構成,一個lucene索引由一個或多個segment構成,其中segment是最小的檢索域。

資料具體被儲存到哪個分片上: shard = hash(routing) % number_of_primary_shards

預設情況下 routing引數是文件ID (murmurhash3),可透過 URL中的 _routing 引數指定資料分佈在同一個分片中,index和search的時候都需要一致才能找到資料

如果能明確根據_routing進行資料分割槽,則可減少分片的檢索工作,以提高效能

四、最佳化案例

在我們的案例中,查詢欄位都是固定的,不提供全文檢索功能,這也是幾十億資料能秒級返回的一個大前提:

1、ES僅提供欄位的檢索,僅儲存HBase的Rowkey不儲存實際資料。

2、實際資料儲存在HBase中,透過Rowkey查詢,如下圖。

3、提高索引與檢索的效能建議,可參考官方文件(如 https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html

一些細節最佳化項官方與其他的一些文章都有描述,在此文章中僅提出一些本案例的重點最佳化項。

Elasticsearch 億級資料檢索效能最佳化案例實戰!

4.1 最佳化索引效能

1、批次寫入,看每條資料量的大小,一般都是幾百到幾千。

2、多執行緒寫入,寫入執行緒數一般和機器數相當,可以配多種情況,在測試環境透過Kibana觀察效能曲線。

3、增加segments的重新整理時間,透過上面的原理知道,segment作為一個最小的檢索單元,比如segment有50個,目的需要查10條資料,但需要從50個segment

分別查詢10條,共500條記錄,再進行排序或者分數比較後,擷取最前面的10條,丟棄490條。在我們的案例中將此 "refresh_interval": "-1" ,程式批次寫入完成後

進行手工重新整理(呼叫相應的API即可)。

4、記憶體分配方面,很多文章已經提到,給系統50%的記憶體給Lucene做檔案快取,它任務很繁重,所以ES節點的記憶體需要比較多(比如每個節點能配置64G以上最好)

5、磁碟方面配置SSD,機械盤做陣列RAID5 RAID10雖然看上去很快,但是隨機IO還是SSD好。

6、 使用自動生成的ID,在我們的案例中使用自定義的KEY,也就是與HBase的ROW KEY,是為了能根據rowkey刪除和更新資料,效能下降不是很明顯。

7、關於段合併,合併在後臺定期執行,比較大的segment需要很長時間才能完成,為了減少對其他操作的影響(如檢索),elasticsearch進行閾值限制,預設是20MB/s,

可配置的引數:"indices.store.throttle.max_bytes_per_sec" : "200mb" (根據磁碟效能調整)

合併執行緒數預設是:Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2)),如果是機械磁碟,可以考慮設定為1:index.merge.scheduler.max_thread_count: 1,

在我們的案例中使用SSD,配置了6個合併執行緒。

 4.2 最佳化檢索效能

1、關閉不需要欄位的doc values。

2、儘量使用keyword替代一些long或者int之類,term查詢總比range查詢好 (參考lucene說明 )。

3、關閉不需要查詢欄位的_source功能,不將此儲存僅ES中,以節省磁碟空間。

4、評分消耗資源,如果不需要可使用filter過濾來達到關閉評分功能,score則為0,如果使用constantScoreQuery則score為1。

5、關於分頁:

(1)from + size:

每分片檢索結果數最大為 from + size,假設from = 20, size = 20,則每個分片需要獲取20 * 20 = 400條資料,多個分片的結果在協調節點合併(假設請求的分配數為5,則結果數最大為 400*5 = 2000條) 再在記憶體中排序後然後20條給使用者。

這種機制導致越往後分頁獲取的代價越高,達到50000條將面臨沉重的代價,預設from + size預設如下:

 index.max_result_window :10000

(2) search_after: 使用前一個分頁記錄的最後一條來檢索下一個分頁記錄,在我們的案例中,首先使用from+size,檢索出結果後再使用search_after,在頁面上我們限制了使用者只能跳5頁,不能跳到最後一頁。

(3) scroll 用於大結果集查詢,缺陷是需要維護scroll_id

6、關於排序:我們增加一個long欄位,它用於儲存時間和ID的組合(透過移位即可),正排與倒排效能相差不明顯。

7、關於CPU消耗,檢索時如果需要做排序則需要欄位對比,消耗CPU比較大,如果有可能儘量分配16cores以上的CPU,具體看業務壓力。

8、關於合併被標記刪除的記錄,我們設定為0表示在合併的時候一定刪除被標記的記錄,預設應該是大於10%才刪除:"merge.policy.expunge_deletes_allowed": "0"。

{
    "mappings": {
        "data": {
            "dynamic""false",
            "_source": {
                "includes": ["XXX"]  -- 僅將查詢結果所需的資料儲存僅_source中
            },
            "properties": {
                "state": {
                    "type""keyword",   -- 雖然state為int值,但如果不需要做範圍查詢,儘量使用keyword,因為int需要比keyword增加額外的消耗。
                    "doc_values"false  -- 關閉不需要欄位的doc values功能,僅對需要排序,匯聚功能的欄位開啟。
                },
                "b": {
                    "type""long"    -- 使用了範圍查詢欄位,則需要用long或者int之類 (構建類似KD-trees結構)
                }
            }
        }
    },
   "settings": {......}
}

五、效能測試

最佳化效果評估基於基準測試,如果沒有基準測試無法瞭解是否有效能提升,在這所有的變動前做一次測試會比較好。在我們的案例中:

1、單節點5千萬到一億的資料量測試,檢查單點承受能力。

2、叢集測試1億-30億的數量,磁碟IO/記憶體/CPU/網路IO消耗如何。

3、隨機不同組合條件的檢索,在各個資料量情況下表現如何。

4、另外SSD與機械盤在測試中效能差距如何。

效能的測試組合有很多,通常也很花時間,不過作為評測標準時間上的投入有必要,否則生產出現效能問題很難定位或不好改善。

對於ES的效能研究花了不少時間,最多的關注點就是lucene的最佳化,能深入瞭解lucene原理對最佳化有很大的幫助。

六、生產效果

目前平臺穩定執行,幾十億的資料查詢100條都在3秒內返回,前後翻頁很快,如果後續有效能瓶頸,可透過擴充套件節點分擔資料壓力。

來源:https://www.cnblogs.com/mikevictor07/p/10006553.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901780/viewspace-2657982/,如需轉載,請註明出處,否則將追究法律責任。

相關文章