1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com

前面我们学习了内核可调项和 TuneD 调优工具,这些主要是在操作系统层面调整系统行为。但是实际生产环境还有一种很常见的情况。一台服务器上可能同时跑着很多业务,比如 Web 服务、数据库、监控程序、日志程序……

如果其中某一个程序突然出现异常,疯狂占用 CPU、创建大量进程、打开海量文件,甚至不断申请系统资源,那么它很可能会把其他业务一起拖垮。这时候我们就需要给进程或者服务设置一些资源上限。

你可以把它理解成:

服务器就像一栋办公楼,每个业务就是一个公司。

如果某家公司突然疯狂占用会议室、电话线路、办公桌,其他公司自然就没地方用了。

所以管理员会给它规定:

“你最多只能用这么多资源,超过这个范围就不允许继续申请。”

Linux 中的资源限制,就是在做类似的事情。


什么是 POSIX 资源限制?

在 Linux 中,一个进程能够使用多少系统资源,并不是完全没有限制的。系统可以通过 POSIX resource limits,也就是 POSIX 资源限制,对进程能够使用的部分资源设置上限。

例如:

  • 最多可以打开多少文件
  • 最多可以创建多少进程
  • 最多可以使用多少 CPU 时间
  • 栈空间最大是多少
  • core dump 最大是多少

Linux中常见的两种操作方式是:

方式主要作用对象典型用途
ulimit当前 Shell 及其子进程临时测试、用户登录会话
systemd Limitsystemd 管理的服务进程给后台服务设置资源限制

这里大家先记住一个非常重要的区别:

ulimit 更偏向“用户登录会话”;systemd 更适合“后台服务”。

比如:

你 SSH 登录服务器之后,在命令行里运行一个程序,这时候可以使用 ulimit。但是如果是 Nginx、MySQL、Redis 这种由 systemd 管理的后台服务,就应该考虑在对应的 systemd service 中设置限制。而如果以后需要对 CPU、内存等资源进行更加完整的服务级隔离,就会涉及 cgroup v2,这个我们后面再讲。


ulimit 命令详解

ulimit 是 Bash 的内置命令,用于查看或者修改当前 Shell 的资源限制。

这里一定要注意:

ulimit 修改的是当前 Shell 进程的资源限制。

而 Shell 启动出来的子进程,会继承这些限制。

所以可以简单理解成:

1
2
3
4
5
当前 Shell
│
├── 子进程 A
├── 子进程 B
└── 子进程 C

如果我们在这个 Shell 中设置了资源限制,那么从这个 Shell 启动的子进程通常也会继承相应限制。

这也是为什么:

直接在终端执行 ulimit,通常只影响当前会话,不会影响其他 SSH 会话。


soft limit 和 hard limit

ulimit 中有两个非常重要的概念:

soft limit:软限制

软限制可以理解成:

当前真正生效的限制值。

普通用户可以在权限允许的范围内调整自己的 soft limit,但不能超过 hard limit。

hard limit:硬限制

hard limit 可以理解成:

这个资源允许设置的最高上限。

普通用户不能把自己的 hard limit 随便调高。

所以可以简单记成:

1
2
3
4
5
6
7
hard limit
↓
允许达到的最高上限

soft limit
↓
当前实际使用的限制

例如:

1
2
soft = 100
hard = 200

那么当前实际限制是 100。用户可以把 soft 从 100 调到 150,但不能调到 300,因为已经超过 hard limit。

不同资源达到限制之后的具体行为并不完全一样,所以不要简单理解成“所有限制达到以后都会被杀掉”,也就是说**ulimit 的不同资源限制,达到上限后,程序的反应是不一样的,并不是所有情况都会直接把进程杀掉。**。

资源参数达到限制后的典型行为
CPU 时间-t / LimitCPU超过限制后,进程可能收到 SIGXCPU,继续超限可能被终止
打开文件数-n / LimitNOFILE不会直接杀进程,程序继续运行,但再打开文件可能失败
用户进程数-u / LimitNPROC创建新进程/线程可能失败,已有进程通常不会因此被杀掉
Core 文件大小-c / LimitCORECore Dump 文件可能被截断或无法生成,并不是杀掉原进程
文件大小-f / LimitFSIZE继续写文件可能失败,进程不一定被直接杀掉

举个最容易理解的例子:nofile,假设:

1
ulimit -n 100

意思是:

当前进程最多可以同时打开 100 个文件描述符。

假设一个程序已经打开了 100 个文件,这时候它又想打开第 101 个:

1
2
3
4
5
6
7
8
9
打开第 101 个文件
↓
超过 NOFILE 限制
↓
open() 调用失败
↓
程序收到“打开失败”的错误
↓
程序是否退出,由程序自己的错误处理决定

并不是 systemd 或内核看到达到 100,就直接把这个进程杀掉。

例如程序处理得比较好:

1
2
3
4
5
6
7
打开文件失败
↓
记录日志
↓
稍后重试
↓
程序继续运行

处理得不好,也可能:

1
2
3
4
5
打开文件失败
↓
程序没有正确处理异常
↓
程序自己退出

所以这里真正需要理解的是:

资源限制更像是在资源入口处设置了一道“门槛”,超过门槛以后,具体发生什么,要看这个资源的语义以及应用程序如何处理失败。

那真正需要限制内存怎么办?

如果你的场景是:

“服务器上运行一个 systemd 服务,我不希望它把整台机器的内存吃光。”

这时候应该使用 systemd 的:

1
MemoryMax=

例如:

1
2
[Service]
MemoryMax=1G

可以把它理解成:

1
2
3
4
5
6
7
8
9
10
11
服务器 16 GB RAM
│
├── MySQL
│
├── Nginx
│
└── example.service
│
└── MemoryMax=1G
↓
给这个服务设置内存上限

systemd 会利用 cgroup 对服务进行资源控制,至于这个,我下面会介绍,这里先理解就行。


交互式操作 ulimit

先来看当前 Shell 的资源限制。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30400
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 30400
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

可以查看当前 Shell 的各种资源限制。如果想明确查看 soft limit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# ulimit -S -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30400
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 30400
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

如果想查看 hard limit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# ulimit -H -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30400
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 524288
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) unlimited
cpu time (seconds, -t) unlimited
max user processes (-u) 30400
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

其中:

1
-S

表示 soft limit。

1
-H

表示 hard limit。

如果设置的时候不指定 -S 或 -H,需要特别注意命令的具体行为和当前权限,不要想当然地认为它只修改 soft limit。

你可能会发现,很多参数的输出完全一样。这并不代表 Soft 和 Hard 没有区别,而是因为这些资源当前设置的 Soft 和 Hard 值刚好相同。真正有区别的地方,可以看下面几个例子:

  • open files(-n)

    • Soft:1024
    • Hard:524288
    • 说明:当前 Shell 最多打开 1024 个文件,但 Soft limit 最多可以提高到 524288。
  • stack size(-s)

    • Soft:8192 KB
    • Hard:unlimited
    • 说明:当前限制是 8192 KB,但 Hard limit 没有设置上限。
  • pending signals(-i)

    • Soft:30400
    • Hard:30400
    • 说明:Soft 和 Hard 当前设置成了一样的值,所以两边显示相同。
  • CPU time(-t)

    • Soft:unlimited
    • Hard:unlimited
    • 说明:Soft 和 Hard 都没有设置 CPU 时间限制,所以两边显示相同。

因此大家可以简单理解:

  • Soft limit:当前真正生效的资源限制。
  • Hard limit:Soft limit 能够提升到的最高边界。
  • 如果 Soft 和 Hard 本身设置成一样,执行 ulimit -S -a 和 ulimit -H -a 看起来就会完全一样。
  • 如果两者不同,就能明显看到区别。

例如:

1
2
3
4
open files (-n)

Soft:1024
Hard:524288

这就意味着:

现在只能打开 1024 个文件,但可以把 Soft limit 调高,最高不能超过 524288。


实操案例:查看单项资源限制

比如我们想看看:

当前 Shell 允许进程累计使用多少 CPU 时间。

可以执行:

1
2
[root@localhost ~]# ulimit -S -t
unlimited

这里的:

1
unlimited

表示当前没有设置 CPU 时间上限。

为什么重新打开 SSH 窗口就失效?

如果我们刚才执行的是:

1
ulimit -S -t 60

它修改的是:

当前 Shell 的资源限制。

比如:

1
2
3
4
5
6
7
8
9
SSH窗口 A
│
└── Shell A
└── 测试程序

SSH窗口 B
│
└── Shell B
└── 测试程序

我们只在 Shell A 中执行了:

1
ulimit -S -t 60

那么 Shell B 并不会自动继承这个限制。

所以:

直接执行 ulimit,非常适合临时测试,但不适合作为长期配置方案。


ulimit 如何持久化?

刚才我们直接执行:

1
ulimit -S -t 60

退出当前 Shell 后,这个配置就没有了。

如果希望:

用户每次登录服务器时,都自动获得指定的资源限制。

那么就需要使用 PAM 的 pam_limits 机制。Linux中常见的配置文件是:

1
2
/etc/security/limits.conf
/etc/security/limits.d/*.conf

其中更推荐把自己的配置放在:

1
/etc/security/limits.d/

这样做的好处是:

不需要直接修改系统主配置文件,自己的配置更加独立,也方便后期维护。


limits.conf 配置格式

配置格式是:

1
<domain> <type> <item> <value>

分别表示:

参数含义
domain对哪个用户或用户组生效
typesoft 或 hard
item要限制的资源
value限制值

例如:

1
@managers hard maxlogins 3

假设服务器上有一个managers用户组。现在公司的要求是:

managers 组中的用户最多同时建立 3 个登录会话。

我们可以创建:

1
/etc/security/limits.d/managers.conf

内容:

1
@managers hard maxlogins 3

这样,当属于这个组的用户进行 PAM 登录时,就会受到这个限制。

可以把它理解成:

公司办公楼规定:这个部门最多只能同时占用 3 个工位。

第 4 个登录请求就会受到限制。


一个非常重要的坑:limits.conf 不管 systemd 服务

这里是这篇文章非常重要的一个知识点。很多初学者会有这样的想法:

“我把 /etc/security/limits.d/ 里面的 nofile 调大了,那 Nginx、MySQL 应该也跟着变大了吧?”

不一定,而且对于 systemd 直接启动的后台服务,通常不是这么生效的。

原因很简单:

1
2
3
4
5
6
7
SSH登录用户
↓
PAM
↓
pam_limits
↓
limits.conf / limits.d

而 systemd 启动后台服务,并不是通过这种 PAM 登录会话启动的。

所以:

1
2
/etc/security/limits.conf
/etc/security/limits.d/*.conf

主要针对 PAM 登录会话。而 systemd 管理的后台服务,要从 systemd 自己的配置入手。


使用 systemd 给服务设置资源限制

systemd 不仅负责:

1
2
3
启动服务
停止服务
重启服务

它还可以参与:

1
资源管理

现在大家对systemd 的资源管理推荐程度也比较高,其资源控制底层会结合 cgroup 等机制管理服务进程。所以,如果我们的需求是:

“我要限制一个后台服务。”

那么相比修改用户的 limits.conf,更应该考虑 systemd 的服务级配置。


systemd 的 Limit* 参数

在 systemd service 的[Service]部分,可以使用很多Limit参数。

这些参数对应 Linux 的 POSIX resource limit,也就是我们前面讲的 ulimit / setrlimit 这一套限制机制。

常见的对应关系如下:

systemd 参数ulimit 选项作用
LimitCPU=-tCPU 时间限制
LimitNOFILE=-n最大打开文件描述符数量
LimitNPROC=-u用户进程数量限制
LimitSTACK=-s进程栈大小
LimitCORE=-ccore dump 文件大小

systemd 修改配置的正确姿势

这里还有一个生产环境非常重要的习惯。不要直接修改软件包安装的:

1
/usr/lib/systemd/system/xxx.service

因为软件升级的时候,这些文件可能被软件包更新。更推荐使用:

1
/etc/systemd/system/<服务名>.service.d/

创建 drop-in 配置片段。

这样可以:

不修改原始 service 文件,只额外增加自己的配置。

而 /etc/systemd/system/ 的优先级高于 /usr/lib/systemd/system/。

实操案例:给 sshd 服务设置 100M 内存上限

假设现在我们希望限制 sshd 服务:

sshd 服务及其 cgroup 中的进程,内存使用量最多为 100MB。

首先创建 sshd 的 drop-in 配置目录:

1
[root@localhost ~]# mkdir -p /etc/systemd/system/sshd.service.d

然后创建配置文件:

1
2
3
4
cat > /etc/systemd/system/sshd.service.d/10-memorylimits.conf <<EOF
[Service]
MemoryMax=100M
EOF

配置完成后,让 systemd 重新读取配置:

1
systemctl daemon-reload

然后重启 sshd 服务:

1
systemctl restart sshd

这样,新的 sshd 服务进程就会受到 MemoryMax=100M 的内存限制。

查看配置是否生效可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@localhost ~]# systemctl cat sshd
# /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.target
Wants=sshd-keygen.target
# Migration for Fedora 38 change to remove group ownership for standard host keys
# See https://fedoraproject.org/wiki/Changes/SSHKeySignSuidBit
Wants=ssh-host-keys-migration.service

[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

# /etc/systemd/system/sshd.service.d/10-memorylimits.conf
[Service]
MemoryMax=100M

或者

[root@localhost ~]# systemctl show sshd -p MemoryMax
MemoryMax=104857600

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

说明 `100M` 的内存限制已经应用。也可以查看服务当前的 cgroup 资源使用情况:

```bash
[root@localhost ~]# systemctl status sshd
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/sshd.service.d
└─10-memorylimits.conf
Active: active (running) since Wed 2026-09-30 12:52:44 CST; 1min 2s ago
Invocation: 202719eef634447b9ca9afb7b64c8c4b
Docs: man:sshd(8)
man:sshd_config(5)
Main PID: 10745 (sshd)
Tasks: 1 (limit: 48640)
Memory: 1.2M (max: 100M, available: 98.7M, peak: 1.3M)
CPU: 7ms
CGroup: /system.slice/sshd.service
└─10745 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"

⚠️ 实际生产环境要特别注意

这里大家不要简单理解成:

“sshd 自己最多只能使用 100MB。”

更准确地说:

MemoryMax=100M 限制的是 sshd.service 对应 cgroup 的内存使用上限。

如果该 cgroup 中的内存使用超过限制,systemd/cgroup 会采取内存回收和 OOM 处理机制,相关进程可能被终止。

因此,生产环境中不要随便给 sshd 设置一个过低的 MemoryMax。否则可能导致 SSH 服务异常,甚至影响远程登录和运维。

这个案例主要是为了帮助我们理解:

1
2
3
4
5
6
7
8
9
systemd
↓
sshd.service
↓
cgroup
↓
MemoryMax=100M
↓
限制该服务的内存使用

这也是现代 Linux 中使用 systemd 管理服务资源时比较典型的思路,具体的值大家还是要理性设置,不要让我老李背锅。


为什么需要 daemon-reload?很多刚接触 systemd 的同学会问:

“我都把文件改好了,为什么还要 daemon-reload?”

原因是:

systemd 本身需要重新读取 unit 配置。

所以我们修改 service 配置后,一般按照这个流程:

1
2
3
4
5
6
7
修改配置文件
↓
systemctl daemon-reload
↓
systemctl restart XXX.service
↓
新的服务进程加载新配置

注意:

1
daemon-reload

只是让 systemd 重新读取配置。

它不会自动把已经运行的服务进程全部按照新的限制重新启动。

因此这里还需要:

1
systemctl restart xxx.service

ulimit 和 systemd Limit* 到底怎么选?

到这里,我们把前面的内容串起来。

方式主要生效对象配置方式适合场景
ulimit当前 Shell 及其子进程命令行临时测试
pam_limitsPAM 登录会话limits.conf / limits.dSSH 等用户登录会话
systemd Limit*systemd 服务的进程service / drop-in后台服务的 POSIX 资源限制
systemd cgroupservice 对应的资源组MemoryMax、CPUQuota、TasksMax 等更完整的服务级资源控制

这里特别注意:

systemd 的资源管理并不只有 Limit*。

例如真正要控制一个服务:

1
2
3
最多使用多少内存
最多使用多少 CPU
最多创建多少任务

生产环境应该怎么做?

实际生产环境中,不建议一上来就给所有服务设置一大堆限制。更合理的思路是:

第一步:先明确问题

例如发现:

1
Too many open files

再去研究:

1
nofile

而不是:

“网上有人说 Linux 性能调优必须把所有参数都调大,那我也全部调大。”


第二步:根据进程启动方式选择方案

如果是:

1
2
3
SSH登录
↓
用户运行程序

可以考虑:

1
2
3
ulimit
limits.conf
limits.d

如果是:

1
2
3
systemd
↓
nginx.service

那么优先考虑:

1
systemd service

对应的:

1
Limit*

或者后续学习的:

1
cgroup

第三步:先临时测试,再持久化

这个思路和前面的内核参数调优是一致的。先测试:

1
ulimit

或者临时修改 service 配置。

确认业务没有异常以后,再正式写入:

1
/etc/security/limits.d/

或者:

1
/etc/systemd/system/<service>.service.d/

第四步:修改后一定验证

比如修改 systemd 配置后,可以检查:

1
systemctl show example.service | grep '^Limit'

也可以针对具体参数查看:

1
systemctl show example.service -p LimitNOFILE

确认 systemd 当前真正加载的值,而不是只看自己写入的配置文件。


最后总结一下

这一篇我们主要解决一个问题:

如果某个进程或者服务太能“吃资源”,怎么给它设置一个边界?

先记住三个东西:

1
2
3
ulimit
limits.conf
systemd Limit*

它们分别解决不同场景。

场景优先考虑
临时测试当前 Shellulimit
SSH 用户登录会话limits.conf / limits.d
systemd 后台服务systemd Limit*
服务级 CPU、内存、任务数等资源控制systemd + cgroup

还有一个非常重要的经验:

不要看到资源限制就全部设置成一个很大的数字。

例如:

1
2
LimitNOFILE=999999
LimitNPROC=999999

并不代表性能一定更好。资源限制本质上是在:

系统稳定性、业务需求和资源利用率之间找一个合适的边界。

真正做生产调优时,应该先知道:

1
2
3
4
5
6
7
8
9
10
11
现在出了什么问题?
↓
哪个资源成为瓶颈?
↓
是谁在消耗资源?
↓
需要限制什么?
↓
应该使用哪一种限制机制?
↓
修改后验证业务表现

而不是:

1
2
3
4
5
复制一堆参数
↓
全部调大
↓
祈祷服务器性能变好

参考手册

1
2
3
4
5
6
7
8
man bash
man ulimit
man limits.conf
man pam_limits
man setrlimit
man systemctl
man systemd.exec
man systemd.resource-control

下一篇预告:如果只是使用 ulimit 和 Limit*,我们解决的主要还是传统 POSIX resource limits。真正想做到**“这个服务最多用多少 CPU、多少内存、最多创建多少任务”**,就需要进一步学习 Linux 的 cgroup v2,以及 systemd 如何通过 cgroup 对服务进行资源控制。