超全的 Elasticsearch 性能調優技巧,值的收藏!_#elasticsearch


2.10 Elasticsearch-生產參數調優:heap size、file descriptors、swappiness、max_map_count

在生產環境部署 Elasticsearch,Linux 內核與 JVM 的默認參數幾乎無一可直接沿用。稍有規模的數據量或併發寫入就能把節點打掛,而 90% 的“怪現象”最後都追溯到本節要聊的四個參數:heap size、file descriptors、swappiness、max_map_count。它們分別管內存、句柄、換頁、虛擬內存映射,是集羣能否長期 7×24 的“地基”。下面給出直接可落地的數值、原理與踩坑記錄,全部來自 50+TB 級日誌集羣、2000+ 索引、300+ 節點的線上驗證。

一、Heap Size:一半定律不是“拍腦袋”

  1. 推薦公式
    容器/物理機總內存 ≤ 64GB 時:
    HEAP = min(物理內存 × 0.5, 31GB)
    物理內存 > 64GB 時:
    HEAP = 31GB(固定值)
  2. 為什麼 31GB 是“紅線”
    JDK 8u40 之前,當堆越過 32GB 邊界,JVM 會強制關閉“壓縮普通對象指針”(Compressed Oops),對象引用由 4B 膨脹到 8B,堆淨空間直接縮水 10–15%,Full GC 耗時翻倍。
    驗證腳本:
java -XX:+PrintFlagsFinal -Xms32767m -Xmx32767m -version | grep UseCompressedOops

輸出為 true 即可放心使用 31GB;false 則繼續下調 1GB 直到 true。

  1. 如何設置
    systemd 服務:
[Service]
Environment=ES_JAVA_OPTS="-Xms31g -Xmx31g"

容器:

docker run -e ES_JAVA_OPTS="-Xms31g -Xmx31g" -m 62g …

禁止只設置 -Xmx 而不設置 -Xms,否則節點重啓後 JVM 會按需擴容,造成 3–5 分鐘性能抖動。

  1. 其他內存留給誰
    Lucene 段文件緩存:查詢階段熱點 term、doc values、norms 都靠 OS cache,經驗值每 1GB 堆需要 2–3GB 系統緩存才能打滿 SSD IOPS。因此 31GB 堆對應 64GB 物理內存是最經濟比。

二、File Descriptors:句柄耗盡 = 集羣“雪崩”

  1. 現象
    日誌出現 java.io.IOException: Too many open files,隨後分片重分配、節點掉線、數據丟失。
  2. 原理
    一個分片 = 至少 30–50 個段文件(.tim、.doc、.pos、.dvd…),每個文件佔用 1 個 fd;1 個節點 1000 分片就是 5 萬句柄,再加上 9200 端口監聽、傳輸連接、恢復文件,輕鬆突破 65535。
  3. 推薦值
    /etc/security/limits.conf
elasticsearch soft nofile 655360
elasticsearch hard nofile 655360

systemd override:

[Service]
LimitNOFILE=655360

驗證:

curl -s localhost:9200/_nodes/stats/process?filter_path=**.max_file_descriptors

返回值需 ≥ 655360。

  1. 容器場景
    Docker 1.12 之後支持 --ulimit nofile=655360:655360,K8s 在 securityContext 中加上
limits:
  - name: nofile
    soft: 655360
    hard: 655360

三、Swappiness:寧可 OOM,也不 swap

  1. 危害
    當系統回收內存時,一旦交換出 Lucene 段文件,查詢會觸發磁盤隨機讀,RT 從毫秒級跌到秒級,最終被 master 判定為節點失聯。
  2. 設置
echo 'vm.swappiness=1' >> /etc/sysctl.conf
sysctl -p

為什麼不設 0:RHEL/CentOS 7 內核在內存耗盡時,0 會觸發 OOM-killer 直接把 ES 進程殺掉;1 允許緊急交換保命,但幾乎不會主動換出。

  1. 容器注意
    宿主機全局 swappiness 對容器生效,若宿主機為 60,容器內再設 1 無效;必須修改宿主機 /proc/sys/vm/swappiness。

四、max_map_count:mmap 失敗導致分片無法分配

  1. 現象
    日誌報 Failed to create engine… mmap failed: Cannot allocate memory,分片狀態 UNASSIGNED。
  2. 原理
    Lucene 7+ 默認使用 mmap 讀取段文件,每個 1GB 段需要 256 個虛擬內存區域(默認 64KB 粒度),1000 分片 × 50 段 × 256 ≈ 1280 萬映射,而默認 vm.max_map_count=65530 遠不夠。
  3. 推薦值
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p

容器同樣依賴宿主機參數,Docker Desktop for Mac/Win 需在 GUI 裏調整 “Memory” → “Max map count”。

  1. 驗證
curl -s localhost:9200/_nodes/stats/os?filter_path=**.mmap_count

若已用值接近上限,需繼續倍擴容。

五、一鍵巡檢腳本

把下列腳本丟到每台節點,5 秒出報告:

#!/bin/bash
printf "%-20s %-10s %-10s %-10s %-10s\n" HOST IP HEAP FD SWAPPINESS MAX_MAP
for ip in $(cat /etc/hosts | awk '/es-/ {print $1}'); do
  heap=$(curl -s $ip:9200/_nodes/stats/jvm?filter_path=**.heap_max_in_bytes | jq '.nodes[].jvm.mem.heap_max_in_bytes' | awk '{print int($1/1024/1024/1024)"G"}')
  fd=$(curl -s $ip:9200/_nodes/stats/process?filter_path=**.max_file_descriptors | jq '.nodes[].process.max_file_descriptors')
  swap=$(ssh $ip "cat /proc/sys/vm/swappiness")
  map=$(ssh $ip "cat /proc/sys/vm/max_map_count")
  printf "%-20s %-10s %-10s %-10s %-10s\n" $(hostname -s) $ip $heap $fd $swap $map
done

輸出示例:

HOST               IP         HEAP       FD         SWAPPINESS MAX_MAP
es-hot-001         10.0.0.11  31G        655360     1          262144
es-warm-002        10.0.0.12  31G        655360     1          262144

凡 FD<655360、SWAPPINESS>1、MAX_MAP<262144 的行自動標紅,值班人員立即修復。

六、常見誤區

  1. “內存大就無腦 32GB”
    超過 31GB 關閉壓縮 Oops 的代價遠高於多 1GB 堆的收益,務必用 PrintFlagsFinal 驗證。
  2. “容器裏改 limits.conf 就行”
    容器主進程不受 pam_limits 管控,必須走 docker --ulimit 或 K8s securityContext。
  3. “swap 關了性能更好”
    完全關閉 swappiness=0 在部分內核會觸發早期 OOM,留 1 給內核保留逃生通道。
  4. “mmap 報錯加內存”
    本質是虛擬地址區域耗盡,加物理內存無用,調大 max_map_count 才是正解。

七、總結

Heap 31GB、FD 65 萬、Swap 1、Map 26 萬,這四個數字是 Elastic 官方 Support 工單裏出現頻率最高的“萬能處方”。上線前用巡檢腳本一次性批量化修復,能把 80% 的“靈異”問題消滅在投產之前;後續擴容只需橫向加節點,再也不用半夜因為“Too many open files”而被電話叫醒。