從原理到應用,Elasticsearch詳解

TalkingData發表於2019-08-10

簡介

Elasticsearch(簡稱ES)是一個分散式、可擴充套件、實時的搜尋與資料分析引擎。ES不僅僅只是全文搜尋,還支援結構化搜尋、資料分析、複雜的語言處理、地理位置和物件間關聯關係等。

ES的底層依賴Lucene,Lucene可以說是當下最先進、高效能、全功能的搜尋引擎庫。但是Lucene僅僅只是一個庫。為了充分發揮其功能,你需要使用Java並將Lucene直接整合到應用程式中。更糟糕的是,您可能需要獲得資訊檢索學位才能瞭解其工作原理,因為Lucene非常複雜——《ElasticSearch官方權威指南》。

鑑於Lucene如此強大卻難以上手的特點,誕生了ES。ES也是使用Java編寫的,它的內部使用Lucene做索引與搜尋,它的目的是隱藏Lucene的複雜性,取而代之的提供一套簡單一致的RESTful API。

總體來說,ES具有如下特點:

  • 一個分散式的實時文件儲存引擎,每個欄位都可以被索引與搜尋
  • 一個分散式實時分析搜尋引擎,支援各種查詢和聚合操作
  • 能勝任上百個服務節點的擴充套件,並可以支援PB級別的結構化或者非結構化資料

架構

節點型別

ES的架構很簡單,叢集的HA不需要依賴任務外部元件(例如Zookeeper、HDFS等),master節點的主備依賴於內部自建的選舉演算法,通過副本分片的方式實現了資料的備份的同時,也提高了併發查詢的能力。

ES叢集的伺服器分為以下四種角色:

1.列表專案master節點,負責儲存和更新叢集的一些後設資料資訊,之後同步到所有節點,所以每個節點都需要儲存全量的後設資料資訊:

  • 叢集的配置資訊
  • 叢集的節點資訊
  • 模板template設定
  • 索引以及對應的設定、mapping、分詞器和別名
  • 索引關聯到的分片以及分配到的節點

2.datanode:負責資料儲存和查詢

3.coordinator:

  • 路由索引請求
  • 聚合搜尋結果集
  • 分發批量索引請求

4.ingestor:

  • 類似於logstash,對輸入資料進行處理和轉換

如何配置節點型別

一個節點的預設配置是:主節點+資料節點兩屬性為一身。對於3-5個節點的小叢集來講,通常讓所有節點儲存資料和具有獲得主節點的資格。

專用協調節點(也稱為client節點或路由節點)從資料節點中消除了聚合/查詢的請求解析和最終階段,隨著叢集寫入以及查詢負載的增大,可以通過協調節點減輕資料節點的壓力,可以讓資料節點更多專注於資料的寫入以及查詢。

master選舉

選舉策略

  • 如果叢集中存在master,認可該master,加入叢集
  • 如果叢集中不存在master,從具有master資格的節點中選id最小的節點作為master

選舉時機

叢集啟動:後臺啟動執行緒去ping叢集中的節點,按照上述策略從具有master資格的節點中選舉出master

現有的master離開叢集:後臺一直有一個執行緒定時ping master節點,超過一定次數沒有ping成功之後,重新進行master的選舉

選舉流程

clipboard.png

避免腦裂

腦裂問題是採用master-slave模式的分散式叢集普遍需要關注的問題,腦裂一旦出現,會導致叢集的狀態出現不一致,導致資料錯誤甚至丟失。

ES避免腦裂的策略:過半原則,可以在ES的叢集配置中新增一下配置,避免腦裂的發生

#一個節點多久ping一次,預設1s
discovery.zen.fd.ping_interval: 1s
##等待ping返回時間,預設30s
discovery.zen.fd.ping_timeout: 10s
##ping超時重試次數,預設3次
discovery.zen.fd.ping_retries: 3
##選舉時需要的節點連線數,N為具有master資格的節點數量
discovery.zen.minimum_master_nodes=N/2+1

注意問題

  • 配置檔案中加入上述避免腦裂的配置,對於網路波動比較大的叢集來說,增加ping的時間和ping的次數,一定程度上可以增加叢集的穩定性
  • 動態的欄位field可能導致後設資料暴漲,新增欄位mapping對映需要更新mater節點上維護的欄位對映資訊,master修改了對映資訊之後再同步到叢集中所有的節點,這個過程中資料的寫入是阻塞的。所以建議關閉自動mapping,沒有預先定義的欄位mapping會寫入失敗
  • 通過定時任務在叢集寫入的低峰期,將索引以及mapping對映提前建立好

負載均衡

ES叢集是分散式的,資料分佈到叢集的不同機器上,對於ES中的一個索引來說,ES通過分片的方式實現資料的分散式和負載均衡。建立索引的時候,需要指定分片的數量,分片會均勻的分佈到叢集的機器中。分片的數量是需要建立索引的時候就需要設定的,而且設定之後不能更改,雖然ES提供了相應的api來縮減和擴增分片,但是代價是很高的,需要重建整個索引。

考慮到併發響應以及後續擴充套件節點的能力,分片的數量不能太少,假如你只有一個分片,隨著索引資料量的增大,後續進行了節點的擴充,但是由於一個分片只能分佈在一臺機器上,所以叢集擴容對於該索引來說沒有意義了。

但是分片數量也不能太多,每個分片都相當於一個獨立的lucene引擎,太多的分片意味著叢集中需要管理的後設資料資訊增多,master節點有可能成為瓶頸;同時叢集中的小檔案會增多,記憶體以及檔案控制程式碼的佔用量會增大,查詢速度也會變慢。

資料副本

ES通過副本分片的方式,保證叢集資料的高可用,同時增加叢集併發處理查詢請求的能力,相應的,在資料寫入階段會增大叢集的寫入壓力。

資料寫入的過程中,首先被路由到主分片,寫入成功之後,將資料傳送到副本分片,為了保證資料不丟失,最好保證至少一個副本分片寫入成功以後才返回客戶端成功。

相關配置

5.0之前通過consistency來設定

consistency引數的值可以設為 :

  • one :只要主分片狀態ok就允許執行寫操作
  • all:必須要主分片和所有副本分片的狀態沒問題才允許執行寫操作
  • quorum:預設值為quorum,即大多數的分片副本狀態沒問題就允許執行寫操作,副本分片數量計算方式為int( (primary +
    number_of_replicas) / 2 ) + 1

5.0之後通過wait_for_active_shards引數設定

  • 索引時增加引數:?wait_for_active_shards=3
  • 給索引增加配置:index.write.wait_for_active_shards=3

資料寫入

寫入過程

幾個概念:

  • 記憶體buffer
  • translog
  • 檔案系統緩衝區
  • refresh
  • segment(段)
  • commit
  • flush

clipboard.png

translog

寫入ES的資料首先會被寫入translog檔案,該檔案持久化到磁碟,保證伺服器當機的時候資料不會丟失,由於順序寫磁碟,速度也會很快。

  • 同步寫入:每次寫入請求執行的時候,translog在fsync到磁碟之後,才會給客戶端返回成功
  • 非同步寫入:寫入請求快取在記憶體中,每經過固定時間之後才會fsync到磁碟,寫入量很大,對於資料的完整性要求又不是非常嚴格的情況下,可以開啟非同步寫入

refresh

經過固定的時間,或者手動觸發之後,將記憶體中的資料構建索引生成segment,寫入檔案系統緩衝區

commit/flush

超過固定的時間,或者translog檔案過大之後,觸發flush操作:

  • 記憶體的buffer被清空,相當於進行一次refresh
  • 檔案系統緩衝區中所有segment刷寫到磁碟
  • 將一個包含所有段列表的新的提交點寫入磁碟
  • 啟動或重新開啟一個索引的過程中使用這個提交點來判斷哪些segment隸屬於當前分片
  • 刪除舊的translog,開啟新的translog

merge

上面提到,每次refresh的時候,都會在檔案系統緩衝區中生成一個segment,後續flush觸發的時候持久化到磁碟。所以,隨著資料的寫入,尤其是refresh的時間設定的很短的時候,磁碟中會生成越來越多的segment:

  • segment數目太多會帶來較大的麻煩。 每一個segment都會消耗檔案控制程式碼、記憶體和cpu執行週期。
  • 更重要的是,每個搜尋請求都必須輪流檢查每個segment,所以segment越多,搜尋也就越慢。

merge的過程大致描述如下:

  • 磁碟上兩個小segment:A和B,記憶體中又生成了一個小segment:C
  • A,B被讀取到記憶體中,與記憶體中的C進行merge,生成了新的更大的segment:D
  • 觸發commit操作,D被fsync到磁碟
  • 建立新的提交點,刪除A和B,新增D
  • 刪除磁碟中的A和B

刪改操作

segment的不可變性的好處

  • segment的讀寫不需要加鎖
  • 常駐檔案系統快取(堆外記憶體)
  • 查詢的filter快取可以常駐記憶體(堆記憶體)

刪除

磁碟上的每個segment都有一個.del檔案與它相關聯。當傳送刪除請求時,該文件未被真正刪除,而是在.del檔案中標記為已刪除。此文件可能仍然能被搜尋到,但會從結果中過濾掉。當segment合併時,在.del檔案中標記為已刪除的文件不會被包括在新的segment中,也就是說merge的時候會真正刪除被刪除的文件。

更新

建立新文件時,Elasticsearch將為該文件分配一個版本號。對文件的每次更改都會產生一個新的版本號。當執行更新時,舊版本在.del檔案中被標記為已刪除,並且新版本在新的segment中寫入索引。舊版本可能仍然與搜尋查詢匹配,但是從結果中將其過濾掉。

版本控制

通過新增版本號的樂觀鎖機制保證高併發的時候,資料更新不會出現執行緒安全的問題,避免資料更新被覆蓋之類的異常出現。

使用內部版本號:刪除或者更新資料的時候,攜帶_version引數,如果文件的最新版本不是這個版本號,那麼操作會失敗,這個版本號是ES內部自動生成的,每次操作之後都會遞增一。

PUT /website/blog/1?version=1 
{
  "title": "My first blog entry",
  "text":  "Starting to get the hang of this..."
}

使用外部版本號:ES預設採用遞增的整數作為版本號,也可以通過外部自定義整數(long型別)作為版本號,例如時間戳。通過新增引數version_type=external,可以使用自定義版本號。內部版本號使用的時候,更新或者刪除操作需要攜帶ES索引當前最新的版本號,匹配上了才能成功操作。但是外部版本號使用的時候,可以將版本號更新為指定的值。

PUT /website/blog/2?version=5&version_type=external
{
  "title": "My first external blog entry",
  "text":  "Starting to get the hang of this..."
}

原始文件儲存(行式儲存)

clipboard.png

fdt檔案

文件內容的物理儲存檔案,由多個chunk組成,Lucene索引文件時,先快取文件,快取大於16KB時,就會把文件壓縮儲存。

clipboard.png

fdx檔案

文件內容的位置索引,由多個block組成:

  • 1024個chunk歸為一個block
  • block記錄chunk的起始文件ID,以及chunk在fdt中的位置

clipboard.png

fnm檔案

文件後設資料資訊,包括文件欄位的名稱、型別、數量等。

原始文件的查詢

clipboard.png

注意問題:lucene對原始檔案的存放是行式儲存,並且為了提高空間利用率,是多文件一起壓縮,因此取文件時需要讀入和解壓額外文件,因此取文件過程非常依賴CPU以及隨機IO。

相關設定

壓縮方式的設定

原始文件的儲存對應_source欄位,是預設開啟的,會佔用大量的磁碟空間,上面提到的chunk中的文件壓縮,ES預設採用的是LZ4,如果想要提高壓縮率,可以將設定改成best_compression。

index.codec: best_compression

特定欄位的內容儲存

查詢的時候,如果想要獲取原始欄位,需要在_source中獲取,因為所有的欄位儲存在一起,所以獲取完整的文件內容與獲取其中某個欄位,在資源消耗上幾乎相同,只是返回給客戶端的時候,減少了一定量的網路IO。

ES提供了特定欄位內容儲存的設定,在設定mappings的時候可以開啟,預設是false。如果你的文件內容很大,而其中某個欄位的內容有需要經常獲取,可以設定開啟,將該欄位的內容單獨儲存。

PUT my_index
{
  "mappings": {
    "_doc": {
      "properties": {
        "title": {
          "type": "text",
          "store": true 
        }
      }
    }
  }
}

倒排索引

clipboard.png

倒排索引中記錄的資訊主要有:

  • 文件編號:segment內部文件編號從0開始,最大值為int最大值,文件寫入之後會分配這樣一個順序號
  • 字典:欄位內容經過分詞、歸一化、還原詞根等操作之後,得到的所有單詞
  • 單詞出現位置:分詞欄位預設開啟,提供對於短語查詢的支援;對於非常常見的詞,例如the,位置資訊可能佔用很大空間,短語查詢需要讀取的資料量很大,查詢速度慢
  • 單詞出現次數:單詞在文件中出現的次數,作為評分的依據
  • 單詞結束字元到開始字元的偏移量:記錄在文件中開始與結束字元的偏移量,提供高亮使用,預設是禁用的
  • 規範因子:對欄位長度進行規範化的因子,給予較短欄位更多權重

倒排索引的查詢過程本質上是通過單詞找對應的文件列表的過程,因此倒排索引中字典的設計決定了倒排索引的查詢速度,字典主要包括字首索引(.tip檔案)和字尾索引(.tim)檔案。

字典字首索引(.tip檔案)

一個合格的詞典結構一般有以下特點:

-查詢速度快 -記憶體佔用小 -記憶體+磁碟相結合

clipboard.png

Lucene採用的字首索引資料結構為FST,它的優點有:

詞查詢複雜度為O(len(str))

  • 共享字首、節省空間、記憶體佔用率低,壓縮率高,模糊查詢支援好
  • 記憶體存放字首索引,磁碟存放字尾詞塊
  • 缺點:結構複雜、輸入要求有序、更新不易

字典字尾(.tim檔案)

字尾詞塊主要儲存了單詞字尾,以及對應的文件列表的位置。

文件列表(.doc檔案)

clipboard.png

lucene對文件列表儲存進行了很好的壓縮,來保證快取友好:

  • 差分壓縮:每個ID只記錄跟前面的ID的差值
  • 每256個ID放入一個block中
  • block的頭資訊存放block中每個ID佔用的bit位數,因為經過上面的差分壓縮之後,文件列表中的文件ID都變得不大,佔用的bit位數變少

上圖經過壓縮之後將6個數字從原先的24bytes壓縮到7bytes。

文件列表的合併

clipboard.png

ES的一個重要的查詢場景是bool查詢,類似於mysql中的and操作,需要將兩個條件對應的文件列表進行合併。為了加快文件列表的合併,lucene底層採用了跳錶的資料結構,合併過程中,優先遍歷較短的連結串列,去較長的列表中進行查詢,如果存在,則該文件符合條件。

clipboard.png

倒排索引的查詢過程

clipboard.png

  • 記憶體載入tip檔案,通過FST匹配字首找到字尾詞塊位置
  • 根據詞塊位置,讀取磁碟中tim檔案中字尾塊並找到字尾和相應的倒排表位置資訊
  • 根據倒排表位置去doc檔案中載入倒排表
  • 藉助跳錶結構,對多個文件列表進行合併

filter查詢的快取

對於filter過濾查詢的結果,ES會進行快取,快取採用的資料結構是RoaringBitmap,在match查詢中配合filter能有效加快查詢速度。

  • 普通bitset的缺點:記憶體佔用大,RoaringBitmap有很好的壓縮特性
  • 分桶:解決文件列表稀疏的情況下,過多的0佔用記憶體,每65536個docid分到一個桶,桶內只記錄docid%65536
  • 桶內壓縮:4096作為分界點,小余這個值用short陣列,大於這個值用bitset,每個short佔兩位元組,4096個short佔用65536bit,所以超過4096個文件id之後,是bitset更節省空間。

clipboard.png

DocValues(正排索引&列式儲存)

clipboard.png

倒排索引儲存的是詞項到文件的對映,也就是詞項存在於哪些文件中,DocValues儲存的是文件到詞項的對映,也就是文件中有哪些詞項。

相關設定

keyword欄位預設開啟

ES6.0(lucene7.0)之前

DocValues採用的資料結構是bitset,bitset對於稀疏資料的支援不好:

  • 對於稀疏的欄位來說,絕大部分的空間都被0填充,浪費空間
  • 由於欄位的值之間插入了0,可能本來連續的值被0間隔開來了,不利於資料的壓縮
  • 空間被一堆0佔用了,快取中快取的有效資料減少,查詢效率也會降低

查詢邏輯很簡單,類似於陣列通過下標進行索引,因為每個value都是固定長度,所以讀取文件id為N的value直接從N*固定長度位置開始讀取固定長度即可。

clipboard.png

ES6.0(lucene7.0)

  • docid的儲存的通過分片加快對映到value的查詢速度
  • value儲存的時候不再給空的值分配空間

因為value儲存的時候,空值不再分配空間,所以查詢的時候不能通過上述通過文件id直接對映到在bitset中的偏移量來獲取對應的value,需要通過獲取docid的位置來找到對應的value的位置。

所以對於DocValues的查詢,關鍵在於DocIDSet中ID的查詢,如果按照簡單的連結串列的查詢邏輯,那麼DocID的查詢速度將會很慢。lucene7借用了RoaringBitmap的分片的思想來加快DocIDSet的查詢速度:

clipboard.png

  • 分片容量為2的16次方,最多可以儲存65536個docid
  • 分片包含的資訊:分片ID;儲存的docid的個數(值不為空的DocIDSet);DocIDSet明細,或者標記分片型別(ALL或者NONE)
  • 根據分片的容量,將分片分為四種不同的型別,不同型別的查詢邏輯不通:ALL:該分片內沒有不存在值的DocID;NONE:該分片內所有的DocID都不存在值;SPARSE:該分片記憶體在值的DocID的個數不超過4096,DocIDSet以short陣列的形式儲存,查詢的時候,遍歷陣列,找到對應的ID的位置;DENSE:該分片記憶體在值的DocID的個數超過4096,DocIDSet以bitset的形式儲存,ID的偏移量也就是在該分片中的位置

最終DocIDSet的查詢邏輯為:

  • 計算DocID/65536,得到所在的分片N
  • 計算前面N-1個分片的DocID的總數
  • 找到DocID在分片N內部的位置,從而找到所在位置之前的DocID個數M
  • 找到N+M位置的value即為該DocID對應的value

資料查詢

查詢過程(query then fetch)

  • 協調節點將請求傳送給對應分片
  • 分片查詢,返回from+size數量的文件對應的id以及每個id的得分
  • 彙總所有節點的結果,按照得分獲取指定區間的文件id
  • 根據查詢需求,像對應分片傳送多個get請求,獲取文件的資訊
  • 返回給客戶端

get查詢更快

預設根據id對文件進行路由,所以指定id的查詢可以定位到文件所在的分片,只對某個分片進行查詢即可。當然非get查詢,只要寫入和查詢的時候指定routing,同樣可以達到該效果。

主分片與副本分片

ES的分片有主備之分,但是對於查詢來說,主備分片的地位完全相同,平等的接收查詢請求。這裡涉及到一個請求的負載均衡策略,6.0之前採用的是輪詢的策略,但是這種策略存在不足,輪詢方案只能保證查詢資料量在主備分片是均衡的,但是不能保證查詢壓力在主備分片上是均衡的,可能出現慢查詢都路由到了主分片上,導致主分片所在的機器壓力過大,影響了整個叢集對外提供服務的能力。

新版本中優化了該策略,採用了基於負載的請求路由,基於佇列的耗費時間自動調節佇列長度,負載高的節點的佇列長度將減少,讓其他節點分攤更多的壓力,搜尋和索引都將基於這種機制。

get查詢的實時性

ES資料寫入之後,要經過一個refresh操作之後,才能夠建立索引,進行查詢。但是get查詢很特殊,資料實時可查。

ES5.0之前translog可以提供實時的CRUD,get查詢會首先檢查translog中有沒有最新的修改,然後再嘗試去segment中對id進行查詢。5.0之後,為了減少translog設計的負責性以便於再其他更重要的方面對translog進行優化,所以取消了translog的實時查詢功能。

get查詢的實時性,通過每次get查詢的時候,如果發現該id還在記憶體中沒有建立索引,那麼首先會觸發refresh操作,來讓id可查。

查詢方式

兩種查詢上下文:

  • query:例如全文檢索,返回的是文件匹配搜尋條件的相關性,常用api:match
  • filter:例如時間區間的限定,回答的是是否,要麼是,要麼不是,不存在相似程度的概念,常用api:term、range

過濾(filter)的目標是減少那些需要進行評分查詢(scoring queries)的文件數量。

分析器(analyzer)

當索引一個文件時,它的全文域被分析成詞條以用來建立倒排索引。當進行分詞欄位的搜尋的時候,同樣需要將查詢字串通過相同的分析過程,以保證搜尋的詞條格式與索引中的詞條格式一致。當查詢一個不分詞欄位時,不會分析查詢字串,而是搜尋指定的精確值。

可以通過下面的命令檢視分詞結果:

GET /_analyze
{
  "analyzer": "standard",
  "text": "Text to analyze"
}

相關性

預設情況下,返回結果是按相關性倒序排列的。每個文件都有相關性評分,用一個正浮點數字段score來表示。score的評分越高,相關性越高。

ES的相似度演算法被定義為檢索詞頻率/反向文件頻率(TF/IDF),包括以下內容:

  • 檢索詞頻率:檢索詞在該欄位出現的頻率,出現頻率越高,相關性也越高。欄位中出現過5次要比只出現過1次的相關性高。
  • 反向文件頻率:每個檢索詞在索引中出現的頻率,頻率越高,相關性越低。檢索詞出現在多數文件中會比出現在少數文件中的權重更低。
  • 欄位長度準則:欄位的長度是多少,長度越長,相關性越低。
    檢索詞出現在一個短的title要比同樣的詞出現在一個長的content欄位權重更大。

查詢的時候可以通過新增?explain引數,檢視上述各個演算法的評分結果。

ES在Ad Tracking的應用

日誌查詢工具

TalkingData 移動廣告監測產品Ad Tracking(簡稱ADT)的系統會接收媒體發過來的點選資料以及SDK發過來的啟用和各種效果點資料,這些資料的處理過程正確與否至關重要。例如,裝置的一條啟用資料為啥沒有歸因到點選,這類問題的排查在Ad Tracking中很常見,通過將資料流中的各個處理環節的重要日誌統一傳送到ES,可以很方便的進行查詢,技術支援的同事可以通過拼寫簡單的查詢條件排查客戶的問題。

  • 索引按天建立:定時關閉歷史索引,釋放叢集資源
  • 別名查詢:資料量增大之後,可以通過拆分索引減輕寫入壓力,拆分之後的索引採用相同的別名,查詢服務不需要修改程式碼
  • 索引重要的設定:
   {
  
  "settings": {
        "index": {
            "refresh_interval": "120s",
            "number_of_shards": "12",
            "translog": {
                "flush_threshold_size": "2048mb"
            },
            "merge": {
                "scheduler": {
                    "max_thread_count": "1"
                }
            },
            "unassigned": {
                "node_left": {
                    "delayed_timeout": "180m"
                }
            }
        }
    }
}
  • 索引mapping的設定

     {
    
     "properties": {
           "action_content": {
               "type": "string",
               "analyzer": "standard"
           },
           "time": {
               "type": "long"
           },
           "trackid": {
               "type": "string",
               "index": "not_analyzed"
           }
       }
       }
    
  • sql外掛,通過拼sql的方式,比起拼json更簡單

點選資料儲存(kv儲存場景)

Ad Tracking收集的點選資料是與廣告投放直接相關的資料,應用安裝之後,SDK會上報啟用事件,系統會去查詢這個啟用事件是否來自於之前使用者點選的某個廣告,如果是,那麼該啟用就是一個推廣量,也就是投放的廣告帶來的啟用。啟用後續的效果點資料也都會去查詢點選,從點選中獲取廣告投放的一些資訊,所以點選查詢在Ad Tracking的業務中至關重要。

業務的前期,點選資料是儲存在Mysql中的,隨著後續點選量的暴增,由於Mysql不能橫向擴充套件,所以需要更換為新的儲存。由於ES擁有橫向擴充套件和強悍的搜尋能力,並且之前日誌查詢工具中也一直使用ES,所以決定使用ES來進行點選的儲存。

重要的設定

  • "refresh_interval": "1s"
  • "translog.flush_threshold_size": "2048mb"
  • "merge.scheduler.max_thread_count": 1
  • "unassigned.node_left.delayed_timeout": "180m"

結合業務進行系統優化

結合業務定期關閉索引釋放資源:Ad Tracking的點選資料具有有效期的概念,超過有效期的點選,啟用不會去歸因。點選有效期最長一個月,所以理論上每天建立的索引在一個月之後才能關閉。但是使用者配置的點選有效期大部分都是一天,這大部分點選在叢集中儲存30天是沒有意義的,而且會佔用大部分的系統資源。所以根據點選的這個業務特點,將每天建立的索引拆分成兩個,一個是有效期是一天的點選,一個是超過一天的點選,有效期一天的點選的索引在一天之後就可以關閉,從而保證叢集中開啟的索引的資料量維持在一個較少的水平。

結合業務將熱點資料單獨索引:啟用和效果點資料都需要去ES中查詢點選,但是兩者對於點選的查詢場景是有差異的,因為效果點事件(例如登入、註冊等)歸因的時候不是去直接查詢點選,而是查詢啟用進而找到點選,效果點要找的點選一定是之前啟用歸因到的,所以啟用歸因到的這部分點選也就是熱點資料。啟用歸因到點選之後,將這部分點選單獨儲存到單獨的索引中,由於這部分點選量少很多,所以效果點查詢的時候很快。

索引拆分:Ad Tracking的點選資料按天進行儲存,但是隨著點選量的增大,單天的索引大小持續增大,尤其是晚上的時候,merge需要合併的segment數量以及大小都很大,造成了很高的IO壓力,導致叢集的寫入受限。後續採用了拆分索引的方案,每天的索引按照上午9點和下午5點兩個時間點將索引拆分成三個,由於索引之間的segment合併是相互獨立的,只會在一個索引內部進行segment的合併,所以在每個小索引內部,segment合併的壓力就會減少。

其他調優

分片的數量

經驗值:

  • 每個節點的分片數量保持在低於每1GB堆記憶體對應叢集的分片在20-25之間。
  • 分片大小為50GB通常被界定為適用於各種用例的限制。

JVM設定

  • 堆記憶體設定:不要超過32G,在Java中,物件例項都分配在堆上,並通過一個指標進行引用。對於64位作業系統而言,預設使用64位指標,指標本身對於空間的佔用很大,Java使用一個叫作記憶體指標壓縮(compressed
    oops)的技術來解決這個問題,簡單理解,使用32位指標也可以對物件進行引用,但是一旦堆記憶體超過32G,這個壓縮技術不再生效,實際上失去了更多的記憶體。
  • 預留一半記憶體空間給lucene用,lucene會使用大量的堆外記憶體空間。
  • 如果你有一臺128G的機器,一半記憶體也是64G,超過了32G,可以通過一臺機器上啟動多個ES例項來保證ES的堆記憶體小於32G。
  • ES的配置檔案中加入bootstrap.mlockall: true,關閉記憶體交換。

通過_cat api獲取任務執行情況

GET http://localhost:9201/_cat/thread_pool?v&h=host,search.active,search.rejected,search.completed
  • 完成(completed)
  • 進行中(active)
  • 被拒絕(rejected):需要特別注意,說明已經出現查詢請求被拒絕的情況,可能是執行緒池大小配置的太小,也可能是叢集效能瓶頸,需要擴容。

小技巧

  • 重建索引或者批量想ES寫歷史資料的時候,寫之前先關閉副本,寫入完成之後,再開啟副本。
  • ES預設用文件id進行路由,所以通過文件id進行查詢會更快,因為能直接定位到文件所在的分片,否則需要查詢所有的分片。
  • 使用ES自己生成的文件id寫入更快,因為ES不需要驗證一次自定義的文件id是否存在。

參考資料

https://www.elastic.co/guide/...

https://github.com/Neway6655/...

https://www.elastic.co/blog/f...

https://blog.csdn.net/zteny/a...

https://www.elastic.co/blog/m...


作者:TalkingData戰鵬弘

封面圖來源於網路,如有侵權,請聯絡刪除

相關文章