您好,登錄后才能下訂單哦!
ElasticSearch讀寫的底層原理是什么,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
ES寫入/查詢底層原理
Elasticsearch寫入數據流程
1.客戶端隨機選擇一個ES集群中的節點,發送POST/PUT請求,被選擇的節點為協調節點(coordinating node)2.協調節點查詢集群狀態信息并計算路由,將請求發送到真正處理請求的節點(primary shard所在的節點)3.包含primary shard的節點處理寫入請求,并將數據同步到包含replica shard的節點4.coordinating node收到包含primary shard的節點的響應信息,將最終結果返回給Client端
1.客戶端隨機選擇一個ES集群中的節點,發送GET請求,被選擇的節點為協調節點(coordinating node)2.協調節點查詢集群狀態信息并使用round-robin隨機輪詢算法計算出去此次請求的節點,將請求發送到真正處理請求的節點(主分片節點和副本節點隨機分配)3.處理讀請求的節點將數據返回給協調節點4.協調節點會把文檔信息返回給Client
1.客戶端發送請求到一個協調節點2.協調節點將搜索請求轉發到所有的shard對應的primary shard或replica shard也可以3.每個shard將自己的搜索結果(其實就是一些doc id),返回給協調節點,由協調節點進行數據的合并、排序、分頁等操作,產出最終結果4.接著由協調節點,根據doc id去各個節點上拉取實際的document數據,最終返回給客戶端
這里需要注意,分頁查詢,當from特別大時會造成大量無用數據返回到協調節點,謹慎使用。
1.先寫入buffer,在buffer里的時候數據是搜索不到的;同時將數據寫入translog日志文件。2.如果buffer到達閾值,或者到一定時間,ES會將buffer中的數據refresh到一個新的segment file中,但是此時數據不是直接進入segment file的磁盤文件的,而是先進入os cache的。這個過程就是refresh。3.每隔1秒鐘,es就會將buffer中的數據寫入到一個新的segment file,因此每秒鐘產生一個新的磁盤文件(segment file),這個segment file中就存儲最近1秒內buffer中寫入的數據。如果buffer里面此時沒有數據,就不會執行refresh操作;如果buffer里面有數據,默認1秒鐘執行一次refresh操作,刷入一個新的segment file中。4.操作系統里面存在操作系統緩存(os cache),數據寫入磁盤文件之前會先進入os cache,先進入操作系統級別的一個內存緩存中。只要buffer中的數據被refresh到os cache中,數據就可以被檢索到了。5.可以通過es的restful api或者java api,手動執行一次refresh操作,就是手動將buffer中的數據刷入os cache中,讓數據立馬就可以被搜索到。只要數據被輸入os cache中,buffer就會被清空了,因為不需要保留buffer了,數據在translog里面已經持久化到磁盤去一份了。
這里需要注意,分頁查詢,當from特別大時會造成大量無用數據返回到協調節點,謹慎使用。
系統層面的調優主要是內存的設定與避免交換內存。ES 安裝后默認設置的堆內存是 1GB,這很明顯是不夠的,那么接下來就會有一個問題出現:我們要設置多少內存給 ES 呢?其實這是要看我們集群節點的內存大小,還取決于我們是否在服務器節點上還是否要部署其他服務。如果內存相對很大,如 64G 及以上,并且不在 ES 集群上部署其他服務,那么建議 ES 內存可以設置為 31G-32G,因為這里有一個 32G 性能瓶頸問題,直白的說就是即使你給了 ES 集群大于 32G 的內存,其性能也不一定會更加優良,甚至會不如設置為 31G-32G 時候的性能。設置 ES 集群內存的時候,還有一點就是確保堆內存最小值(Xms)與最大值(Xmx)的大小是相同的,防止程序在運行時改變堆內存大小,這是一個很耗系統資源的過程。
禁止swap,一旦允許內存與磁盤的交換,會引起致命的性能問題。swap空間是一塊磁盤空間,操作系統使用這塊空間保存從內存中換出的操作系統不常用page數據,這樣可以分配出更多的內存做page cache。這樣通常會提升系統的吞吐量和IO性能,但同樣會產生很多問題。頁面頻繁換入換出會產生IO讀寫、操作系統中斷,這些都很影響系統的性能。這個值越大操作系統就會更加積極的使用swap空間。通過:在elasticsearch.yml 中 bootstrap.memory_lock: true, 以保持JVM鎖定內存,保證ES的性能。
ES 是一個分布式的搜索引擎, 索引通常都會分解成不同部分, 分布在不同節點的部分數據就是分片。ES 自動管理和組織分片, 并在必要的時候對分片數據進行再平衡分配, 所以用戶基本上不用擔心分片的處理細節。創建索引時默認的分片數為 5 個,并且一旦創建不能更改。
ES 默認創建一份副本,就是說在 5 個主分片的基礎上,每個主分片都相應的有一個副本分片。額外的副本有利有弊,有副本可以有更強的故障恢復能力,但也占了相應副本倍數的磁盤空間。
對于副本數,比較好確定,可以根據我們集群節點的多少與我們的存儲空間決定,我們的集群服務器多,并且有足夠大多存儲空間,可以多設置副本數,一般是 1-3 個副本數,如果集群服務器相對較少并且存儲空間沒有那么寬松,則可以只設定一份副本以保證容災(副本數可以動態調整)。
對于分片數,是比較難確定的。因為一個索引分片數一旦確定,就不能更改,所以我們在創建索引前,要充分的考慮到,以后我們創建的索引所存儲的數據量,否則創建了不合適的分片數,會對我們的性能造成很大的影響。
查詢大量小分片使得每個分片處理數據速度更快了,那是不是分片數越多,我們的查詢就越快,ES 性能就越好呢?其實也不是,因為在查詢過程中,有一個分片合并的過程,如果分片數不斷的增加,合并的時間則會增加,而且隨著更多的任務需要按順序排隊和處理,更多的小分片不一定要比查詢較小數量的更大的分片更快。如果有多個并發查詢,則有很多小碎片也會降低查詢吞吐量。
如果現在你的場景是分片數不合適了,但是又不知道如何調整,那么有一個好的解決方法就是按照時間創建索引,然后進行通配查詢。如果每天的數據量很大,則可以按天創建索引,如果是一個月積累起來導致數據量很大,則可以一個月創建一個索引。如果要對現有索引進行重新分片,則需要重建索引,對于每個index的shard數量,可以根據數據總量、寫入壓力、節點數量等綜合考量后設定,然后根據數據增長狀態定期檢測下shard數量是否合理。
騰訊云CES技術團隊的推薦方案是:對于數據量較小(100GB以下)的index,往往寫入壓力查詢壓力相對較低,一般設置35個shard,number_of_replicas設置為1即可(也就是一主一從,共兩副本)。對于數據量較大(100GB以上)的index:一般把單個shard的數據量控制在(20GB50GB)讓index壓力分攤至多個節點:可通過index.routing.allocation.total_shards_per_node參數,強制限定一個節點上該index的shard數量,讓shard盡量分配到不同節點上綜合考慮整個index的shard數量,如果shard數量(不包括副本)超過50個,就很可能引發拒絕率上升的問題,此時可考慮把該index拆分為多個獨立的index,分攤數據量,同時配合routing使用,降低每個查詢需要訪問的shard數量。
確定集群CPU占用率高的原因,使用GET_nodes/{node}/hot_threads,如果結果為elasticsearch[{node}][search][T#10]則為查詢導致,如果結果為elasticsearch[{node}][bulk][T#1],則為寫入導致。
index.merge.scheduler.max_thread_count
在實際調優中,cpu使用率很高,使用SSD替代機械硬盤。SSD與機械磁盤相比,具有高效的讀寫速度和穩定性。如果不是SSD,建議設置index.merge.scheduler.max_thread_count: 1,即索引merge最大線程數設置為1 個,該參數可以有效調節寫入的性能。因為在存儲介質上并發寫,由于尋址的原因,寫入性能不會提升,只會降低。當有多個磁盤時可以設置為對應的數量。
index.refresh_interval
這個參數的意思是數據寫入后幾秒可以被搜索到,默認是 1s。每次索引的 refresh 會產生一個新的 lucene 段, 這會導致頻繁的合并行為,如果業務需求對實時性要求沒那么高,可以將此參數調大。
indices.memory.index_buffer_size
如果我們要進行非常重的高并發寫入操作,那么最好將它調大一些,index buffer的大小是所有的shard公用的,對于每個 shard來說,最多給512MB,因為再大性能就沒什么提升了。ES會將這個設置作為每個shard共享的index buffer,那些特別活躍的shard會更多的使用這個 buffer。默認這個參數的值是10%,也就是jvm堆內存的10%。
translog
ES為了保證數據不丟失,每次index、bulk、delete、update完成的時候,一定會觸發刷新translog到磁盤上。在提高數據安全性的同時當然也降低了性能。如果你不在意這點可能性,還是希望性能優先,可以設置如下參數:
"index.translog": {
"sync_interval": "120s", #sync間隔調高
"durability": "async", # 異步更新
"flush_threshold_size":"1g" #log文件大小
}
這樣設定的意思是開啟異步寫入磁盤,并設定寫入的時間間隔與大小,有助于寫入性能的提升。
replica數目
為了讓創建的es index在每臺datanode上均勻分布,同一個datanode上同一個index的shard數目不應超過3個。計算公式: (number_of_shard*(1+number_of_replicas)) < 3*number_of_datanodes每臺機器上分配的shard數目,index.routing.allocation.total_shards_per_node: 2
merge相關參數
"index.merge.policy.floor_segment": "100mb"
"index.merge.scheduler.max_thread_count": "1"
"index.merge.policy.min_merge_size": "10mb"
超時參數
discovery.zen.ping_timeout 判斷 master 選舉過程中,發現其他 node 存活的超時設置
discovery.zen.fd.ping_interval 節點被 ping 的頻率,檢測節點是否存活
discovery.zen.fd.ping_timeout 節點存活響應的時間,默認為 30s,如果網絡可能存在隱患,可以適當調大
discovery.zen.fd.ping_retries ping 失敗/超時多少導致節點被視為失敗,默認為 3
Linux中,每個進程默認打開的最大文件句柄數是1000,對于服務器進程來說,顯然太小,通過修改/etc/security/limits.conf來增大打開最大句柄數* - nofile 65535
vm.dirty_background_ratio: 這個參數指定了當文件系統緩存臟頁數量達到系統內存百分之多少時(如5%)就會觸發pdflush/flush/kdmflush等后臺回寫進程運行,將一定緩存的臟頁異步地刷入外存;
vm.dirty_ratio: 該參數則指定了當文件系統緩存臟頁數量達到系統內存百分之多少時(如10%),系統不得不開始處理緩存臟頁(因為此時臟頁數量已經比較多,為了避免數據丟失需要將一定臟頁刷入外存);在此過程中很多應用進程可能會因為系統轉而處理文件IO而阻塞。
把該參數適當調小。如果cached的臟數據所占比例(這里是占MemTotal的比例)超過這個設置,系統會停止所有的應用層的IO寫操作,等待刷完數據后恢復IO。所以萬一觸發了系統的這個操作,對于用戶來說影響非常大的。
sysctl -w vm.dirty_ratio=10
sysctl -w vm.dirty_background_ratio=5
可以修改/etc/sysctl.conf文件進行持久化。
讀數據
避免大結果集和深翻,ES 提供了 Scroll 和 Scroll-Scan 這兩種查詢模式。
Scroll:是為檢索大量的結果而設計的。例如,我們需要查詢 1~100 頁的數據,每頁 100 條數據。
如果使用Search查詢:每次都需要在每個分片上查詢得分最高的 from+100 條數據,然后協同節點把收集到的 n×(from+100)條數據聚合起來再進行一次排序。
每次返回from+1開始的100條數據,并且要重復執行100次。
如果使用Scroll查詢:在各個分片上查詢10000條數據,協同節點聚合n×10000條數據進行合并、排序,并將排名前10000的結果快照起來。這樣做的好處是減少了查詢和排序的次數。
插入索引自動生成 id
當寫入端使用特定的 id 將數據寫入 ES 時,ES 會檢查對應的索引下是否存在相同的 id,這個操作會隨著文檔數量的增加使消耗越來越大,所以如果業務上沒有硬性需求建議使用 ES 自動生成的 id,加快寫入速率。
避免稀疏索引
索引稀疏之后,會導致索引文件增大。ES的keyword,數組類型采用doc_values結構,即使字段是空值,每個文檔也會占用一定的空間,所以稀疏索引會造成磁盤增大,導致查詢和寫入效率降低。
index.merge.scheduler.max_thread_count:1 # 索引 merge 最大線程數
indices.memory.index_buffer_size:30% # 內存
index.translog.durability:async # 這個可以異步寫硬盤,增大寫的速度
index.translog.sync_interval:120s #translog 間隔時間
discovery.zen.ping_timeout:120s # 心跳超時時間
discovery.zen.fd.ping_interval:120s # 節點檢測時間
discovery.zen.fd.ping_timeout:120s #ping 超時時間
discovery.zen.fd.ping_retries:6 # 心跳重試次數
thread_pool.bulk.size:20 # 寫入線程個數 由于我們查詢線程都是在代碼里設定好的,我這里只調節了寫入的線程數
thread_pool.bulk.queue_size:1000 # 寫入線程隊列大小
index.refresh_interval:300s #index 刷新間隔
bootstrap.memory_lock: true#以保持JVM鎖定內存,保證ES的性能。
用bulk批量寫入
你如果要往es里面灌入數據的話,那么根據你的業務場景來,如果你的業務場景可以支持讓你將一批數據聚合起來,一次性寫入es,那么就盡量采用bulk的方式,每次批量寫個幾百條這樣子。
bulk批量寫入的性能比你一條一條寫入大量的document的性能要好很多。但是如果要知道一個bulk請求最佳的大小,需要對單個es node的單個shard做壓測。先bulk寫入100個document,然后200個,400個,以此類推,每次都將bulk size加倍一次。如果bulk寫入性能開始變平緩的時候,那么這個就是最佳的bulk大小。并不是bulk size越大越好,而是根據你的集群等環境具體要測試出來的,因為越大的bulk size會導致內存壓力過大,因此最好一個請求不要發送超過10mb的數據量。
先確定一個是bulk size,此時就盡量是單線程,一個es node,一個shard,進行測試。看看單線程最多一次性寫多少條數據,性能是比較好的。
使用多線程將數據寫入es
單線程發送bulk請求是無法最大化es集群寫入的吞吐量的。如果要利用集群的所有資源,就需要使用多線程并發將數據bulk寫入集群中。為了更好的利用集群的資源,這樣多線程并發寫入,可以減少每次底層磁盤fsync的次數和開銷。首先對單個es節點的單個shard做壓測,比如說,先是2個線程,然后是4個線程,然后是8個線程,16個,每次線程數量倍增。一旦發現es返回了TOO_MANY_REQUESTS的錯誤,JavaClient也就是EsRejectedExecutionException。此時那么就說明es是說已經到了一個并發寫入的最大瓶頸了,此時我們就知道最多只能支撐這么高的并發寫入了。
增加refresh間隔
默認的refresh間隔是1s,用index.refresh_interval參數可以設置,這樣會其強迫es每秒中都將內存中的數據寫入磁盤中,創建一個新的segment file。正是這個間隔,讓我們每次寫入數據后,1s以后才能看到。但是如果我們將這個間隔調大,比如30s,可以接受寫入的數據30s后才看到,那么我們就可以獲取更大的寫入吞吐量,因為30s內都是寫內存的,每隔30s才會創建一個segment file。
禁止refresh和replia
如果我們要一次性加載大批量的數據進es,可以先禁止refresh和replia復制,將index.refresh_interval設置為-1,將index.number_of_replicas設置為0即可。這可能會導致我們的數據丟失,因為沒有refresh和replica機制了。但是不需要創建segment file,也不需要將數據replica復制到其他的replica shasrd上面去。此時寫入的速度會非常快,一旦寫完之后,可以將refresh和replica修改回正常的狀態。
禁止swapping交換內存
如果要將es jvm內存交換到磁盤,再交換回內存,大量磁盤IO,性能很差
給filesystem cache更多的內存
filesystem cache被用來執行更多的IO操作,如果我們能給filesystemcache更多的內存資源,那么es的寫入性能會好很多。
使用自動生成的id
如果我們要手動給es document設置一個id,那么es需要每次都去確認一下那個id是否存在,這個過程是比較耗費時間的。如果我們使用自動生成的id,那么es就可以跳過這個步驟,寫入性能會更好。對于你的業務中的表id,可以作為es document的一個field。
用性能更好的硬件
我們可以給filesystem cache更多的內存,也可以使用SSD替代機械硬盤,避免使用NAS等網絡存儲,考慮使用RAID 0來條帶化存儲提升磁盤并行讀寫效率,等等。
index buffer
如果我們要進行非常重的高并發寫入操作,那么最好將index buffer調大一些,indices.memory.index_buffer_size,這個可以調節大一些,設置的這個index buffer大小,是所有的shard公用的,但是如果除以shard數量以后,算出來平均每個shard可以使用的內存大小,一般建議,但是對于每個shard來說,最多給512mb,因為再大性能就沒什么提升了。es會將這個設置作為每個shard共享的index buffer,那些特別活躍的shard會更多的使用這個buffer。默認這個參數的值是10%,也就是jvm heap的10%,如果我們給jvmheap分配10gb內存,那么這個index buffer就有1gb,對于兩個shard共享來說,是足夠的了。
1.query_string 或 multi_match的查詢字段越多, 查詢越慢。可以在mapping階段,利用copy_to屬性將多字段的值索引到一個新字段,multi_match時,用新的字段查詢。2.日期字段的查詢, 尤其是用now 的查詢實際上是不存在緩存的,因此, 可以從業務的角度來考慮是否一定要用now, 畢竟利用query cache 是能夠大大提高查詢效率的。3.查詢結果集的大小不能隨意設置成大得離譜的值, 如query.setSize不能設置成 Integer.MAX_VALUE, 因為ES內部需要建立一個數據結構來放指定大小的結果集數據。4.盡量避免使用script,萬不得已需要使用的話,選擇painless & experssions 引擎。一旦使用script查詢,一定要注意控制返回,千萬不要有死循環(如下錯誤的例子),因為ES沒有腳本運行的超時控制,只要當前的腳本沒執行完,該查詢會一直阻塞。
如:{
“script_fields”:{
“test1”:{
“lang”:“groovy”,
“script”:“while(true){print 'don’t use script'}”
}
}
}
5. 避免層級過深的聚合查詢, 層級過深的group by , 會導致內存、CPU消耗,建議在服務層通過程序來組裝業務,也可以通過pipeline的方式來優化。
6. 復用預索引數據方式來提高AGG性能:
如通過 terms aggregations 替代 range aggregations, 如要根據年齡來分組,分組目標是: 少年(14歲以下) 青年(14-28) 中年(29-50) 老年(51以上), 可以在索引的時候設置一個age_group字段,預先將數據進行分類。從而不用按age來做range aggregations, 通過age_group字段就可以了。
7. Cache的設置及使用:
a) QueryCache: ES查詢的時候,使用filter查詢會使用query cache, 如果業務場景中的過濾查詢比較多,建議將querycache設置大一些,以提高查詢速度。
indices.queries.cache.size:10%(默認),可設置成百分比,也可設置成具體值,如256mb。
當然也可以禁用查詢緩存(默認是開啟), 通過index.queries.cache.enabled:false設置。
b) FieldDataCache: 在聚類或排序時,field data cache會使用頻繁,因此,設置字段數據緩存的大小,在聚類或排序場景較多的情形下很有必要,可通過indices.fielddata.cache.size:30% 或具體值10GB來設置。但是如果場景或數據變更比較頻繁,設置cache并不是好的做法,因為緩存加載的開銷也是特別大的。
c) ShardRequestCache: 查詢請求發起后,每個分片會將結果返回給協調節點(Coordinating Node), 由協調節點將結果整合。
如果有需求,可以設置開啟; 通過設置index.requests.cache.enable: true來開啟。
不過,shard request cache只緩存hits.total, aggregations, suggestions類型的數據,并不會緩存hits的內容。也可以通過設置indices.requests.cache.size: 1%(默認)來控制緩存空間大小。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。