一、先从一台让人头疼的服务器说起

很多用RHEL的朋友都会遇到这样的难题:一台服务器上同时跑着多种业务,不同业务的性能要求却完全相反。比如,白天有大量Web请求进来,我们关心的是一秒能处理多少个请求,这叫作“吞吐优先”;晚上要跑一些对实时性很敏感的批任务,比如金融结算、模型推理,这时候我们关心的又是单个任务多久能完成,这叫“延迟极致”。以前我遇到这种需求,只能手动去改一堆内核参数,比如设置CPU调频模式、调整内存大页、修改网络缓冲区,改来改去还容易把系统搞乱,更别说要频繁回滚了。后来我发现了RHEL自带的tuned工具,它就像一位贴身管家,提前准备好几套“调优配方”,我们只需要选择当前想要的配方,它就会自动把各种参数调整到位,甚至可以让我们用脚本根据时间来动态切换。这篇文章就来分享一下我的实践过程。

二、tuned是个什么好东西

tuned是红帽官方开发的一个性能调优守护进程。它的核心概念是“profile”,也就是方案。每个方案会定义一组系统参数的配置,包括CPU、内存、磁盘、网络等。tuned会依据当前激活的方案,把这些参数实时地应用到系统中。更重要的是,tuned支持“继承”,我们可以在官方方案的基础上修改和扩展,这样既能保证基础优化不丢,又能加入自己的特殊需求。

2.1 先看看系统里有哪些profile

打开终端,我们可以用tuned-adm命令查看所有profile。下面这个命令会列出所有可用方案的名称和简介。

# 列出所有可用的tuned方案
sudo tuned-adm list

执行之后,你可能会看到诸如balanced、throughput-performance、latency-performance、powersave等方案。如果系统没有显示任何方案,可能是tuned服务没启动,需要先运行systemctl start tuned。

2.2 当前用的是哪个

想知道当前系统正在使用哪个方案,我们可以运行:

# 显示当前生效的tuned方案
sudo tuned-adm active

通常刚装完系统时,默认可能是balanced,它试图在性能和能耗之间取得平衡。但对我们这种明确需要“吞吐优先”或“延迟极致”的场景来说,默认方案就不太够用了。

三、吞吐优先和延迟极致到底差在哪

用生活化的例子来说。吞吐优先就像超市收银台,希望每个小时结账的顾客总数最多,所以可以把收银台开成“批量处理”模式,一次给好几个顾客结账,整体效率高,但单个顾客等待时间会长一些。延迟极致就像急诊室,要求每个病人都能最快得到处理,所以哪怕外面排队的人很多,也要优先保证当前这个病人。

在Linux系统里,吞吐优先会倾向于把资源尽量利用满,比如让CPU持续高频运行、开启大页、增加队列长度。延迟极致则更关注响应速度,比如减少CPU频率切换、避免中断分散、降低网络延迟。

3.1 吞吐优先适合的场景

如果你的服务器主要跑数据采集、批量计算、日志处理,那吞吐优先就很合适。这类任务对单次耗时不太敏感,只要单位时间处理的数据量够大就行。这时候我们可以把CPU调频强制到最高,使用较大的内存页,并让磁盘IO调度器偏向顺序读写。

3.2 延迟极致适合的场景

如果你的服务器承载着在线交易、实时语音、工业控制等业务,那么每个请求的延迟都非常关键。哪怕只慢一毫秒,都可能影响用户体验甚至造成事故。这时候我们要尽可能减少一切不必要的等待,比如关闭CPU休眠状态、把网卡中断绑定到指定CPU、禁用TCP的延迟确认等。

3.3 影响这两种模式的常见参数

这里简单介绍几个关键参数。CPU governor是频率调节器,performance模式会一直保持最高频率,对延迟和吞吐都有好处,但更费电。transparent_hugepages是透明大页,开启后能减少页表开销,对吞吐有利,但可能导致某些场景的延迟抖动。net.ipv4.tcp_low_latency可以让TCP减少调度延迟,更适合延迟敏感的应用。磁盘IO调度器不同,比如mq-deadline在吞吐和延迟之间比较均衡,none则更适合SSD等快速设备。

这些参数在tuned的配置文件中都有体现,我们接下来会亲手做两个自定义方案。

四、动手写两个自己的profile

tuned的方案都放在/etc/tuned目录下面,每个方案是一个子目录,子目录里必须有一个tuned.conf文件。我们准备创建两个方案,一个给白天用,一个给晚上用。这里我用的技术栈是Shell脚本和tuned命令,所有操作都在RHEL终端里完成。

4.1 创建吞吐优先的profile

我们创建一个名叫throughput-optimized的方案。它继承自官方提供的throughput-performance,这样我们只需要修改几个我们关心的参数。执行下面的命令:

# 创建吞吐优先方案的目录
sudo mkdir -p /etc/tuned/throughput-optimized

# 使用tee命令生成配置文件(单引号EOF防止变量展开)
sudo tee /etc/tuned/throughput-optimized/tuned.conf <<'EOF'
# 吞吐优先方案配置
[main]
summary=自定义吞吐优先方案,继承官方吞吐性能
include=throughput-performance

[cpu]
# 让CPU始终运行在最高频率
governor=performance
# 性能优先,电费无所谓
energy_perf_bias=performance

[vm]
# 开启透明大页,减少内存页表开销
transparent_hugepages=always

[disk]
# 使用mq-deadline调度器,兼顾吞吐和延迟
scheduler=mq-deadline
EOF

# 激活这个新方案
sudo tuned-adm profile throughput-optimized

我们来解释一下。include=throughput-performance表示我们完全继承官方方案的所有参数,然后通过下面的块覆盖或追加我们自己的配置。CPU governor设为performance,系统就不会降频,能最大程度压榨CPU。transparent_hugepages开启后,当进程申请大块内存时,系统会尽量分配2MB的大页,减少TLB缓存未命中。磁盘调度器我们选mq-deadline,它适合多队列硬盘,在顺序和随机读写之间都能有不错的表现。

4.2 创建延迟极致的profile

接下来我们创建latency-ultra方案。这个方案基于官方latency-performance,但我们会加入一些更激进的低延迟优化。

# 创建延迟极致方案的目录
sudo mkdir -p /etc/tuned/latency-ultra

# 生成配置文件
sudo tee /etc/tuned/latency-ultra/tuned.conf <<'EOF'
# 延迟极致方案配置
[main]
summary=自定义延迟极致方案,继承官方延迟性能
include=latency-performance

[cpu]
# 同样锁频在最高,避免频率切换造成延迟
governor=performance
energy_perf_bias=performance
# 强制关闭CPU空闲状态,减少唤醒延迟
force_latency=1

[rt]
# 让中断默认绑定到当前所在CPU,减少跨代码切换
default_irq_affinity=true

[sysctl]
# 开启TCP低延迟模式,快速发送小包
net.ipv4.tcp_low_latency=1
EOF

# 切换到延迟极致方案
sudo tuned-adm profile latency-ultra

这里面的force_latency=1选项会让tuned关闭CPU的深度睡眠功能,宁可多耗电也要保证从睡眠到唤醒的时间最短。default_irq_affinity=true则是把设备中断尽量绑定到固定的CPU核心,避免中断在不同核心之间乱跑,从而减少缓存失效。tcp_low_latency会让TCP协议栈优先考虑延迟而不是吞吐,对于大量小包交互很有帮助。

4.3 关联技术:irqbalance

当我们提到中断亲和性时,系统里通常有一个叫irqbalance的服务,它会自动把中断分配到各个CPU核心,以平衡负载。但在延迟极致的场景里,我们可能希望手动绑定中断,而不是让irqbalance随机调度。如果irqbalance在运行,我们有可能会和tuned设置的中断亲和性产生冲突。所以在使用我们自定义的延迟方案时,可以暂时关闭irqbalance,或者调整它的配置。这种联动关系是我们在调优时要注意的。

五、让切换自动化起来

手动执行tuned-adm profile不是什么难事,但每天手动切两次也麻烦。我们可以写一个Shell脚本,再结合cron定时任务,让它自动在早晨和晚上切换。

下面这个脚本很简单,通过第一个参数来判断是切到吞吐还是延迟。

#!/bin/bash
# 自动切换tuned方案的脚本
# 用法:tuned-switch.sh morning|night

case "$1" in
    morning)
        # 早晨切到吞吐优先,应对白天的业务高峰
        sudo tuned-adm profile throughput-optimized
        ;;
    night)
        # 晚上切到延迟极致,应对低延迟任务
        sudo tuned-adm profile latency-ultra
        ;;
    *)
        # 其他参数则打印用法
        echo "用法: $0 {morning|night}"
        exit 1
        ;;
esac

# 显示当前生效的方案,方便排查
sudo tuned-adm active

将脚本保存到/usr/local/bin/tuned-switch.sh,并添加执行权限:

# 设置可执行权限
sudo chmod +x /usr/local/bin/tuned-switch.sh

然后编辑当前用户的crontab:

# 打开crontab编辑器
crontab -e

在编辑器里加入下面两行(这里只是展示,实际需要你手动在crontab里输入):

# 每天上午8:59切到吞吐优先(提前一分钟,避免业务刚开始再切)
59 8 * * * /usr/local/bin/tuned-switch.sh morning
# 每天下午20:59切到延迟极致
59 20 * * * /usr/local/bin/tuned-switch.sh night

保存之后,cron就会每天自动执行切换。如果当前机器上没有cron服务,RHEL已经默认安装了cronie,可以用systemctl status crond确认。

这样我们就实现了从吞吐优先到延迟极致的动态切换,整个过程不需要人工干预。

六、应用场景与优缺点分析

6.1 应用场景

这种动态切换特别适合两种需求并存的环境。比如一台数据库服务器,白天主要是大量OLTP查询,吞吐很重要;晚上跑批处理任务,比如报表汇总,这时如果能更快完成一批任务,会节省很多时间。另一个场景是云主机,白天被分配来跑Web服务,晚上被分配给深度学习训练,两个任务对参数的需求完全不同,用tuned切换比重启机器快得多。

6.2 优点

第一,tuned是RHEL原生组件,不需要安装额外软件,兼容性有保障。第二,切换不用重启系统,tuned会在后台平滑调整参数,对业务影响小。第三,方案支持继承和覆盖,我们可以复用官方大量经验,不用从零研究每个内核参数。第四,配合脚本可以实现完全自动化,大大减轻运维负担。

6.3 缺点

缺点也很明显。tuned切换需要一定时间,通常在几秒钟到几十秒之间,在切换过程中,系统参数会处于一个中间状态,如果刚好有高负载请求,可能会看到性能波动。另外,tuned会覆盖我们手动通过sysctl设置的部分参数,如果我们没有意识到这一点,可能会被“自己修改的参数莫名其妙失效”坑到。还有,不是所有参数在所有硬件上都有效,比如某些CPU governor在虚拟机里不生效,需要提前验证。

七、注意事项和踩过的坑

根据我的实践经验,有几个坑值得提醒大家。第一,别在业务高峰期切换方案,尽量选在低峰期切换。第二,切换后一定要检查日志,看tuned有没有报错,可以用journalctl -u tuned查看。第三,如果自定义方案里写了sysctl参数,要确保参数名正确,否则tuned可能加载失败。第四,建议在每个方案的[main]段里写清楚summary,方便以后识别。第五,在内存特别大的机器上开启transparent_hugepages有时候会引起内存碎片化,需要结合业务观察。第六,如果你修改了中断亲和性,记得把irqbalance关掉或者排除相关设备,否则可能导致配置被覆盖。第七,脚本里使用sudo时,要保证当前用户有免密sudo权限,否则cron执行时会卡在密码输入上。

八、总结

RHEL的tuned工具让我们从“手动改参数”的烦恼中解放了出来。通过自定义两个profile,一个吞吐优先,一个延迟极致,再配合一个Shell脚本和cron定时任务,就能让服务器在白天和晚上分别跑到最合适的状态。这个方法不需要重启,不需要额外安装软件,非常实用。如果你也有一台频繁切换工作负载的服务器,不妨照着这个思路试一试。调优不是一锤子买卖,而是持续观察、不断调整的过程,tuned给了我们一把好用的钥匙,剩下的就要靠我们的智慧去用了。