Ubuntu服务器在高并发场景下出现性能瓶颈,往往不是单一原因造成的,而是文件描述符限制、TCP内核参数、内存回收策略、Web服务进程模型和反向代理缓冲共同作用的结果。要真正提升并发处理能力,必须从系统资源限制、内核网络栈、应用运行方式和架构分层几个维度同时调整,否则很容易出现“CPU没满但请求排队超时”或者“内存充足但连接被拒绝”的诡异现象。
先解除文件描述符和进程数限制
Ubuntu默认的单进程文件描述符上限通常是1024,对于高并发服务来说这个值会在压力上来后立刻触发“Too many open files”错误。很多运维只修改了/etc/security/limits.conf,却忽略了systemd管理下的服务还需要单独配置。正确做法是同时处理两处:
# /etc/security/limits.conf root soft nofile 655350 root hard nofile 655350 * soft nofile 655350 * hard nofile 655350 * soft nproc 655350 * hard nproc 655350
然后检查/etc/systemd/system/目录下对应服务的service文件,或者直接在[Service]段加入:
LimitNOFILE=655350 LimitNPROC=655350
修改后执行systemctl daemon-reload并重启服务。单纯修改limits.conf对由systemd拉起的进程经常无效,这是Ubuntu 16.04之后版本的一个常见坑。同时还要确认/proc/sys/fs/file-max的值足够大,一般建议设置为内存KB数的10%左右,例如64GB内存可以设置fs-max=6553500。
TCP内核参数必须针对高并发场景调整
Ubuntu默认内核参数偏向通用桌面和低负载服务器,高并发下会出现大量TIME_WAIT连接、SYN队列溢出和端口快速耗尽。下面这组参数是经过大量生产环境验证的基础优化:
net.core.somaxconn = 65535 net.core.netdev_max_backlog = 65535 net.ipv4.tcp_max_syn_backlog = 65535 net.ipv4.tcp_fin_timeout = 10 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 0 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_tw_buckets = 550000 net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 3 net.core.rmem_default = 262144 net.core.wmem_default = 262144 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 net.ipv4.tcp_mem = 786432 1048576 1572864 net.ipv4.tcp_slow_start_after_idle = 0
这里特别说明两点:tcp_tw_recycle在Ubuntu 4.12以上内核已经移除,设置为0是兼容写法;tcp_tw_reuse开启后可以安全复用TIME_WAIT状态的连接,对于短连接密集的API服务效果非常明显。另外tcp_slow_start_after_idle设置为0可以避免空闲后的连接重新进入慢启动,对长连接和连接池非常关键。
内存回收和Swap策略要主动干预
高并发服务最怕的就是内存回收时直接回收匿名页导致业务进程被阻塞,或者Swap频繁换入换出让响应时间从毫秒级恶化到秒级。Ubuntu默认的vm.swappiness=60对于数据库和缓存类服务来说过于激进。建议:
vm.swappiness = 10 vm.vfs_cache_pressure = 50 vm.dirty_ratio = 10 vm.dirty_background_ratio = 5 vm.dirty_expire_centisecs = 500 vm.dirty_writeback_centisecs = 100 vm.min_free_kbytes = 262144
如果服务器内存足够大,比如128GB以上,可以直接把vm.swappiness设为1甚至0。但不要完全关闭Swap,因为某些极端情况下OOM Killer杀掉关键进程比慢一点更糟。dirty_ratio和dirty_background_ratio调低是为了避免大量脏页堆积后一次性刷盘导致IO尖峰,这对MySQL、Redis和日志写入密集的服务尤其重要。
Web服务器和反向代理的并发模型选择
Nginx在Ubuntu上默认的worker_connections只有768,而且events块可能没有显式配置。高并发场景下必须改成:
user www-data;
worker_processes auto;
worker_rlimit_nofile 655350;
events {
use epoll;
worker_connections 65535;
multi_accept on;
}
http {
keepalive_timeout 65;
keepalive_requests 10000;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
client_body_buffer_size 128k;
client_max_body_size 20m;
client_header_buffer_size 4k;
large_client_header_buffers 4 16k;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
worker_processes设置为auto让Nginx自动匹配CPU核心数,worker_connections直接拉满到65535,这样单机理论并发连接数可以达到worker_processes乘以worker_connections。multi_accept开启后每个worker会一次接受所有新连接,减少内核唤醒开销。open_file_cache在高并发静态文件服务中能显著降低磁盘IO和系统调用次数。
PHP-FPM的进程管理要避免资源浪费
很多Ubuntu服务器用Nginx+PHP-FPM跑业务,默认的pm=dynamic配置在并发波动大的场景下会频繁创建和销毁进程,导致响应延迟不稳定。对于高并发且内存充足的机器,推荐使用pm=static并直接固定进程数:
pm = static pm.max_children = 200 pm.max_requests = 10000 pm.process_idle_timeout = 10s request_terminate_timeout = 30s catch_workers_output = yes php_admin_value[memory_limit] = 256M
pm.max_children的计算方法是:每台服务器可用内存除以单个PHP进程平均内存占用。比如64GB内存,预留16GB给系统和其他服务,剩余48GB,每个PHP-FPM进程平均占用200MB,那么max_children可以设为240左右。pm.max_requests设置为10000是为了定期回收进程,防止PHP内存泄漏累积。request_terminate_timeout必须设置,否则慢请求会占住worker不放,导致其他请求排队。
MySQL和Redis的连接池与线程参数
高并发下数据库层往往是最终瓶颈。Ubuntu上MySQL默认的max_connections通常只有151,这对于API服务来说远远不够。但也不能盲目调大,因为每个连接都会消耗线程和内存。建议结合应用连接池来配置:
[mysqld] max_connections = 1000 max_connect_errors = 100000 thread_cache_size = 256 table_open_cache = 4096 table_definition_cache = 2048 innodb_buffer_pool_size = 48G innodb_buffer_pool_instances = 16 innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT innodb_io_capacity = 2000 innodb_io_capacity_max = 4000 innodb_thread_concurrency = 0 innodb_read_io_threads = 16 innodb_write_io_threads = 16 innodb_log_file_size = 4G innodb_log_buffer_size = 256M
innodb_flush_log_at_trx_commit设置为2可以在性能和数据安全之间取得平衡,每秒刷一次日志而不是每次提交都刷,能大幅提升写入吞吐。innodb_buffer_pool_instances设置为16是为了减少高并发下的缓冲池锁竞争。Redis方面除了设置maxclients外,还要注意关闭THP(透明大页),否则内存碎片和延迟会非常严重:
echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag
同时建议在/etc/rc.local或systemd服务中持久化这个设置,否则重启后失效。
网络中断和CPU亲和性优化
高并发流量下,网卡中断可能全部集中在CPU0上,导致单核被打满而其他核心空闲。Ubuntu可以通过irqbalance服务自动分配中断,但对于万兆网卡和极端并发场景,手动绑定中断到不同CPU核心效果更好。先查看网卡中断号:
cat /proc/interrupts | grep eth0
然后将不同的中断号绑定到不同CPU核心,例如:
echo 2 > /proc/irq/123/smp_affinity echo 4 > /proc/irq/124/smp_affinity echo 8 > /proc/irq/125/smp_affinity
这里的数字是CPU亲和性掩码,2代表CPU1,4代表CPU2,8代表CPU3。对于多队列网卡,每个队列的中断绑定到不同核心可以显著提升网络吞吐和降低延迟。如果使用systemd管理的服务,还可以通过CPUAffinity指令限制服务进程运行在特定核心上,避免与网卡中断争抢资源。
日志和文件系统层面的细节
高并发服务会产生大量日志,如果日志写入阻塞了主业务线程,性能会急剧下降。Nginx和PHP-FPM的日志建议异步处理,或者直接输出到tmpfs内存文件系统再定期同步到磁盘。对于ext4文件系统,挂载时加上noatime,nodiratime选项可以减少元数据写入:
/dev/sda1 / ext4 defaults,noatime,nodiratime,errors=remount-ro 0 1
对于数据库数据目录,建议使用XFS文件系统并挂载时加上noatime,nodiratime,logbufs=8,logbsize=32k选项。XFS在高并发随机写入场景下比ext4表现更稳定,特别是在大文件和大IO深度下。
监控和压测验证配置效果
所有参数调整后必须通过压测来验证实际效果,而不是凭感觉。推荐使用wrk或ab进行HTTP压测,使用sysbench测试数据库性能。压测时要同时观察vmstat、iostat、netstat和top的输出,重点关注:CPU的sy和us比例是否合理,si和so是否频繁出现,磁盘util是否长期接近100%,TCP连接状态中TIME_WAIT和SYN_RECV的数量是否异常。如果压测过程中出现连接被拒绝但CPU和内存都未打满,大概率是文件描述符或内核网络参数没有生效,需要回头检查systemd配置和sysctl是否真正加载。
高并发优化是一个系统工程,单点调整往往收效甚微。Ubuntu服务器的优势在于内核较新、工具链完善,但默认配置离生产级高并发还有很大距离。只有把资源限制、内核参数、服务进程模型、数据库连接和网络中断全部协调到位,才能在流量洪峰到来时保持稳定和低延迟。
