前言
生产环境有一个3节点es集群,突然收到es告警,es集群状态异常,其中一个es节点挂掉(37节点)
一、异常现象
es节点进程不存在,日志报错如下:

重新启动报错日志如下:

二、排查
根据报错直接去排查,发现thread_create failed (EAGAIN) 这个操作系统错误意味着“资源不足”。
首先猜测是不是nproc或者nofile不足导致或者不生效导致,将其配置写在es管理脚本里,发现ulimit生效了而且配置的值已经很大了,理论应不会超出。

因为es Jvm分配了31g内存大小,猜测是不是内存导致的(只是猜测,因为从free来看,内存压力还不大)

于是将这个思路给到Gemini,Gemini给出测试思路:
假设 A(概率最高):本机内存(RAM)被堆耗尽
这是我目前的首要怀疑。
问题: 你的 -Xmx31g 设置得相对于服务器的总物理内存来说太大了。
举例: 如果你的服务器总共只有 32GB 物理内存,你把 31GB 分配给了 JVM 堆。操作系统本身、磁盘缓存、以及所有 ES 需要的本机内存(包括为每个线程分配 1MB 的栈)加起来连 1GB 都不到了。
结果: ES 启动时,main 线程OK,但当它尝试创建第一个工作线程(如 scheduler 线程)时,操作系统已经没有任何空闲内存可以分配那 1MB 的栈了,因此返回 EAGAIN,导致进程崩溃。
假设 B(仍然可能):nproc 限制确实未生效
问题: 你的 elasticsearch.sh 脚本非常复杂。它打印出 nproc=102400(这只是检查了它自己的 Shell 环境),但当它在后台(daemonize)启动 java 进程时,java 进程没有继承这个限制,而是继承了一个很低的系统默认值(如 4096)。
结果: ES 启动时,创建了成百上千个线程,很快达到了 4096 的上限,导致崩溃。
决定性的测试:二选一
我们必须做一个测试来区分这两种可能。这是一个非常简单的测试:
请你暂时修改你的 jvm.options 配置文件。
找到你的 config/jvm.options 文件。
将 -Xms31g 和 -Xmx31g 这两行注释掉或修改为:
-Xms4g
-Xmx4g
保存文件。
使用你原来的脚本 sh elasticsearch.sh start 重新启动。
分析测试结果:
请你告诉我执行这个测试后发生了什么,这将告诉我们一切:
情况一:ES 成功启动了!
诊断: 100% 确定是 假设 A。你的 31GB 堆内存对于这台服务器来说太大了,导致了本机内存耗尽。
解决方案: 你必须永久地减少你的堆内存(例如 16GB),或者为服务器增加更多的物理内存(推荐至少 64GB 内存来支持 31GB 的堆)。
情况二:ES 仍然以 完全相同 的 unable to create new native thread 错误失败。
诊断: 100% 确定是 假设 B。4GB 的堆为本机内存留下了大量空间,如果此时仍然无法创建线程,唯一的解释就是 nproc 限制(比如 4096)被触发了。
解决方案: 这证明了你的 elasticsearch.sh 脚本有问题。你必须停止使用它,并使用我之前提供的那个简单的 start.sh 脚本来启动。
将Jvm内存设置成4g进行测试,发现es节点成功启动,并且运行30分钟没有再次出现OOM,到这里可以定位大概率是内存的问题,可为什么free内存还有那么多,就会出现这种情况呢?
三、验证
开两个ssh窗口,第一个窗口运行
# -n 0.5 = 每 0.5 秒刷新一次
watch -n 0.5 'ps -U shsnc -L --no-headers | wc -l'
在第二个窗口将Jvm设置成31g,重启es节点
sh elasticsearch.sh restart
结果发现:
启动es之前,shsnc线程数是99464,es启动之后线程数是:100468,说明:
- nproc 限制是 102,400:这是你 shsnc 用户被允许创建的总线程数上限。
- 其他应用已耗尽资源:在 ES 启动之前,你 shsnc 用户下的其他应用(那 570 GB 的负载)就已经占用了 99,464 个线程。
- 剩余配额:102,400 (上限) - 99,464 (已用) = 仅剩 2,936 个线程配额。
可是为什么会出现资源被限制,或者说资源不足呢?
Gemini回答:
当你尝试用 31G 堆启动 ES 时:
ES 启动,给系统增加了巨大的内存压力。
那 99,464 个线程的“邻居”应用感受到了内存压力,开始恐慌性地创建新线程(用于 GC、错误日志、重启工作进程等)。
“邻居”应用瞬间就用光了那剩余的 2,936 个线程配额。
ES 进程(main 线程或 scheduler 线程)此时也尝试创建它启动所必需的线程(它自己也需要几百到上千个线程)。
但当 ES 向内核申请线程时,内核返回 EAGAIN,因为你 shsnc 用户的 102,400 配额已经被你的“邻居”应用在那一刻吃满了。
ES 启动失败。
为什么 28G (或 4G) 就能启动? 启动28g,线程数是:100468
100,468 (总数) - 99,464 (原基线) = 1,004 个线程。
28G 堆(或 4G 堆)启动时内存压力较小,“邻居”应用没有恐慌,保持稳定。
ES 启动自己需要 1,004 个线程。
99,464 + 1,004 = 100,468,这个数字没有超过 102,400 的上限。
所以它成功启动了。
感觉Gemini回答的很有道理,但是不知道如何去验证。
四、结论
因为这台主机跑了几十个Java应用,虽然内存使用率在正常水平,nproc已经接近limit值。但是在es分配使用30多g内存时,引起了系统的恐慌,导致nproc达到102400。
最后我尝试将Jvm分配的内存从原31g设置成28g,将ulimit的值从102400设置成204800,已经运行了半天,没有再次出现报错。
最佳方案还是将es节点迁走,运行在独立的服务器上。基于当前环境,只能先通过参数来临时处理。

被折叠的 条评论
为什么被折叠?



