记一场由OOM引发的环境“雪崩”

文章讲述了在IT环境中,由于微服务xxcud的内存溢出引发docker服务异常,导致其他容器挂掉。故障排查过程中发现代码线程池配置问题和docker容器内存管理不足。最终通过优化代码和调整docker内存限制来解决,并强调了docker内存限制和监控的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。


问题描述

x年x月x日下午2:40左右发现某环境出现故障,某些功能无法正常运行。于是马上进行排查:
1、基础服务端口运行都是正常的
2、查看环境上最近有新发版的三个微服务,发现都在不同频率的打印这句日志:

INFO [DubboMonitor.java:80] :  [DUBBO] Send statistics to monitor zookeeper://192.169.1.111:2181/com.alibaba.dubbo.monitor.MonitorService?anyhost=true&application=dubbo-monitor&check=false&delay=-1&dubbo=crud&generic=false&interface=com.alibaba.dubbo.monitor.MonitorService&methods=lookup,collect&pid=11&revision=monitors&side=provider×tamp=1568598922300, dubbo version: crud, current host: 10.42.91.223

因为之前有一个微服务出现OutOfMemoryError的时候,也一直打印这些日志,因此将三个容器日志导出来查看,刚刚导了两个日志,正在导第三个日志的时候,发现docker命令无法执行,docker挂了…
先重新启动了docker服务,恢复了业务,然后查看docker挂掉的原因。

故障排查分析

1. 查看/var/log/messages日志

将messages文件中跟docker有关的内容过滤出来,发现了这样的信息(部分日志):

14:43:07 rancher-node dockerd-current: time="20xx-0x-xxT14:43:07.982713104+08:00" level=error msg="collecting stats for 587cf4938bed5e3172868d85ae41db3af37e9c1a6cd8192f1cfa22a4e969d53b: rpc error: code = 2 desc = fork/exec /usr/libexec/docker/docker-runc-current: cannot allocate memory: \"\""
14:45:04 rancher-node journal: Suppressed 1116 messages from /system.slice/docker.service
14:45:05 rancher-node dockerd-current: time="20xx-0x-xxT14:45:05.410928493+08:00" level=info msg="Processing signal 'terminated'"
14:45:05 rancher-node journal: time="20xx-0x-xxT06:45:05Z" level=error msg="Error processing event &events.Message{Status:\"kill\", ID:\"af42628b1354b74d08b195c0064d8c5d760c826626a3ad36501a85c824d2204d\", From:\"prod.locmn.cn/prod/locmn-drols-query-chq:latest\", Type:\"container\", Action:\"kill\", ..... Error: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?" 

就是说在14:45的时候,docker就已经无法分配到内存了

然后信号终止,docker进程被杀掉,因此docker的命令无法运行,所有docker容器也都一起挂掉了:

14:45:05 rancher-node dockerd-current: time="20xx-0x-xxT14:45:05.410928493+08:00" level=info msg="Processing signal 'terminated(处理信号的终止)'"
14:45:05 rancher-node journal: time="20xx-0x-xxT06:45:05Z" level=error msg="Error processing event &events.Message{Status:\"kill\", ID:\"af42628b1354b74d08b195c0064d8c5d760c826626a3ad36501a85c824d2204d\", From:\"registry.locman.cn/sefon-online/locman-drools-query-chq:latest\", Type:\"container\", Action:\"kill\", ......Error: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?" 

2. 查看业务日志

后面细查新发版的三个微服务的业务的日志,发现在14:03分的时候,有一个叫xxcud的服务有**“java.lang.OutOfMemoryError”**的报错:

20xx-0x-xx 14:03:10,554 ERROR [ExceptionFilter.java:87] :  [DUBBO] Got unchecked and undeclared exception which called by 10.42.83.124. service: com.run.locman.api.crud.service.AlarmInfoCrudService, method: add, exception: java.lang.OutOfMemoryError: unable to create new native thread, dubbo version: crud, current host: 10.42.91.223
java.lang.OutOfMemoryError: unable to create new native thread
  at java.lang.Thread.start0(Native Method)
......(省略部分日志内容)
Exception in thread "pool-1-thread-3" java.lang.OutOfMemoryError: unable to create new native thread
  at java.lang.Thread.start0(Native Method)
  at java.lang.Thread.start(Thread.java:714)

原来是这个叫xxcud服务的内存溢出了引发的故障,导致了docker服务被kill掉,所有的docker容器瞬间全部挂掉…

故障排查结论

和研发一起对故障进行了分析,发现有两个原因:
1、 这个服务有一个线程池,在代码里面设置的最小是8,最大限制是2147483647 ,用完的线程要1分钟之后才能回收。这就存在两个问题:

1)业务在持续不断的发送请求,这个服务就会一直创建线程,而因为给定的线程最大值过大,相当于可以无限制的创建线程了,会一直消耗资源;

2)用完的线程1分钟之后才会回收,时间过长。

在这两点的影响下,程序跑一段时间,就会出现创建大量的线程,过度的消耗内存资源。

2、由于该环境不属于重要环境,当初搭建时比较随意,docker容器没有做容器的内存限制,所以默认情况下容器使用的资源是不受限制的。

docker可以使用主机内核调度器所允许的最大资源,因此当主机发现内存不够用的时候,也会抛出内存溢出的错误。而且会开始杀死一些进程用于释放内存空间。可怕的是任何进程都可能成为内核猎杀的对象,包括 docker daemon 和宿主机上的其它一些重要的程序。更危险的是如果某个支持系统运行的重要进程被kill掉了,整个系统也就宕掉了。

这次的docker服务进程就被杀掉了。

解决方案

研发运维双管齐下:
1、研发优化代码,包括限制线程池的最大线程数量和线程回收的时间,重新发布代码打补丁,后面观察到目前,没有再出现类这个问题了;

2、运维优化,限制docker内存。重新优化了docker容器,限制了docker内存的使用量,减少docker容器过度占用宿主机资源的风险;

3、加强对docker容器的监控与告警;

总结教训

1、docker限制内存,非常重要!
2、docker限制内存的方式:

方法一:静态修改 -m
-m参数:限制docker容器最大使用内存
例如:

$ docker run -it -m 300M --memory-swap -1 --name con1 u-stress /bin/bash

上面的 docker run 命令中通过 -m 选项限制容器使用的内存上限为 300M。
同时设置 memory-swap 值为 -1,它表示容器程序使用内存的受限,而可以使用的 swap 空间使用不受限制(宿主机有多少 swap 容器就可以使用多少)。

方法二:动态修改 docker update
docker update 动态修改docker容器内存
例如:把一个运行着gitlab 的容器内存限制在2048M以内

docker update --memory 2048m --memory-swap -1 gitlab
<think>我们正在评估每隔2秒发送一次请求是否会导致请求积压。积压的产生取决于两个关键因素:**请求发送速率**和**系统处理速率**。让我们用数学关系来描述: 设: -$\lambda$=请求发送速率(请求数/秒) -$\mu$ =系统处理速率(请求数/秒)则系统稳定性条件为: $$\lambda \leq \mu$$当 $\lambda >\mu$时,系统无法及时处理所有请求,积压会持续增长。 ###一、你的场景分析-**发送速率**:$\lambda =\frac{1}{2} =0.5$请求/秒(每2秒1个请求) -**处理速率**:需根据实际系统能力确定,分三种情况讨论:|处理能力$\mu$ |比较结果|是否积压| |----------------|------------------|----------------| |$\mu<0.5$|$\lambda> \mu$|是(持续增长) || $\mu =0.5$|$\lambda= \mu$|否(动态平衡)| |$\mu>0.5$|$\lambda< \mu$|否|###二、关键风险点即使 $\lambda \leq \mu$,仍可能因以下原因出现**临时积压**: 1. **处理时间波动**单个请求处理时间可能因数据量、IO等待等随机波动。例如:-平均处理时间:1.8秒/请求→$\mu\approx0.56$请求/秒-但某次请求处理耗时5秒 →期间新到2个请求,形成短暂积压2. **资源竞争**如引用[^3]所示,当系统负载高时,数据库查询等操作变慢,导致 $\mu$临时下降[^3]。 3. **网络瓶颈**引用[^2]指出:网络吞吐量不足会导致消息在发送队列积压[^2]。 ###三、积压后果若积压持续发生,可能引发:1.**内存压力**未处理请求会占用内存(如Dubbo的FUTURES积压DefaultFuture对象[^1]) 2. **级联故障**如引用[^3]案例:任务积压导致重试风暴,DB查询QPS翻倍[^3] 3. **响应延迟**用户感知的延迟=排队时间+处理时间###四、实践建议为确保系统稳定,建议:1.**压力测试**测量实际处理能力$\mu_{max}$,要求$\lambda<0.8 \times \mu_{max}$(保留20%缓冲)2.**监控队列长度**```java//伪代码:监控积压if(requestQueue.size() >WARNING_THRESHOLD) {alert("请求积压风险!");}```3.**超时与重试策略**设置合理超时时间(如3秒),避免无效请求堆积(引用[^1]的教训)[^1]。 ###五、结论-**通常情况下**:每2秒1个请求($\lambda=0.5$)不会积压,除非系统处理能力 $\mu \leq0.5$或存在严重性能瓶颈。- **需警惕**:处理时间波动、资源竞争、网络瓶颈等导致的临时积压放大效应。 >通过引用[^2][^3]的案例可知,**即使发送速率不高,网络或下游服务异常也可能引发积压**。建议实施监控和熔断机制[^1][^3]。§§相关问题§§1.如何准确测量系统的请求处理能力$\mu$?2.有哪些实时监控请求队列积压的工具?3.请求处理时间波动过大时应如何优化?4.网络瓶颈导致积压时有哪些解决方案?</think>根据您的场景(每2秒发送一次请求),结合引用内容分析,**是否导致积压主要取决于处理能力与请求特征的匹配度**。以下是关键评估维度: --- ### 一、数学关系分析 设: - **请求间隔** $T_{send} = 2s$(发送速率 $\lambda = 0.5$ 请求/秒) - **平均处理时间** $T_{process}$(处理速率 $\mu = 1/T_{process}$) **积压判定条件**: $$ \lambda > \mu \quad \Rightarrow \quad T_{process} > 2s $$ 当单个请求平均处理时间超过 2 秒时,积压必然发生。 --- ### 二、实际风险场景(结合引用) #### 1. **处理能力不足**(引用[^1]风险) 若服务端存在以下情况,$T_{process}$ 可能 > 2s: - 数据库查询慢(如引用[^3]的 DB QPS 瓶颈) - 依赖外部阻塞调用(网络 IO/文件读写) - 线程资源竞争(如未使用线程池) **结果**:请求队列持续增长 → 内存积压 → 触发 OOM[^1] #### 2. **网络瓶颈**(引用[^2]风险) 即使 $T_{process} < 2s$,若存在: - 网络延迟波动(如移动设备信号不稳) - 对端接收缓冲区满(TCP 层积压) - 带宽限制(如传输大文件) **结果**:Netty 发送队列堆积 → 内存占用上升[^2] #### 3. **超时重试风暴**(引用[^3]风险) 当 $T_{process}$ 接近 2s 时: - 客户端超时重试(如默认 1s 超时) - 服务端重复处理同一请求(如引用[^3]的 Xxl-Job 案例) **结果**:实际负载翻倍 → 雪崩效应 → DB QPS 飙升[^3] --- ### 三、安全边界建议 | 指标 | 安全值 | 风险值 | |---------------------|----------------------------|----------------------------| | 平均处理时间 | $T_{process} < 1s$ | $T_{process} > 1.5s$ | | P99 延迟 | $P99 < 1.8s$ | $P99 > 2s$ | | 队列监控水位 | 积压量 < 50 | 积压量持续 > 100 | | 内存占用率 | < 60% | > 80% | > **案例验证**:在引用[^3]中,当处理延迟接近发送间隔时,重试机制导致 QPS 翻倍[^3]。 --- ### 四、优化措施 1. **容量压测** 用 JMeter 模拟 0.5 请求/秒的负载,观察: ```bash jmeter -n -t test_0.5qps.jmx -l result.csv ``` 关注 $T_{process}$ 的 P90/P99 值 2. **动态流控**(防引用[^1]积压) ```java // 示例:根据队列长度调整发送 if (messageQueue.size() > BACKPRESSURE_THRESHOLD) { Thread.sleep(1000); // 暂停发送 } ``` 3. **异步处理**(解耦发送与处理) ```mermaid graph LR A[2s定时发送] --> B[内存队列] B --> C{线程池} C --> D[快速响应] C --> E[异步处理耗时操作] ``` --- ### 结论 | 场景 | 是否积压 | 风险等级 | |-------------------------------|----------|----------| | 处理时间 ≤ 1.5s + 无重试 | 否 | ✅ 安全 | | 处理时间波动至 >2s | 是 | ⚠️ 高危 | | 存在超时重试机制 | 是 | 🔥 严重 | | 网络带宽不足/传输大文件 | 是 | ⚠️ 中高危 | > **关键建议**: > 1. 确保 $P99(T_{process}) < 1.8s$ > 2. 禁用客户端重试或延长超时时间 >3s > 3. 实施队列积压监控(如引用[^1]的 FUTURES 清理机制)[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值