最近在做一个架构实验,一台1c1g的服务器,部署lnmp服务器,qps只有14左右。我在思考如何提高这个qps,极限压榨这台服务器的性能。

说实话,Linux内核优化这个话题,很多人觉得很高深,其实真正用起来,掌握几个关键点就够了。我在这个行业摸爬滚打这么多年,踩过的坑可以绕地球一圈,今天就把这些经验分享给大家。

为什么要做内核优化

你可能会问,系统默认配置不是挺好的吗?为什么还要折腾?

我给你举个例子,就像买了一辆新车,厂家的默认设置是为了适应大部分人的驾驶习惯,但如果你是个赛车手,肯定要根据赛道特点来调整悬挂、轮胎气压这些参数对吧?

Linux内核也是一样的道理。默认配置考虑的是通用性,但我们的服务器往往有特定的使用场景,比如高并发Web服务、数据库服务器、文件服务器等等,每种场景的优化重点都不一样。

我们之前有个客户项目,用户量突然暴增,服务器压力巨大。当时我们的第一反应是加机器,但预算有限,只能先从优化入手。通过一系列内核参数调整,性能提升了30%多,硬是撑过了那波流量高峰。

网络参数优化 - 让数据传输更顺畅

网络优化是我最常遇到的需求,特别是做Web服务的时候。

TCP连接数限制经常是个大问题。默认情况下,系统的文件描述符限制比较保守,高并发场景下很容易成为瓶颈。

# 查看当前限制
ulimit -n

# 临时修改
ulimit -n 65535

# 永久修改需要编辑 /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535

不过光改这个还不够,还得调整内核的网络参数。我一般会这样配置:

# 编辑 /etc/sysctl.conf
#设置系统级别的socket监听队列的最大长度,当应用程序调用listen()时,backlog参数不能超过这个值。提高此值可以允许更多的连接请求排队等待处理
net.core.somaxconn = 32768
#网络设备接收数据包的队列最大长度,当网络接口接收数据包的速度超过内核处理速度时,数据包会在此队列中排队
net.core.netdev_max_backlog = 32768
#设置SYN_RECV状态的连接队列最大长度,控制处于半连接状态的TCP连接数量,防止SYN flood攻击
net.ipv4.tcp_max_syn_backlog = 32768
#设置TCP连接在FIN-WAIT-2状态的超时时间,缩短TIME_WAIT状态的持续时间,更快释放连接资源
net.ipv4.tcp_fin_timeout = 10
#设置TCP keepalive探测的间隔时间,更频繁地检测死连接,及时清理无效连接
net.ipv4.tcp_keepalive_time = 1200
#设置系统中TIME_WAIT状态连接的最大数量,限制TIME_WAIT连接数,防止占用过多系统资源
net.ipv4.tcp_max_tw_buckets = 32768

这里面每个参数都有它的作用。比如somaxconn控制的是监听队列的最大长度,如果你的应用连接数很高,这个值设小了就会导致连接被拒绝。

tcp_fin_timeout我一般设置得比较小,因为TIME_WAIT状态的连接太多会占用大量资源。当然这个值也不能设得太小,否则可能会有问题。

有一次我把这个值设成了5秒,结果发现偶尔会有连接异常,后来查资料发现是因为网络延迟导致的,最后调整到10秒比较合适。

内存管理优化

内存这块的优化主要集中在几个方面。

交换分区的使用策略是个重点。很多人觉得swap就是内存不够时的备胎,其实不完全是这样。Linux会根据swappiness参数来决定什么时候使用swap。

# 查看当前值
#cat /proc/sys/vm/swappiness
[root@webtest ~]# cat /proc/sys/vm/swappiness
30
[root@webtest ~]#
# 修改为10(默认通常是60)
#echo 10 > /proc/sys/vm/swappiness

我一般把这个值设置得比较低,特别是数据库服务器。因为数据库对延迟很敏感,一旦数据被swap到磁盘,性能就会急剧下降。

内存回收策略也很重要:

vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500

这几个参数控制的是脏页的回写策略。脏页就是在内存中被修改但还没写入磁盘的数据页。如果脏页太多,系统性能会受影响;但如果回写太频繁,磁盘IO压力又会很大。

我记得有次处理一个文件服务器的性能问题,发现写入大文件时系统会卡顿。后来发现是脏页比例设置得太高,导致积累了大量脏页,集中回写时把磁盘IO打满了。调整这几个参数后问题就解决了。

进程调度优化

CPU调度器的优化相对来说比较少用到,但在某些场景下效果很明显。

Linux有几种调度器可以选择,默认的CFS(完全公平调度器)适合大部分场景,但如果你的应用对延迟要求很高,可能需要调整一些参数。

# 查看当前调度策略
#cat /proc/sys/kernel/sched_*

[root@webtest ~]# cat /proc/sys/kernel/sched_*
0
5000
0
4194304
100
cat: /proc/sys/kernel/sched_domain: Is a directory
1
6000000
500000
10000000
32
100
1000000
950000
0
1
15000000

# 调整时间片长度
echo 1000000 > /proc/sys/kernel/sched_latency_ns

调度器相关参数解释

1. sched_autogroup_enabled = 0

  • 作用:控制是否启用自动分组调度
  • :0表示禁用,1表示启用
  • 影响:禁用后所有进程在同一调度组中竞争CPU

2. sched_cfs_bandwidth_slice_us = 5000

  • 作用:CFS带宽控制的时间片长度(微秒)
  • 默认值:5000微秒(5毫秒)
  • 影响:控制cgroup带宽限制的精度

3. sched_child_runs_first = 0

  • 作用:控制fork后是否让子进程优先运行
  • :0表示父进程优先,1表示子进程优先
  • 影响:影响进程创建后的调度顺序

4. sched_latency_ns = 4194304

  • 作用:CFS调度器的目标延迟时间(纳秒)
  • :4194304纳秒 ≈ 4.19毫秒
  • 影响:所有可运行进程至少运行一次的时间周期

5. sched_migration_cost_ns = 100

  • 作用:进程迁移的成本估算(纳秒)
  • 影响:影响负载均衡时进程在CPU间的迁移决策

6. sched_min_granularity_ns = 1000000

  • 作用:进程运行的最小时间粒度(纳秒)
  • :1000000纳秒 = 1毫秒
  • 影响:保证每个进程至少运行的时间

7. sched_nr_migrate = 32

  • 作用:负载均衡时单次迁移的最大进程数
  • 影响:控制负载均衡的开销和效果

8. sched_rr_timeslice_ms = 100

  • 作用:SCHED_RR调度策略的时间片长度(毫秒)
  • 影响:实时轮转调度的时间片大小

9. sched_rt_period_us = 1000000

  • 作用:实时调度的周期时间(微秒)
  • :1000000微秒 = 1秒
  • 影响:实时进程调度的时间窗口

10. sched_rt_runtime_us = 950000

  • 作用:实时进程在一个周期内可使用的最大CPU时间(微秒)
  • :950000微秒 = 0.95秒
  • 影响:限制实时进程的CPU使用,为普通进程保留5%的CPU时间

11. sched_schedstats = 0

  • 作用:控制是否启用调度统计信息收集
  • :0表示禁用,减少统计开销

12. sched_tunable_scaling = 1

  • 作用:控制调度参数是否根据CPU数量自动缩放
  • :1表示启用对数缩放

13. sched_wakeup_granularity_ns = 15000000

  • 作用:唤醒抢占的粒度控制(纳秒)
  • :15000000纳秒 = 15毫秒
  • 影响:控制进程被唤醒时是否能抢占当前运行的进程

不过说实话,调度器参数我很少动,除非遇到特殊需求。因为这块的影响面比较大,改错了可能会影响整个系统的稳定性。

倒是进程优先级的调整用得比较多。对于关键业务进程,可以通过nice值来提高优先级:

# 启动时设置优先级
nice -n -10 ./important_service

# 运行时调整
renice -10 -p 进程ID

文件系统优化

文件系统这块的优化主要看你用的是什么文件系统。

ext4的话,挂载参数很重要:

# /etc/fstab 中的配置示例
/dev/sda1 /data ext4 defaults,noatime,nodiratime,barrier=0 0 2

noatime参数可以避免每次读取文件时更新访问时间,对于读多写少的场景能提升不少性能。barrier=0可以提高写入性能,但要确保硬件有电池保护,否则可能有数据丢失风险。

如果是数据库服务器,我更推荐使用xfs文件系统,它在大文件和高并发场景下表现更好。

# 格式化为xfs
mkfs.xfs -f /dev/sda1

# 挂载时的优化参数
mount -t xfs -o noatime,nodiratime,logbufs=8,logbsize=256k /dev/sda1 /data

IO调度器选择

IO调度器的选择对性能影响很大,特别是在高IO负载的场景下。

Linux提供了几种IO调度器:

# 查看当前调度器
cat /sys/block/sda/queue/scheduler

# 修改调度器
echo deadline > /sys/block/sda/queue/scheduler

对于SSD,我一般推荐使用noop或者deadline调度器,因为SSD没有机械磁盘的寻道时间,复杂的调度算法反而会增加延迟。

传统机械硬盘的话,cfq调度器通常是个不错的选择,它能保证各个进程的IO请求相对公平。

我之前遇到过一个案例,数据库服务器使用的是SSD,但IO调度器还是默认的cfq,导致数据库响应时间不稳定。改成deadline后,响应时间的波动明显减小了。

内核模块和服务优化

系统启动时会加载很多内核模块和服务,但并不是所有的都是必需的。

可以通过以下命令查看当前加载的模块:

lsmod

对于不需要的模块,可以加入黑名单:

# 编辑 /etc/modprobe.d/blacklist.conf
blacklist 模块名

系统服务也是一样,可以关闭不必要的服务:

# 查看所有服务状态
systemctl list-units --type=service

# 关闭不需要的服务
systemctl disable 服务名
systemctl stop 服务名

不过这里要小心,关闭服务前一定要确认它的作用,别把重要的服务给关了。我见过有人把网络服务给关了,结果远程连不上服务器,只能跑机房去恢复。

监控和调试工具

优化完了还得有监控手段,不然怎么知道效果如何?

我常用的监控工具有这些:

# 系统整体性能
top, htop, atop

# 网络监控
iftop, nethogs, ss

# 磁盘IO监控
iotop, iostat

# 内存使用情况
free, vmstat

# 进程跟踪
strace, ltrace

perf工具特别好用,可以分析系统的性能瓶颈:

# 采样系统性能
perf record -g ./your_program

# 查看报告
perf report

通过perf可以看到哪些函数占用CPU最多,对于性能优化很有帮助。

实际案例分享

我来分享一个真实的优化案例。

去年有个电商客户,双11期间服务器压力巨大,经常出现响应超时。我们接到求助后,第一时间上去看了看系统状态。

通过监控发现,CPU使用率不高,内存也够用,但网络连接数接近上限,而且TIME_WAIT状态的连接特别多。

分析后发现主要问题在于:

  1. 文件描述符限制太小
  2. TCP参数没有针对高并发场景优化
  3. 应用层面的连接池配置不合理

我们的优化步骤是这样的:

调整系统参数:

# 增加文件描述符限制
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "* hard nofile 65535" >> /etc/security/limits.conf

# 优化TCP参数
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
echo "net.ipv4.tcp_fin_timeout = 10" >> /etc/sysctl.conf
echo "net.core.somaxconn = 32768" >> /etc/sysctl.conf

sysctl -p

同时建议应用团队调整了连接池的配置,增加了连接复用。

优化后,系统的并发处理能力提升了40%多,成功撑过了双11的流量高峰。客户特别满意,后来还介绍了其他项目给我们。

注意事项和踩坑经验

做内核优化有几个要特别注意的地方。

参数不是越大越好。我见过有人觉得缓冲区大一点总是好的,结果把各种buffer都设得很大,反而导致内存不够用。

修改前一定要备份原始配置。我的习惯是每次修改前都会把原来的值记录下来,万一有问题可以快速回滚。

# 修改前先备份
cp /etc/sysctl.conf /etc/sysctl.conf.backup.$(date +%Y%m%d)

测试要充分。内核参数的影响往往不是立竿见影的,需要在实际负载下运行一段时间才能看出效果。我一般会先在测试环境验证,然后在生产环境小范围试点,最后才全面推广。

不要一次改太多参数。如果同时修改很多参数,出了问题很难定位是哪个参数导致的。我的建议是每次只改一两个相关的参数,观察一段时间后再继续。

还有就是要考虑业务特点。同样的优化参数,在不同的业务场景下效果可能完全不同。比如说,对于批处理任务,可能更关注吞吐量;但对于在线服务,延迟可能更重要。

不同场景的优化重点

Web服务器的优化重点通常在网络和并发连接数上。除了前面提到的TCP参数,还要关注Apache或Nginx的配置。

数据库服务器就不一样了,内存和磁盘IO是关键。我一般会把swappiness设得很低,甚至直接关闭swap。同时调整内存分配策略:

vm.overcommit_memory = 2
vm.overcommit_ratio = 80

这样可以避免系统过度分配内存导致OOM killer被触发。

文件服务器的话,磁盘IO和文件系统参数是重点。除了前面说的挂载参数,还可以调整读写缓存:

vm.dirty_ratio = 15
vm.dirty_background_ratio = 5

缓存服务器(比如Redis、Memcached)对内存延迟特别敏感,可能需要调整NUMA相关的参数:

# 查看NUMA信息
numactl --hardware

# 绑定进程到特定NUMA节点
numactl --cpunodebind=0 --membind=0 redis-server

自动化优化脚本

手动一个个改参数太麻烦了,我写了个脚本来自动化这个过程:

#!/bin/bash
# 系统优化脚本

# 备份原始配置
cp /etc/sysctl.conf /etc/sysctl.conf.backup.$(date +%Y%m%d)

# 网络优化
cat >> /etc/sysctl.conf << EOF
# 网络优化参数
net.core.somaxconn = 32768
net.core.netdev_max_backlog = 32768
net.ipv4.tcp_max_syn_backlog = 32768
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 1200
EOF

# 内存优化
cat >> /etc/sysctl.conf << EOF
# 内存优化参数
vm.swappiness = 10
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
EOF

# 应用配置
sysctl -p

echo "优化完成,建议重启系统使所有配置生效"

当然,这只是个基础版本,实际使用时还要根据具体场景来调整。

性能测试和验证

优化完了怎么验证效果呢?我一般会用几种方法。

压力测试是必不可少的。对于Web服务,我常用ab或者wrk:

# Apache Bench测试
ab -n 10000 -c 100 http://your-server/

# wrk测试(功能更强大)
wrk -t12 -c400 -d30s http://your-server/

数据库的话,可以用sysbench:

# MySQL压力测试
sysbench oltp_read_write --mysql-host=localhost --mysql-user=root --mysql-password=password --mysql-db=test --tables=10 --table-size=100000 run

网络性能可以用iperf:

# 服务端
iperf -s

# 客户端
iperf -c server_ip -t 60

除了压力测试,日常监控也很重要。我会设置一些关键指标的监控,比如连接数、响应时间、CPU使用率等。一旦发现异常,就能及时处理。

容器化环境的特殊考虑

现在很多应用都跑在容器里,容器化环境的内核优化有些特殊之处。

Docker容器默认会继承宿主机的内核参数,但有些参数是容器级别的,需要在容器启动时指定:

# 启动容器时调整参数
docker run --sysctl net.core.somaxconn=32768 --sysctl net.ipv4.tcp_keepalive_time=600 your-image

Kubernetes环境下,可以通过securityContext来设置:

apiVersion: v1
kind: Pod
spec:
  securityContext:
    sysctls:
    - name: net.core.somaxconn
      value: "32768"
    - name: net.ipv4.tcp_keepalive_time
      value: "600"

不过要注意,不是所有的内核参数都能在容器里修改,有些涉及到安全的参数是受限制的。

云环境的优化策略

在云环境下,有些优化策略需要调整。

比如在AWS EC2上,网络性能跟实例类型关系很大。小实例的网络带宽有限,再怎么优化TCP参数也提升不了多少。这时候可能需要考虑升级实例类型。

云硬盘的IOPS也是有限制的,特别是gp2类型的EBS,IOPS跟存储容量相关。如果IO是瓶颈,可能需要换成io1或者gp3类型。

还有就是云厂商通常会提供一些优化建议,比如AWS的Enhanced Networking,阿里云的多队列网卡等,这些特性要充分利用起来。

监控和告警设置

优化不是一次性的工作,需要持续监控和调整。

我一般会设置这些监控指标:

系统层面:

  • CPU使用率和负载
  • 内存使用率和swap使用情况
  • 磁盘IO和使用率
  • 网络流量和连接数

应用层面:

  • 响应时间和吞吐量
  • 错误率
  • 连接池状态
  • 缓存命中率

告警阈值不要设得太敏感,否则会有很多误报。我的经验是先设置得宽松一点,运行一段时间后根据实际情况再调整。

# 简单的监控脚本示例
#!/bin/bash
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
MEMORY_USAGE=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100.0}')
DISK_USAGE=$(df -h / | awk 'NR==2{print $5}' | cut -d'%' -f1)

if [ $(echo "$CPU_USAGE > 80" | bc) -eq 1 ]; then
    echo "CPU使用率过高: $CPU_USAGE%"
fi

if [ $(echo "$MEMORY_USAGE > 85" | bc) -eq 1 ]; then
    echo "内存使用率过高: $MEMORY_USAGE%"
fi

版本升级的考虑

Linux内核版本的选择也很重要。新版本通常会有性能改进和新特性,但稳定性可能不如LTS版本。

对于生产环境,我一般推荐使用LTS版本,比如目前的5.4或5.15。这些版本经过了长期的测试和验证,bug相对较少。

不过如果你的应用需要某些新特性,比如eBPF、io_uring等,可能就需要使用较新的内核版本。这时候要做好充分的测试。

升级内核前一定要做好备份和回滚准备。我见过因为内核升级导致系统无法启动的情况,如果没有备份就很麻烦了。

结论测试

上面都是理论。我开了一台1c1g虚拟机实用ab测试结果如下:

测试前的qps:59.54
2025-07-31T02:15:05.png

测试后的qps:61.53

![image-20250731000253527](./assets/image-20250731000253527.png)

结论:不如加机器,当然还是有点作用的。这次是1c1g的。经过优化后有微量提升。但是在大点的实例上面,或许效果会更好!!!

最后一哆嗦

Linux内核优化确实是个技术活,但掌握了基本原理和常用参数,大部分场景都能应对。

关键是要理解你的应用特点和性能瓶颈在哪里。网络密集型应用重点优化网络参数,IO密集型应用关注磁盘和文件系统,内存密集型应用调整内存管理策略。

优化是个持续的过程,不要指望一次就能解决所有问题。先解决最明显的瓶颈,然后逐步完善。

最重要的是要有监控和测试手段。没有数据支撑的优化都是盲目的,可能会适得其反。

我这些年踩过的坑总结起来就是:备份、测试、监控、渐进式优化。只要按照这个原则来,基本不会出大问题。

当然,每个环境都有自己的特点,我分享的这些经验只是个参考。具体怎么优化,还是要结合实际情况来分析。

如果你在优化过程中遇到问题,欢迎交流讨论。毕竟这个领域的坑确实不少,大家一起填坑,能少走很多弯路。

希望这篇文章对你有帮助。如果觉得有用的话,别忘了点赞转发,让更多的朋友看到。关注@运维躬行录,我会持续分享更多实用的运维技术和经验,咱们一起在运维这条路上越走越远!

标签: none