Python setrlimit设内存限制后子进程与HTTP请求异常问题
问题描述
在Python程序中使用setrlimit设置RLIMIT_AS内存限制后,出现两类异常:
- 子进程调用失败,报错:
/bin/sh: error while loading shared libraries: libc.so.6: failed to map segment from shared object requests.get()发送HTTP请求时出现挂起
代码示例:
def main(): # resource.setrlimit(resource.RLIMIT_AS, (1024 *1000, 1024*1000)) print(requests.get('https://api.github.com').json()) print(subprocess.Popen(['which', 'wkhtmltopdf'], stdout=subprocess.PIPE).communicate()[0])
运行环境:
- Kubernetes/Docker 基于 python:3.10.5 镜像
- Linux内核版本 6.1.129-vsh0zfs227
- Kubernetes已配置cgroup
memory.max内存限制,但希望在Python中捕获MemoryError,而非容器被随机Sigkill
原因分析
- 子进程启动失败:
RLIMIT_AS限制的是进程的虚拟内存总大小,包括主进程、子进程的所有虚拟内存空间。子进程启动时需要加载共享库(如libc.so.6),这些库需要映射到虚拟内存中,但你设置的1MB限制过小,完全不足以容纳共享库的映射空间,导致加载失败。 - HTTP请求挂起:
requests底层的socket操作、SSL/TLS握手等流程需要分配虚拟内存(如缓冲区、加密上下文),当虚拟内存被严格限制到1MB时,这些操作会因内存不足陷入阻塞或无法完成,表现为请求挂起。 - 两类内存限制的差异:cgroup的
memory.max限制的是物理内存+swap的实际使用量,而RLIMIT_AS是虚拟内存上限——虚拟内存远大于实际内存(包含磁盘缓存、共享库映射、匿名内存等),过小的RLIMIT_AS会触发和实际内存使用无关的内存不足问题。
解决方法
1. 合理调整RLIMIT_AS阈值
不要设置过小的虚拟内存限制,需预留足够空间给共享库、系统调用所需的虚拟内存区域。可参考程序正常运行时的虚拟内存使用量来设置:
import resource # 设置虚拟内存限制为2GB(单位:字节) resource.setrlimit(resource.RLIMIT_AS, (2 * 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024))
可通过pmap -x <pid>命令查看正常运行时进程的虚拟内存使用情况,以此为参考设置阈值。
2. 改用实际内存限制(RLIMIT_RSS)
如果目标是限制进程的实际物理内存占用,建议使用RLIMIT_RSS(Resident Set Size),它限制的是进程实际占用的物理内存大小,不会影响共享库映射等虚拟内存操作:
import resource # 设置物理内存限制为1GB resource.setrlimit(resource.RLIMIT_RSS, (1 * 1024 * 1024 * 1024, 1 * 1024 * 1024 * 1024))
注意:RLIMIT_RSS在部分Linux内核中是建议性限制,内核不会严格强制执行,但结合cgroup的限制可形成互补——当进程接近RLIMIT_RSS时Python会抛出MemoryError,cgroup则作为最后防线防止内存溢出。
3. 隔离子进程的内存限制
如果不需要子进程继承主进程的内存限制,可在启动子进程前临时重置其内存限制:
import resource import subprocess def reset_rlimit(): # 子进程将RLIMIT_AS重置为无限制 resource.setrlimit(resource.RLIMIT_AS, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)) def main(): resource.setrlimit(resource.RLIMIT_AS, (2 * 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024)) print(requests.get('https://api.github.com').json()) # 启动子进程时执行重置函数 print(subprocess.Popen(['which', 'wkhtmltopdf'], stdout=subprocess.PIPE, preexec_fn=reset_rlimit).communicate()[0])
4. 结合cgroup与Python限制的最佳实践
- 保留Kubernetes的cgroup
memory.max作为最终兜底,防止容器因内存溢出被Sigkill。 - 在Python中设置合理的
RLIMIT_AS或RLIMIT_RSS,确保程序在接近内存上限时主动抛出MemoryError,方便捕获并处理,而非直接被内核终止。
内容的提问来源于stack exchange,提问作者Bálint Balina
相关产品推荐
相关产品推荐

