PHP-FPM 容器在鲲鹏 ARM64 性能异常排查与信创内核调优 --- 一、为什么鲲鹏 ARM64 会有性能问题? 鲲鹏处理器用的是 ARM64 架构,和 x86(Intel/AMD)不一样。容器里跑 PHP-FPM 时,最常见的坑: ┌───────────────────────────┬───────────────────────────────────────────────────┐ │ 问题根源 │ 大白话 │ ├───────────────────────────┼───────────────────────────────────────────────────┤ │ JIT 没开 │ PHP8.1+ 有即时编译,ARM64 上收益更大,但默认关着 │ ├───────────────────────────┼───────────────────────────────────────────────────┤ │ 内存页太小 │ 默认 4KB 页,ARM64 支持 64KB 大页,没开就白费 │ ├───────────────────────────┼───────────────────────────────────────────────────┤ │ 内核网络参数是默认值 │ 高并发时连接队列满了直接丢请求 │ ├───────────────────────────┼───────────────────────────────────────────────────┤ │ 容器 CPU/内存限制设错 │ 限制太死导致进程被 OOM Kill │ ├───────────────────────────┼───────────────────────────────────────────────────┤ │ 进程数算法没针对 ARM64 调 │ ARM64 内存访问模式和 x86 不同 │ └───────────────────────────┴───────────────────────────────────────────────────┘ --- 二、第一步:排查——先看现在哪里出了问题2.1快速诊断脚本(直接跑)#!/bin/bash# 文件名: diagnose_phpfpm.sh# 用法: bash diagnose_phpfpm.shecho"===== PHP-FPM 进程状态 ====="psaux|grepphp-fpm|grep-vgrepecho""echo"===== PHP-FPM 内存总占用 ====="psaux|grepphp-fpm|grep-vgrep|awk'{sum+=$6} END { printf "总内存: %.1f MB (%.1f GB)\n", sum/1024, sum/1024/1024 }'echo""echo"===== 系统连接数 ====="ss-s# 看 TCP 连接统计echo""echo"===== 文件句柄使用情况 ====="cat/proc/sys/fs/file-nr# 输出: 已用 空闲 上限echo""echo"===== 当前内核关键参数 ====="sysctlnet.core.somaxconn# 连接队列长度(默认128,太小)sysctlnet.ipv4.tcp_max_syn_backlogsysctlvm.swappiness# 换页倾向(默认60,太高)sysctlvm.nr_hugepages# 大页数量(默认0)echo""echo"===== CPU 架构确认 ====="uname-m# 应该输出: aarch64lscpu|grep-E"Architecture|CPU\(s\)|Thread"echo""echo"===== PHP 版本和 JIT 状态 ====="php-vphp-r"echo opcache_get_status()['jit']['enabled'] ? 'JIT: 开启' : 'JIT: 关闭'; echo PHP_EOL;"2.2查看 PHP-FPM 状态页(需要先开启)# 查询状态(返回 JSON 格式)curlhttp://127.0.0.1/fpm-status?json|python3-mjson.tool# 关键字段说明:# "accepted conn" → 累计接受的请求数# "listen queue" → 当前等待处理的请求数(这个 > 0 就说明在排队,要加 worker)# "active processes" → 当前活跃的 PHP-FPM 子进程数# "idle processes" → 空闲进程数# "max children reached" → 这个 > 0 说明进程数不够用了!2.3慢请求日志分析# 查最慢的请求(按脚本路径分组统计)grep"script_filename"/var/log/php-fpm/www-slow.log|\awk-F'=''{print $2}'|\sort|uniq-c|sort-rn|head-20# 实时监控慢日志tail-f/var/log/php-fpm/www-slow.log --- 三、第二步:PHP-FPM 进程池配置 大白话:pm.max_children 是最重要的参数,就是"最多开多少个 PHP 工人"。3.1进程数计算公式 pm.max_children=可用内存(MB)÷ 单个 PHP-FPM 进程内存(MB)# 先查单个进程占多少内存(单位 KB)ps--no-headers-orss-p$(pgrep php-fpm|head-1)# 假设输出 61440(即 60MB)# 假设服务器 16GB,给 PHP-FPM 分配 8GB:# pm.max_children = 8192MB ÷ 60MB ≈ 136,取 1283.2完整配置文件 /usr/local/etc/php-fpm.d/www.conf[www];==========进程管理模式==========;dynamic=动态模式,根据请求量自动增减进程(推荐);static=静态模式,固定数量进程(极高并发用这个);ondemand=按需启动(低流量省内存用这个) pm=dynamic;==========进程数量(8核16GB 服务器示例)==========;最多开多少个工人 pm.max_children=128;启动时先开多少个工人(=CPU核数 ×4) pm.start_servers=32;至少保持多少个空闲工人(=CPU核数 ×2) pm.min_spare_servers=16;最多保持多少个空闲工人(=CPU核数 ×4) pm.max_spare_servers=32;每个工人处理多少请求后重启(防止内存泄漏);ARM64 上建议设小一点,2500~5000 pm.max_requests=5000;工人空闲多久后退出(dynamic/ondemand 模式) pm.process_idle_timeout=10s;==========通信方式==========;Unix Socket 比 TCP 快10~30%,同机部署 Nginx+PHP-FPM 必须用这个 listen=/run/php-fpm/www.sock;等待队列长度,必须和内核 net.core.somaxconn 一致 listen.backlog=65535listen.owner=www-data listen.group=www-data listen.mode=0660;==========超时保护==========;单个请求最多执行多少秒,超时直接 kill(防止慢请求拖死全服务) request_terminate_timeout=30s;超过多少秒记入慢日志 request_slowlog_timeout=5s slowlog=/var/log/php-fpm/www-slow.log;==========访问日志==========;格式含执行时间(ms)和内存(KB),便于排查 access.log=/var/log/php-fpm/www-access.log access.format="%R - %u %t\"%m %r\"%s %f %{mili}dms %{kilo}M";==========PHP 运行参数==========php_admin_value[memory_limit]=256M;==========OPcache(PHP 字节码缓存,必开)==========php_admin_value[opcache.enable]=1php_admin_value[opcache.memory_consumption]=256;缓存内存,单位MB php_admin_value[opcache.interned_strings_buffer]=16php_admin_value[opcache.max_accelerated_files]=10000;最多缓存多少个文件 php_admin_value[opcache.validate_timestamps]=0;生产环境关掉,省去文件检查;==========JIT 编译(PHP8.1+,ARM64 收益更大)==========;tracing 模式:运行时分析热点函数再编译,最好的选择 php_admin_value[opcache.jit]=tracing php_admin_value[opcache.jit_buffer_size]=100M;==========状态监控端点==========pm.status_path=/fpm-status ping.path=/fpm-ping ping.response=pong --- 四、第三步:信创内核参数调优 大白话:就是告诉 Linux 内核"你要为高并发服务,不是普通桌面电脑"。4.1创建调优配置文件# 文件名: /etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf# 创建后执行: sysctl -p /etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf# =============================================# 网络栈调优# =============================================# 监听队列长度(默认128,高并发必须调大)# 大白话:这是"排队等待被 PHP-FPM 处理的请求"的队列,太小了请求就被丢弃net.core.somaxconn=65535net.ipv4.tcp_max_syn_backlog=65535# TCP 读写缓冲区大小(三个值:最小/默认/最大,单位字节)net.ipv4.tcp_rmem=40968738067108864net.ipv4.tcp_wmem=40966553667108864net.core.rmem_max=134217728net.core.wmem_max=134217728# 可用本地端口范围(PHP-FPM 建很多连接,端口不够会报错)net.ipv4.ip_local_port_range=102465535# TIME_WAIT 状态的连接可以被新连接复用(减少端口占用)net.ipv4.tcp_tw_reuse=1# TCP KeepAlive:检测死连接(单位:秒)net.ipv4.tcp_keepalive_time=600;空闲多久开始发探针 net.ipv4.tcp_keepalive_probes=3;发几次没响应就断开 net.ipv4.tcp_keepalive_intvl=15;探针间隔# TCP Fast Open:减少握手延迟(内核 3.13+)net.ipv4.tcp_fastopen=3# =============================================# 内存管理# =============================================# 换页积极程度(0~100,默认60)# 大白话:越小越不愿意把内存换到磁盘,10 表示非常不愿意# 服务器内存够用就设低,防止 PHP 进程被换出去vm.swappiness=10# 脏页比例控制(脏页 = 已修改但还没写入磁盘的内存页)vm.dirty_ratio=15;内存用这么多脏页后强制写盘 vm.dirty_background_ratio=5;后台开始写盘的阈值# VFS 缓存回收压力(默认100,降低保留更多文件系统缓存)vm.vfs_cache_pressure=50# 保留最小空闲内存(单位 KB,防止内存完全耗尽)vm.min_free_kbytes=262144# NUMA 架构下关闭本地内存优先回收(鲲鹏多路服务器必须关)# 大白话:不要只用本地 NUMA 节点的内存,让内存可以跨节点vm.zone_reclaim_mode=0# =============================================# 大页内存(Huge Pages)—— ARM64 专属优化# =============================================# 分配多少个 2MB 大页(对比默认 4KB 小页,减少 TLB 缺失)# 大白话:把小纸片换成大纸,每次查地址表效率更高# 512个大页 = 1GB,可根据内存大小调整vm.nr_hugepages=512# =============================================# 文件系统# =============================================# 系统最大文件句柄数(PHP-FPM 进程多的时候容易不够用)fs.file-max=2097152fs.nr_open=2097152# =============================================# 进程调度(鲲鹏 ARM64 NUMA 优化)# =============================================# 进程迁移代价(纳秒),防止进程在 CPU 间频繁跳动kernel.sched_migration_cost_ns=5000000# 调度延迟(一个任务最多等多久才被调度)kernel.sched_latency_ns=240000004.2立即生效# 应用所有参数sysctl-p/etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf# 验证是否生效sysctlnet.core.somaxconn# 应该输出 65535sysctlvm.swappiness# 应该输出 10sysctlvm.nr_hugepages# 应该输出 512--- 五、第四步:容器配置(Docker / Kubernetes)5.1Docker Compose 完整配置# docker-compose.ymlversion:"3.9"services: php-fpm:# 鲲鹏 ARM64 专用镜像(注意 arm64v8 标签)image: php:8.3-fpm platform: linux/arm64 container_name: app-php-fpm restart: unless-stopped# ========== 资源限制 ==========# 大白话:给容器划定边界,防止一个容器把整台机器吃掉deploy: resources: limits: cpus:"4.0"# 最多用 4 个 CPU 核memory: 2G# 最多用 2GB 内存reservations: cpus:"1.0"# 保证至少有 1 个核memory: 512M# 保证至少有 512MB 内存# ========== 系统参数(容器内)==========sysctls: net.core.somaxconn:65535net.ipv4.tcp_max_syn_backlog:65535# ========== 文件句柄限制 ==========ulimits: nofile: soft:65536hard:65536nproc: soft:65535hard:65535# ========== 挂载卷 ==========volumes: - /var/www/html:/var/www/html - ./config/php-fpm.conf:/usr/local/etc/php-fpm.d/www.conf:ro - ./config/php.ini:/usr/local/etc/php/php.ini:ro - php-socket:/run/php-fpm# 共享 Unix Socket- /dev/hugepages:/dev/hugepages# 大页内存(主机要先分配)environment: TZ: Asia/Shanghai nginx: image: nginx:alpine depends_on: - php-fpm volumes: - /var/www/html:/var/www/html - php-socket:/run/php-fpm# 和 PHP-FPM 共享同一个 socket- ./config/nginx.conf:/etc/nginx/conf.d/default.conf:ro ports: -"80:80"volumes: php-socket: driver:local5.2Nginx 配合 Unix Socket 的配置# config/nginx.confserver{listen80;root /var/www/html/public;index index.php;location ~\.php${# 用 Unix Socket,比 TCP 快很多fastcgi_pass unix:/run/php-fpm/www.sock;fastcgi_index index.php;fastcgi_param SCRIPT_FILENAME$realpath_root$fastcgi_script_name;include fastcgi_params;# 超时设置要和 PHP-FPM 的 request_terminate_timeout 一致fastcgi_read_timeout30;fastcgi_send_timeout30;fastcgi_connect_timeout5;# 缓冲区调大,减少磁盘写入fastcgi_buffer_size 128k;fastcgi_buffers8128k;}}5.3Kubernetes 初始化容器调内核参数# k8s-phpfpm.yamlapiVersion: apps/v1 kind: Deployment metadata: name: php-fpm spec: replicas:3selector: matchLabels: app: php-fpm template: metadata: labels: app: php-fpm spec:# ========== 只调度到 ARM64 节点 ==========nodeSelector: kubernetes.io/arch: arm64# ========== 初始化容器:设置内核参数 ==========# 大白话:Pod 启动前先跑这个小容器改系统参数initContainers: - name: sysctl-tuning image: busybox:latest securityContext: privileged:true# 需要特权才能改内核参数command: -sh--c-|sysctl-wnet.core.somaxconn=65535sysctl-wnet.ipv4.tcp_max_syn_backlog=65535sysctl-wvm.swappiness=10echo"内核参数调优完成"containers: - name: php-fpm image: php:8.3-fpm# ========== 资源限制 ==========resources: requests: memory:"512Mi"cpu:"500m"limits: memory:"2Gi"cpu:"4000m"# 4000m = 4 个 CPU 核# ========== 健康检查 ==========livenessProbe: tcpSocket: port:9000initialDelaySeconds:10periodSeconds:10readinessProbe: tcpSocket: port:9000initialDelaySeconds:5periodSeconds:5--- 六、第五步:验证调优效果6.1压测对比脚本#!/bin/bash# 文件名: benchmark.sh# 依赖: ab (Apache Bench) 或 wrkTARGET_URL="http://127.0.0.1/index.php"echo"===== 压测开始(100并发,共10000请求)====="ab-n10000-c100-k"$TARGET_URL"# 重点看这几行输出:# Requests per second: 这个越大越好(每秒处理多少请求)# Time per request: 这个越小越好(平均响应时间,ms)# Failed requests: 这个应该是 06.2实时监控脚本#!/bin/bash# 文件名: monitor_phpfpm.sh# 大白话:每秒刷新一次关键指标whiletrue;doclearecho"=====$(date'+%Y-%m-%d %H:%M:%S')====="# PHP-FPM 进程数TOTAL=$(pgrep-cphp-fpm2>/dev/null||echo0)echo"PHP-FPM 总进程数:$TOTAL"# 内存占用MEM=$(psaux|grepphp-fpm|grep-vgrep|\awk'{sum+=$6} END {printf "%.1f MB", sum/1024}')echo"PHP-FPM 内存总占用:$MEM"# 连接队列(有值说明在排队,可能需要加进程)echo""echo"=== 网络连接状态 ==="ss-s|grep-E"TCP|estab"# PHP-FPM 状态页echo""echo"=== PHP-FPM 状态 ==="curl-s"http://127.0.0.1/fpm-status?json"2>/dev/null|\python3-c" import sys, json d = json.load(sys.stdin) print(f'活跃进程: {d[\"active processes\"]}') print(f'空闲进程: {d[\"idle processes\"]}') print(f'等待队列: {d[\"listen queue\"]}') # 这个不为0就要加进程! print(f'达到上限次数: {d[\"max children reached\"]}') # 不为0就要加进程! "2>/dev/null||echo"状态页不可用,请检查 pm.status_path 配置"sleep1done6.3常见异常诊断表# 问题1:PHP-FPM 状态页显示 listen queue > 0# 原因:进程数不够用,请求在排队# 解决:增大 pm.max_childrensed-i's/pm.max_children = .*/pm.max_children = 200/'/usr/local/etc/php-fpm.d/www.confkill-USR2$(cat/var/run/php-fpm.pid)# 平滑重载配置# 问题2:max children reached 不为 0# 原因:同上,进程数触及上限# 解决:同上# 问题3:内存不断增长(内存泄漏)# 原因:PHP 扩展内存泄漏,进程没有定期回收# 解决:减小 pm.max_requests(比如 1000)# 检查:watch-n5'ps aux | grep php-fpm | grep -v grep | \ awk "{sum+=\$6; count++} END {print count\" 个进程, 平均 \"sum/count/1024\" MB\"}"'# 问题4:出现 "connection refused" 或 502 错误# 原因:可能是 socket 权限问题或 backlog 太小ls-la/run/php-fpm/www.sock# 检查权限sysctlnet.core.somaxconn# 检查队列长度# 问题5:ARM64 上 CPU 很高但吞吐量低# 原因:JIT 没开,或者在做大量正则php-r"var_dump(opcache_get_status()['jit']);"# 如果 enabled=false,在 php.ini 添加:# opcache.jit=tracing# opcache.jit_buffer_size=100M--- 七、完整调优清单(按优先级排序) 优先级1- 立竿见影(30分钟内完成):[]确认 JIT 是否开启:php-r"var_dump(opcache_get_status()['jit']);"[]调整 pm.max_children(用上面的公式重新算)[]将 listen 改为 Unix Socket[]设置 pm.max_requests=5000(防内存泄漏) 优先级2- 内核参数(需要重启生效):[]创建 /etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf[]sysctl-p应用参数[]分配大页内存:vm.nr_hugepages=512优先级3- 容器配置(需要重建容器):[]设置正确的 CPU/内存 limits[]配置 ulimits(nofile=65536)[]Kubernetes 加 initContainer 调内核参数 优先级4- 持续监控:[]开启状态页 pm.status_path=/fpm-status[]配置慢日志 request_slowlog_timeout=5s[]每日检查 max children reached 是否为0--- 总结:鲲鹏 ARM64 上 PHP-FPM 性能优化的核心三件事——开 JIT、调内核队列、算准进程数。按上面的流程走完,正常情况下吞吐量能提升50%~150%。