PHP-FPM 容器在鲲鹏 ARM64 性能异常排查与信创内核调优 --- 一、为什么鲲鹏 ARM64 会有性能问题 鲲鹏处理器用的是 ARM64 架构和 x86Intel/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-vgrepechoecho PHP-FPM 内存总占用 psaux|grepphp-fpm|grep-vgrep|awk{sum$6} END { printf 总内存: %.1f MB (%.1f GB)\n, sum/1024, sum/1024/1024 }echoecho 系统连接数 ss-s# 看 TCP 连接统计echoecho 文件句柄使用情况 cat/proc/sys/fs/file-nr# 输出: 已用 空闲 上限echoecho 当前内核关键参数 sysctlnet.core.somaxconn# 连接队列长度默认128太小sysctlnet.ipv4.tcp_max_syn_backlogsysctlvm.swappiness# 换页倾向默认60太高sysctlvm.nr_hugepages# 大页数量默认0echoecho CPU 架构确认 uname-m# 应该输出: aarch64lscpu|grep-EArchitecture|CPU\(s\)|Threadechoecho PHP 版本和 JIT 状态 php-vphp-recho 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慢请求日志分析# 查最慢的请求按脚本路径分组统计grepscript_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)# 先查单个进程占多少内存单位 KBps--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按需启动低流量省内存用这个 pmdynamic;进程数量8核16GB 服务器示例;最多开多少个工人 pm.max_children128;启动时先开多少个工人CPU核数 ×4 pm.start_servers32;至少保持多少个空闲工人CPU核数 ×2 pm.min_spare_servers16;最多保持多少个空闲工人CPU核数 ×4 pm.max_spare_servers32;每个工人处理多少请求后重启防止内存泄漏;ARM64 上建议设小一点2500~5000 pm.max_requests5000;工人空闲多久后退出dynamic/ondemand 模式 pm.process_idle_timeout10s;通信方式;Unix Socket 比 TCP 快10~30%同机部署 NginxPHP-FPM 必须用这个 listen/run/php-fpm/www.sock;等待队列长度必须和内核 net.core.somaxconn 一致 listen.backlog65535listen.ownerwww-data listen.groupwww-data listen.mode0660;超时保护;单个请求最多执行多少秒超时直接 kill防止慢请求拖死全服务 request_terminate_timeout30s;超过多少秒记入慢日志 request_slowlog_timeout5s 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;OPcachePHP 字节码缓存必开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.1ARM64 收益更大;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.responsepong --- 四、第三步信创内核参数调优 大白话就是告诉 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.somaxconn65535net.ipv4.tcp_max_syn_backlog65535# TCP 读写缓冲区大小三个值最小/默认/最大单位字节net.ipv4.tcp_rmem40968738067108864net.ipv4.tcp_wmem40966553667108864net.core.rmem_max134217728net.core.wmem_max134217728# 可用本地端口范围PHP-FPM 建很多连接端口不够会报错net.ipv4.ip_local_port_range102465535# TIME_WAIT 状态的连接可以被新连接复用减少端口占用net.ipv4.tcp_tw_reuse1# TCP KeepAlive检测死连接单位秒net.ipv4.tcp_keepalive_time600;空闲多久开始发探针 net.ipv4.tcp_keepalive_probes3;发几次没响应就断开 net.ipv4.tcp_keepalive_intvl15;探针间隔# TCP Fast Open减少握手延迟内核 3.13net.ipv4.tcp_fastopen3# # 内存管理# # 换页积极程度0~100默认60# 大白话越小越不愿意把内存换到磁盘10 表示非常不愿意# 服务器内存够用就设低防止 PHP 进程被换出去vm.swappiness10# 脏页比例控制脏页 已修改但还没写入磁盘的内存页vm.dirty_ratio15;内存用这么多脏页后强制写盘 vm.dirty_background_ratio5;后台开始写盘的阈值# VFS 缓存回收压力默认100降低保留更多文件系统缓存vm.vfs_cache_pressure50# 保留最小空闲内存单位 KB防止内存完全耗尽vm.min_free_kbytes262144# NUMA 架构下关闭本地内存优先回收鲲鹏多路服务器必须关# 大白话不要只用本地 NUMA 节点的内存让内存可以跨节点vm.zone_reclaim_mode0# # 大页内存Huge Pages—— ARM64 专属优化# # 分配多少个 2MB 大页对比默认 4KB 小页减少 TLB 缺失# 大白话把小纸片换成大纸每次查地址表效率更高# 512个大页 1GB可根据内存大小调整vm.nr_hugepages512# # 文件系统# # 系统最大文件句柄数PHP-FPM 进程多的时候容易不够用fs.file-max2097152fs.nr_open2097152# # 进程调度鲲鹏 ARM64 NUMA 优化# # 进程迁移代价纳秒防止进程在 CPU 间频繁跳动kernel.sched_migration_cost_ns5000000# 调度延迟一个任务最多等多久才被调度kernel.sched_latency_ns240000004.2立即生效# 应用所有参数sysctl-p/etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf# 验证是否生效sysctlnet.core.somaxconn# 应该输出 65535sysctlvm.swappiness# 应该输出 10sysctlvm.nr_hugepages# 应该输出 512--- 五、第四步容器配置Docker / Kubernetes5.1Docker Compose 完整配置# docker-compose.ymlversion:3.9services: 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:80volumes: 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.somaxconn65535sysctl-wnet.ipv4.tcp_max_syn_backlog65535sysctl-wvm.swappiness10echo内核参数调优完成containers: - name: php-fpm image: php:8.3-fpm# 资源限制 resources: requests: memory:512Micpu:500mlimits: memory:2Gicpu: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_URLhttp://127.0.0.1/index.phpecho 压测开始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)echoPHP-FPM 总进程数:$TOTAL# 内存占用MEM$(psaux|grepphp-fpm|grep-vgrep|\awk{sum$6} END {printf %.1f MB, sum/1024})echoPHP-FPM 内存总占用:$MEM# 连接队列有值说明在排队可能需要加进程echoecho 网络连接状态 ss-s|grep-ETCP|estab# PHP-FPM 状态页echoecho PHP-FPM 状态 curl-shttp://127.0.0.1/fpm-status?json2/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常见异常诊断表# 问题1PHP-FPM 状态页显示 listen queue 0# 原因进程数不够用请求在排队# 解决增大 pm.max_childrensed-is/pm.max_children .*/pm.max_children 200//usr/local/etc/php-fpm.d/www.confkill-USR2$(cat/var/run/php-fpm.pid)# 平滑重载配置# 问题2max children reached 不为 0# 原因同上进程数触及上限# 解决同上# 问题3内存不断增长内存泄漏# 原因PHP 扩展内存泄漏进程没有定期回收# 解决减小 pm.max_requests比如 1000# 检查watch-n5ps 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# 检查队列长度# 问题5ARM64 上 CPU 很高但吞吐量低# 原因JIT 没开或者在做大量正则php-rvar_dump(opcache_get_status()[jit]);# 如果 enabledfalse在 php.ini 添加:# opcache.jittracing# opcache.jit_buffer_size100M--- 七、完整调优清单按优先级排序 优先级1- 立竿见影30分钟内完成:[]确认 JIT 是否开启php-rvar_dump(opcache_get_status()[jit]);[]调整 pm.max_children用上面的公式重新算[]将 listen 改为 Unix Socket[]设置 pm.max_requests5000防内存泄漏 优先级2- 内核参数需要重启生效:[]创建 /etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf[]sysctl-p应用参数[]分配大页内存vm.nr_hugepages512优先级3- 容器配置需要重建容器:[]设置正确的 CPU/内存 limits[]配置 ulimitsnofile65536[]Kubernetes 加 initContainer 调内核参数 优先级4- 持续监控:[]开启状态页 pm.status_path/fpm-status[]配置慢日志 request_slowlog_timeout5s[]每日检查 max children reached 是否为0--- 总结鲲鹏 ARM64 上 PHP-FPM 性能优化的核心三件事——开 JIT、调内核队列、算准进程数。按上面的流程走完正常情况下吞吐量能提升50%~150%。