簡介
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的選舉
選舉流程
避免腦裂
腦裂問題是採用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
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..."
}
原始文檔存儲(行式存儲)
fdt文件
文檔內容的物理存儲文件,由多個chunk組成,Lucene索引文檔時,先緩存文檔,緩存大於16KB時,就會把文檔壓縮存儲。
fdx文件
文檔內容的位置索引,由多個block組成:
- 1024個chunk歸爲一個block
- block記錄chunk的起始文檔ID,以及chunk在fdt中的位置
fnm文件
文檔元數據信息,包括文檔字段的名稱、類型、數量等。
原始文檔的查詢
注意問題: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
}
}
}
}
}
倒排索引
倒排索引中記錄的信息主要有:
- 文檔編號:segment內部文檔編號從0開始,最大值爲int最大值,文檔寫入之後會分配這樣一個順序號
- 字典:字段內容經過分詞、歸一化、還原詞根等操作之後,得到的所有單詞
- 單詞出現位置:分詞字段默認開啓,提供對於短語查詢的支持;對於非常常見的詞,例如the,位置信息可能佔用很大空間,短語查詢需要讀取的數據量很大,查詢速度慢
- 單詞出現次數:單詞在文檔中出現的次數,作爲評分的依據
- 單詞結束字符到開始字符的偏移量:記錄在文檔中開始與結束字符的偏移量,提供高亮使用,默認是禁用的
- 規範因子:對字段長度進行規範化的因子,給予較短字段更多權重
倒排索引的查找過程本質上是通過單詞找對應的文檔列表的過程,因此倒排索引中字典的設計決定了倒排索引的查詢速度,字典主要包括前綴索引(.tip文件)和後綴索引(.tim)文件。
字典前綴索引(.tip文件)
一個合格的詞典結構一般有以下特點:
-查詢速度快 -內存佔用小 -內存+磁盤相結合
Lucene採用的前綴索引數據結構爲FST,它的優點有:
詞查找複雜度爲O(len(str))
- 共享前綴、節省空間、內存佔用率低,壓縮率高,模糊查詢支持好
- 內存存放前綴索引,磁盤存放後綴詞塊
- 缺點:結構複雜、輸入要求有序、更新不易
字典後綴(.tim文件)
後綴詞塊主要保存了單詞後綴,以及對應的文檔列表的位置。
文檔列表(.doc文件)
lucene對文檔列表存儲進行了很好的壓縮,來保證緩存友好:
- 差分壓縮:每個ID只記錄跟前面的ID的差值
- 每256個ID放入一個block中
- block的頭信息存放block中每個ID佔用的bit位數,因爲經過上面的差分壓縮之後,文檔列表中的文檔ID都變得不大,佔用的bit位數變少
上圖經過壓縮之後將6個數字從原先的24bytes壓縮到7bytes。
文檔列表的合併
ES的一個重要的查詢場景是bool查詢,類似於mysql中的and操作,需要將兩個條件對應的文檔列表進行合併。爲了加快文檔列表的合併,lucene底層採用了跳錶的數據結構,合併過程中,優先遍歷較短的鏈表,去較長的列表中進行查詢,如果存在,則該文檔符合條件。
倒排索引的查詢過程
- 內存加載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更節省空間。
DocValues(正排索引&列式存儲)
倒排索引保存的是詞項到文檔的映射,也就是詞項存在於哪些文檔中,DocValues保存的是文檔到詞項的映射,也就是文檔中有哪些詞項。
相關設置
keyword字段默認開啓
ES6.0(lucene7.0)之前
DocValues採用的數據結構是bitset,bitset對於稀疏數據的支持不好:
- 對於稀疏的字段來說,絕大部分的空間都被0填充,浪費空間
- 由於字段的值之間插入了0,可能本來連續的值被0間隔開來了,不利於數據的壓縮
- 空間被一堆0佔用了,緩存中緩存的有效數據減少,查詢效率也會降低
查詢邏輯很簡單,類似於數組通過下標進行索引,因爲每個value都是固定長度,所以讀取文檔id爲N的value直接從N*固定長度位置開始讀取固定長度即可。
ES6.0(lucene7.0)
- docid的存儲的通過分片加快映射到value的查詢速度
- value存儲的時候不再給空的值分配空間
因爲value存儲的時候,空值不再分配空間,所以查詢的時候不能通過上述通過文檔id直接映射到在bitset中的偏移量來獲取對應的value,需要通過獲取docid的位置來找到對應的value的位置。
所以對於DocValues的查找,關鍵在於DocIDSet中ID的查找,如果按照簡單的鏈表的查找邏輯,那麼DocID的查找速度將會很慢。lucene7借用了RoaringBitmap的分片的思想來加快DocIDSet的查找速度:
- 分片容量爲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戰鵬弘
封面圖來源於網絡,如有侵權,請聯繫刪除