ELK 性能優化實踐


文章轉載自:https://mp.weixin.qq.com/s?__biz=MzI5MTU1MzM3MQ==&mid=2247489814&idx=1&sn=6916f8b79019d6288ec73d541071bf70&chksm=ec0fb022db783934f9fd046c71192e4ec5939d02251790ed4bb0bd39625a086c571c1f8350ec&scene=132#wechat_redirect

一、背景介紹

近一年內對公司的 ELK 日志系統做過性能優化,也對 SkyWalking 使用的 ES 存儲進行過性能優化,在此做一些總結。本篇主要是講 ES 在 ELK 架構中作為日志存儲時的性能優化方案。
ELK 架構作為日志存儲方案
圖片

二、現狀分析

  1. 版本及硬件配置

JDK:JDK1.8_171-b11 (64 位)
ES集群:由3台16核32G的虛擬機部署 ES 集群,每個節點分配 20 G 堆內存
ELK版本:6.3.0
垃圾回收器:ES 默認指定的老年代(CMS)+ 新生代(ParNew)
操作系統:CentOS Linux release 7.4.1708(Core)

  1. 性能問題

隨着接入 ELK 的應用越來越多,每日新增索引約 230 個,新增 document 約 3000 萬到 5000 萬。

每日上午和下午是日志上傳高峰期,在 Kibana 上查看日志,發現問題:

(1) 日志會有 5-40 分鍾的延遲
(2) 有很多日志丟失,無法查到

  1. 問題分析
    3.1 日志延遲
    首先了解清楚:數據什么時候可以被查到?

數據先是存放在 ES 的內存 buffer,然后執行 refresh 操作寫入到操作系統的內存緩存 os cache,此后數據就可以被搜索到。

所以,日志延遲可能是我們的數據積壓在 buffer 中沒有進入 os cache 。
3.2 日志丟失

查看日志發現很多 write 拒絕執行的情況

從日志中可以看出 ES 的 write 線程池已經滿負荷,執行任務的線程已經達到最大 16 個線程,而 200 容量的隊列也已經放不下新的 task。

查看線程池的情況也可以看出 write 線程池有很多寫入的任務

GET /_cat/thread_pool?v&h=host,name,type,size,active,largest,rejected,completed,queue,queue_size

所以我們需要優化 ES 的 write 的性能。
4.解決思路
4.1 分析場景

ES 的優化分為很多方面,我們要根據使用場景考慮對 ES 的要求。

根據個人實踐經驗,列舉三種不同場景下的特點:

SkyWalking:一般配套使用 ES 作為數據存儲,存儲鏈路追蹤數據、指標數據等信息。
ELK:一般用來存儲系統日志,並進行分析,搜索,定位應用的問題。
全文搜索的業務:業務中常用 ES 作為全文搜索引擎,例如在外賣應用中,ES 用來存儲商家、美食的業務數據,用戶在客戶端可以根據關鍵字、地理位置等查詢條件搜索商家、美食信息。

這三類場景的特點如下:

SkyWalking	ELK	全文搜索的業務

並發寫 高並發寫 高並發寫 並發一般不高
並發讀 並發低 並發低 並發高
實時性要求 5分鍾以內 30秒以內 1分鍾內
數據完整性 可容忍丟失少量數據 可容忍丟失少量數據 數據盡量100%不丟失

關於實時性

SkyWalking 在實際使用中,一般使用頻率不太高,往往是發現應用的問題后,再去 SkyWalking 查歷史鏈路追蹤數據或指標數據,所以可以接受幾分鍾的延遲。
ELK 不管是開發、測試等階段,時常用來定位應用的問題,如果不能快速查詢出數據,延遲太久,會耽誤很多時間,大大降低工作效率;如果是查日志定位生產問題,那更是刻不容緩。
全文搜索的業務中一般可以接受在1分鍾內查看到最新數據,比如新商品上架一分鍾后才看到,但盡量實時,在幾秒內可以可看到。

4.2 優化的方向

可以從三方面進行優化:JVM 性能調優、ES 性能調優、控制數據來源
三、ES性能優化

可以從三方面進行優化:JVM 性能調優、ES 性能調優、控制數據來源

  1. JVM調優

第一步是 JVM 調優。

因為 ES 是依賴於 JVM 運行,沒有合理的設置 JVM 參數,將浪費資源,甚至導致 ES 很容易 OOM 而崩潰。
1.1 監控 JVM 運行情況

(1)查看 GC 日志

問題:Young GC 和 Full GC 都很頻繁,特別是 Young GC 頻率高,累積耗時非常多。

(2) 使用 jstat 看下每秒的 GC 情況

參數說明

S0:幸存1區當前使用比例
S1:幸存2區當前使用比例
E:伊甸園區使用比例
O:老年代使用比例
M:元數據區使用比例
CCS:壓縮使用比例
YGC:年輕代垃圾回收次數
FGC:老年代垃圾回收次數
FGCT:老年代垃圾回收消耗時間
GCT:垃圾回收消耗總時間問題:從 jstat gc 中也可以看出,每秒的 eden 增長速度非常快,很快就滿了。

1.2 定位 Young GC 頻繁的原因
1.2.1 檢查是否新生代的空間是否太小

用下面幾種方式都可查看新、老年代內存大小 (1) 使用 jstat -gc pid 查看 Eden 區、老年代空間大小 (2) 使用 jmap -heap pid 查看 Eden 區、老年代空間大小 (3) 查看 GC 日志中的 GC 明細

其中 996800K 為新生代可用空間大小,即 Eden 區 +1 個 Survivor 區的空間大小,所以新生代總內存是996800K/0.9, 約1081M

上面的幾種方式都查詢出,新生代總內存約1081M,即1G左右;老年代總內存為19864000K,約19G。新、老比例約1:19,出乎意料。
1.2.1 新老年代空間比例為什么不是 JDK 默認的1:2【重點!】

這真是一個容易踩坑的地方。如果沒有顯示設置新生代大小,JVM 在使用 CMS 收集器時會自動調參,新生代的大小在沒有設置的情況下是通過計算得出的,其大小可能與 NewRatio 的默認配置沒什么關系而與 ParallelGCThreads 的配置有一定的關系。

參考文末鏈接:CMS GC 默認新生代是多大?

所以:新生代大小有不確定性,最好配置 JVM 參數 -XX:NewSize、-XX:MaxNewSize 或者 -xmn ,免得遇到一些奇怪的 GC,讓人措手不及。
1.3 上面現象造成的影響

新生代過小,老年代過大的影響

新生代過小: (1) 會導致新生代 Eden 區很快用完,而觸發 Young GC,Young GC 的過程中會 STW(Stop The World),也就是所有工作線程停止,只有 GC 的線程在進行垃圾回收,這會導致 ES 短時間停頓。頻繁的 Young GC,積少成多,對系統性能影響較大。(2) 大部分對象很快進入老年代,老年代很容易用完而觸發 Full GC。
老年代過大:會導致 Full GC 的執行時間過長,Full GC 雖然有並行處理的步驟,但是還是比 Young GC 的 STW 時間更久,而 GC 導致的停頓時間在幾十毫秒到幾秒內,很影響 ES 的性能,同時也會導致請求 ES 服務端的客戶端在一定時間內沒有響應而發生 timeout 異常,導致請求失敗。

1.4 JVM優化
1.4.1 配置堆內存空間大小

32G 的內存,分配 20G 給堆內存是不妥當的,所以調整為總內存的50%,即16G。修改 elasticsearch 的 jvm.options 文件

-Xms16g
-Xmx16g

設置要求:
Xms 與 Xmx 大小相同。

在 jvm 的參數中 -Xms 和 -Xmx 設置的不一致,在初始化時只會初始 -Xms 大小的空間存儲信息,每當空間不夠用時再向操作系統申請,這樣的話必然要進行一次 GC,GC會帶來 STW。而剩余空間很多時,會觸發縮容。再次不夠用時再擴容,如此反復,這些過程會影響系統性能。同理在 MetaSpace 區也有類似的問題。

jvm 建議不要超過 32G,否則 jvm 會禁用內存對象指針壓縮技術,造成內存浪費

Xmx 和 Xms 不要超過物理 RAM 的50%。參考文末:官方堆內存設置的建議

Xmx 和 Xms 不要超過物理內存的50%。Elasticsearch 需要內存用於JVM堆以外的其他用途,為此留出空間非常重要。例如,Elasticsearch 使用堆外緩沖區進行有效的網絡通信,依靠操作系統的文件系統緩存來高效地訪問文件,而 JVM 本身也需要一些內存。

1.4.2 配置堆內存新生代空間大小

因為指定新生代空間大小,導致 JVM 自動調參只分配了 1G 內存給新生代。

修改 elasticsearch 的 jvm.options 文件,加上

-XX:NewSize=8G
-XX:MaxNewSize=8G

老年代則自動分配 16G-8G=8G 內存,新生代老年代的比例為 1:1。修改后每次 Young GC 頻率更低,且每次 GC 后只有少數數據會進入老年代。
2.3 使用G1垃圾回收器(未實踐)

G1垃圾回收器讓系統使用者來設定垃圾回收堆系統的影響,然后把內存拆分為大量的小 Region,追蹤每個 Region 中可以回收的對象大小和回收完成的預計花費的時間, 最后在垃圾回收的時候,盡量把垃圾回收對系統造成的影響控制在我們指定的時間范圍內,同時在有限的時間內盡量回收更多的垃圾對象。G1垃圾回收器一般在大數量、大內存的情況下有更好的性能。

ES默認使用的垃圾回收器是:老年代(CMS)+ 新生代(ParNew)。如果是JDK1.9,ES 默認使用 G1 垃圾回收器。

因為使用的是 JDK1.8,所以並未切換垃圾回收器。后續如果再有性能問題再切換G1垃圾回收器,測試是否有更好的性能。
1.5 優化的效果
1.5.1 新生代使用內存的增長率更低

優化前

每秒打印一次 GC 數據。可以看出,年輕代增長速度很快,幾秒鍾年輕代就滿了,導致 Young GC 觸發很頻繁,幾秒鍾就會觸發一次。而每次 Young GC 很大可能有存活對象進入老年代,而且,存活對象多的時候(看上圖中第一個紅框中的old gc數據),有(51.44-51.08)/100 * 19000M = 約68M。每次進入老年代的對象較多,加上頻繁的 Young GC,會導致新老年代的分代模式失去了作用,相當於老年代取代了新生代來存放近期內生成的對象。當老年代滿了,觸發 Full GC,存活的對象也會很多,因為這些對象很可能還是近期加入的,還存活着,所以一次 Full GC 回收對象不多。而這會惡性循環,老年代很快又滿了,又 Full GC,又殘留一大部分存活的,又很容易滿了,所以導致一直頻繁 Full GC。

優化后

每秒打印一次 GC 數據。可以看出,新生代增長速度慢了許多,至少要 60 秒才會滿,如上圖紅框中數據,進入老年代的對象約(15.68-15.60)/100 * 10000 = 8M,非常的少。所以要很久才會觸發一次 Full GC 。而且等到 Full GC 時,老年代里很多對象都是存活了很久的,一般都是不會被引用,所以很大一部分會被回收掉,留一個比較干凈的老年代空間,可以繼續放很多對象。
1.5.2 新生代和老年代 GC 頻率更低

ES 啟動后,運行14個小時

優化前

Young GC 每次的時間是不長的,從上面監控數據中可以看出每次GC時長 1467.995/27276 約等於 0.05 秒。那一秒鍾有多少時間是在處理 Young GC ?

計算公式:1467 秒/( 60 秒× 60 分 14 小時)= 約 0.028 秒,也就是 100 秒中就有 2.8 秒在Young GC,也就是有 2.8S 的停頓,這對性能還是有很大消耗的。同時也可以算出多久一次 Young GC, 方程是:60秒×60分*14小時/ 27276次 = 1次/X秒,計算得出X = 0.54,也就是 0.54 秒就會有一次 Young GC,可見 Young GC 頻率非常頻繁。

優化后

Young GC 次數只有修改前的十分之一,Young GC 時間也是約八分之一。Full GC 的次數也是只有原來的八分之一,GC 時間大約是四分之一。

GC 對系統的影響大大降低,性能已經得到很大的提升。
2.ES 調優

上面已經分析過 ES 作為日志存儲時的特性是:高並發寫、讀少、接受 30 秒內的延時、可容忍部分日志數據丟失。下面我們針對這些特性對ES進行調優。
2.1 優化 ES 索引設置
2.2.1 ES 寫數據底層原理

refresh ES 接收數據請求時先存入 ES 的內存中,默認每隔一秒會從內存 buffer 中將數據寫入操作系統緩存 os cache,這個過程叫做 refresh;

到了 os cache 數據就能被搜索到(所以我們才說 ES 是近實時的,因為 1 s 的延遲后執行 refresh 便可讓數據被搜索到)

fsync translog 會每隔 5 秒或者在一個變更請求完成之后執行一次 fsync 操作,將 translog 從緩存刷入磁盤,這個操作比較耗時,如果對數據一致性要求不是很高時建議將索引改為異步,如果節點宕機時會有5秒數據丟失;

flush ES 默認每隔30分鍾會將 os cache 中的數據刷入磁盤同時清空 translog 日志文件,這個過程叫做 flush。

merge

ES 的一個 index 由多個 shard 組成,而一個 shard 其實就是一個 Lucene 的 index ,它又由多個 segment 組成,且 Lucene 會不斷地把一些小的 segment 合並成一個大的 segment ,這個過程被稱為段merge(參考文末鏈接)。執行索引操作時,ES 會先生成小的segment,ES 有離線的邏輯對小的 segment 進行合並,優化查詢性能。但是合並過程中會消耗較多磁盤 IO,會影響查詢性能。
2.2.2 優化方向
2.2.2.1 優化 fsync

為了保證不丟失數據,就要保護 translog 文件的安全:

Elasticsearch 2.0 之后, 每次寫請求(如 index 、delete、update、bulk 等)完成時, 都會觸發fsync將 translog 中的 segment 刷到磁盤, 然后才會返回200 OK的響應;

或者: 默認每隔5s就將 translog 中的數據通過fsync強制刷新到磁盤.

該方式提高數據安全性的同時, 降低了一點性能.

==> 頻繁地執行 fsync 操作, 可能會產生阻塞導致部分操作耗時較久. 如果允許部分數據丟失, 可設置異步刷新 translog 來提高效率,還有降低 flush 的閥值,優化如下:

"index.translog.durability": "async",
"index.translog.flush_threshold_size":"1024mb",
"index.translog.sync_interval": "120s"

2.2.2.2 優化 refresh

寫入 Lucene 的數據,並不是實時可搜索的,ES 必須通過 refresh 的過程把內存中的數據轉換成 Lucene 的完整 segment 后,才可以被搜索。

默認 1秒后,寫入的數據可以很快被查詢到,但勢必會產生大量的 segment,檢索性能會受到影響。所以,加大時長可以降低系統開銷。對於日志搜索來說,實時性要求不是那么高,設置為 5 秒或者 10s;對於 SkyWalking,實時性要求更低一些,我們可以設置為 30s。

設置如下:

"index.refresh_interval":"5s"

2.2.2.3 優化 merge

index.merge.scheduler.max_thread_count 控制並發的 merge 線程數,如果存儲是並發性能較好的 SSD,可以用系統默認的 max(1, min(4, availableProcessors / 2)),當節點配置的 cpu 核數較高時,merge 占用的資源可能會偏高,影響集群的性能,普通磁盤的話設為1,發生磁盤 IO 堵塞。設置max_thread_count 后,會有 max_thread_count + 2 個線程同時進行磁盤操作,也就是設置為 1 允許3個線程。

設置如下:

"index.merge.scheduler.max_thread_count":"1"

2.2.2 優化設置
2.2.2.1 對現有索引做索引設置

# 需要先 close 索引,然后再執行,最后成功之后再打開
# 關閉索引
curl -XPOST 'http://localhost:9200/_all/_close'

# 修改索引設置
curl -XPUT -H "Content-Type:application/json" 'http://localhost:9200/_all/_settings?preserve_existing=true' -d '{"index.merge.scheduler.max_thread_count" : "1","index.refresh_interval" : "10s","index.translog.durability" : "async","index.translog.flush_threshold_size":"1024mb","index.translog.sync_interval" : "120s"}'

# 打開索引
curl -XPOST 'http://localhost:9200/_all/_open'

該方式可對已經生成的索引做修改,但是對於后續新建的索引不生效,所以我們可以制作 ES 模板,新建的索引按模板創建索引。
2.2.2.2 制作索引模板

制作模板 大部分索引都是業務應用的日志相關的索引,且索引名稱是 202* 這種帶着日期的格式

PUT _template/business_log
{
  "index_patterns": ["*202*.*.*"],
  "settings": {
  "index.merge.scheduler.max_thread_count" : "1","index.refresh_interval" : "5s","index.translog.durability" : "async","index.translog.flush_threshold_size":"1024mb","index.translog.sync_interval" : "120s"}
}

# 查詢模板是否創建成功
GET _template/business_log

因為我們的業務日志是按天維度創建索引,索引名稱示例:user-service-prod-2020.12.12,所以用通配符*202..**匹配對應要創建的業務日志索引。
2.2 優化線程池配置

前文已經提到過,write 線程池滿負荷,導致拒絕任務,而有的數據無法寫入。

而經過上面的優化后,拒絕的情況少了很多,但是還是有拒絕任務的情況。

所以我們還需要優化 write 線程池。

從 prometheus 監控中可以看到線程池的情況:

為了更直觀看到 ES 線程池的運行情況,我們安裝了 elasticsearch_exporter 收集 ES 的指標數據到 prometheus,再通過 grafana 進行查看。

經過上面的各種優化,拒絕的數據量少了很多,但是還是存在拒絕的情況,如下圖:

write 線程池如何設置:

參考文末鏈接:ElasticSearch線程池

write

For single-document index/delete/update and bulk requests. Thread pool type is fixed with a size of # of available processors, queue_size of 200. The maximum size for this pool is 1 + # of available processors.

write 線程池采用 fixed 類型的線程池,也就是核心線程數與最大線程數值相同。線程數默認等於 cpu 核數,可設置的最大值只能是 cpu 核數加 1,也就是 16 核 CPU,能設置的線程數最大值為 17。

優化的方案:

線程數改為 17,也就是 cpu 總核數加 1
隊列容量加大。隊列在此時的作用是消峰。不過隊列容量加大本身不會提升處理速度,只是起到緩沖作用。此外,隊列容量也不能太大,否則積壓很多任務時會占用過多堆內存。

config/elasticsearch.yml文件增加配置

# 線程數設置
thread_pool:
  write:
    # 線程數默認等於cpu核數,即16  
    size: 17
    # 因為任務多時存在任務拒絕的情況,所以加大隊列大小,可以在間歇性任務量陡增的情況下,緩存任務在隊列,等高峰過去逐步消費完。
    queue_size: 10000

優化后效果

可以看到,已經沒有拒絕的情況,這樣也就是解決了日志丟失的問題。
2.3 鎖定內存,不讓 JVM 使用 Swap

Swap 交換分區:

當系統的物理內存不夠用的時候,就需要將物理內存中的一部分空間釋放出來,以供當前運行的程序使用。那些被釋放的空間可能來自一些很長時間沒有什么操作的程序,**這些被釋放的空間被臨時保存到 Swap 中,等到那些程序要運行時,再從 Swap 中恢復保存的數據到內存中。**這樣,系統總是在物理內存不夠時,才進行 Swap 交換。

參考文末鏈接:ElasticSearch官方解釋為什么要禁用交換內存

Swap 交換分區對性能和節點穩定性非常不利,一定要禁用。它會導致垃圾回收持續幾分鍾而不是幾毫秒,並會導致節點響應緩慢,甚至與集群斷開連接。

有三種方式可以實現 ES 不使用 Swap 分區
2.3.1 Linux 系統中的關閉 Swap (臨時有效)

執行命令

sudo swapoff -a

可以臨時禁用 Swap 內存,但是操作系統重啟后失效
2.3.2 Linux 系統中的盡可能減少 Swap 的使用(永久有效)

執行下列命令

echo "vm.swappiness = 1">> /etc/sysctl.conf

正常情況下不會使用 Swap,除非緊急情況下才會 Swap。
2.3.3 啟用 bootstrap.memory_lock

config/elasticsearch.yml 文件增加配置

#鎖定內存,不讓 JVM 寫入 Swap,避免降低 ES 的性能
bootstrap.memory_lock: true

2.4 減少分片數、副本數

分片

索引的大小取決於分片與段的大小,分片過小,可能導致段過小,進而導致開銷增加;分片過大可能導致分片頻繁 Merge,產生大量 IO 操作,影響寫入性能。

因為我們每個索引的大小在 15G 以下,而默認是 5 個分片,沒有必要這么多,所以調整為 3 個。

"index.number_of_shards": "3"

分片的設置我們也可以配置在索引模板。

副本數

減少集群副本分片數,過多副本會導致 ES 內部寫擴大。副本數默認為 1,如果某索引所在的 1 個節點宕機,擁有副本的另一台機器擁有索引備份數據,可以讓索引數據正常使用。但是數據寫入副本會影響寫入性能。對於日志數據,有 1 個副本即可。對於大數據量的索引,可以設置副本數為0,減少對性能的影響。

"index.number_of_replicas": "1"

分片的設置我們也可以配置在索引模板。
3.控制數據來源
3.1 應用按規范打印日志

有的應用 1 天生成 10G 日志,而一般的應用只有幾百到 1G。一天生成 10G 日志一般是因為部分應用日志使用不當,很多大數量的日志可以不打,比如大數據量的列表查詢接口、報表數據、debug 級別日志等數據是不用上傳到日志服務器,這些即影響日志存儲的性能,更影響應用自身性能。
四、ES性能優化后的效果

優化后的兩周內 ELK 性能良好,沒有使用上的問題:
ES 數據不再丟失
數據延時在 10 秒之內,一般在 5 秒可以查出

每個 ES 節點負載比較穩定,CPU 和內存使用率都不會過高,如下圖

五、參考文檔

參考

記一次 ElasticSearch 優化總結: https://juejin.cn/post/6844903689480568840
ElasticSearch 的數據寫入流程及優化: https://www.cnblogs.com/zhangan/p/11231990.html
百億級實時計算系統性能優化–—ElasticSearch篇 : https://www.cnblogs.com/qcloud1001/p/14068642.html
CMS GC 默認新生代是多大?: https://www.jianshu.com/p/832fc4d4cb53
官方堆內存設置的建議: https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html#heap-size-settings
ElasticSearch線程池: https://www.elastic.co/guide/en/elasticsearch/reference/6.3/modules-threadpool.html#modules-threadpool
ElasticSearch官方解釋為什么要禁用交換內存: https://www.elastic.co/guide/en/elasticsearch/reference/6.3/setup-configuration-memory.html
段 merge: https://www.elastic.co/guide/en/elasticsearch/reference/6.3/index-modules-merge.html
https://stackoverflow.com/questions/15426441/understanding-segments-in-elasticsearch


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM