php信创=PHP-FPM容器在鲲鹏ARM64架构性能异常排查与信创内核参数调优
·
PHP-FPM 容器在鲲鹏 ARM64 性能异常排查与信创内核调优
--- 一、为什么鲲鹏 ARM64 会有性能问题?
鲲鹏处理器用的是 ARM64 架构,和 x86(Intel/AMD)不一样。容器里跑 PHP-FPM 时,最常见的坑:
┌───────────────────────────┬───────────────────────────────────────────────────┐
│ 问题根源 │ 大白话 │
├───────────────────────────┼───────────────────────────────────────────────────┤
│ JIT 没开 │ PHP 8.1+ 有即时编译,ARM64 上收益更大,但默认关着 │
├───────────────────────────┼───────────────────────────────────────────────────┤
│ 内存页太小 │ 默认 4KB 页,ARM64 支持 64KB 大页,没开就白费 │
├───────────────────────────┼───────────────────────────────────────────────────┤
│ 内核网络参数是默认值 │ 高并发时连接队列满了直接丢请求 │
├───────────────────────────┼───────────────────────────────────────────────────┤
│ 容器 CPU/内存限制设错 │ 限制太死导致进程被 OOM Kill │
├───────────────────────────┼───────────────────────────────────────────────────┤
│ 进程数算法没针对 ARM64 调 │ ARM64 内存访问模式和 x86 不同 │
└───────────────────────────┴───────────────────────────────────────────────────┘
---
二、第一步:排查——先看现在哪里出了问题
2.1 快速诊断脚本(直接跑)
#!/bin/bash
# 文件名: diagnose_phpfpm.sh
# 用法: bash diagnose_phpfpm.sh
echo "===== PHP-FPM 进程状态 ====="
ps aux | grep php-fpm | grep -v grep
echo ""
echo "===== PHP-FPM 内存总占用 ====="
ps aux | grep php-fpm | grep -v grep | 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 "===== 当前内核关键参数 ====="
sysctl net.core.somaxconn # 连接队列长度(默认128,太小)
sysctl net.ipv4.tcp_max_syn_backlog
sysctl vm.swappiness # 换页倾向(默认60,太高)
sysctl vm.nr_hugepages # 大页数量(默认0)
echo ""
echo "===== CPU 架构确认 ====="
uname -m # 应该输出: aarch64
lscpu | grep -E "Architecture|CPU\(s\)|Thread"
echo ""
echo "===== PHP 版本和 JIT 状态 ====="
php -v
php -r "echo opcache_get_status()['jit']['enabled'] ? 'JIT: 开启' : 'JIT: 关闭'; echo PHP_EOL;"
2.2 查看 PHP-FPM 状态页(需要先开启)
# 查询状态(返回 JSON 格式)
curl http://127.0.0.1/fpm-status?json | python3 -m json.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 -o rss -p $(pgrep php-fpm | head -1)
# 假设输出 61440(即 60MB)
# 假设服务器 16GB,给 PHP-FPM 分配 8GB:
# pm.max_children = 8192MB ÷ 60MB ≈ 136,取 128
3.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 = 65535
listen.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] = 1
php_admin_value[opcache.memory_consumption] = 256 ; 缓存内存,单位MB
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 10000 ; 最多缓存多少个文件
php_admin_value[opcache.validate_timestamps] = 0 ; 生产环境关掉,省去文件检查
; ========== JIT 编译(PHP 8.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 = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# TCP 读写缓冲区大小(三个值:最小/默认/最大,单位字节)
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
# 可用本地端口范围(PHP-FPM 建很多连接,端口不够会报错)
net.ipv4.ip_local_port_range = 1024 65535
# 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 = 2097152
fs.nr_open = 2097152
# =============================================
# 进程调度(鲲鹏 ARM64 NUMA 优化)
# =============================================
# 进程迁移代价(纳秒),防止进程在 CPU 间频繁跳动
kernel.sched_migration_cost_ns = 5000000
# 调度延迟(一个任务最多等多久才被调度)
kernel.sched_latency_ns = 24000000
4.2 立即生效
# 应用所有参数
sysctl -p /etc/sysctl.d/99-phpfpm-kunpeng-arm64.conf
# 验证是否生效
sysctl net.core.somaxconn # 应该输出 65535
sysctl vm.swappiness # 应该输出 10
sysctl vm.nr_hugepages # 应该输出 512
---
五、第四步:容器配置(Docker / Kubernetes)
5.1 Docker Compose 完整配置
# docker-compose.yml
version: "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: 65535
net.ipv4.tcp_max_syn_backlog: 65535
# ========== 文件句柄限制 ==========
ulimits:
nofile:
soft: 65536
hard: 65536
nproc:
soft: 65535
hard: 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: local
5.2 Nginx 配合 Unix Socket 的配置
# config/nginx.conf
server {
listen 80;
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_timeout 30;
fastcgi_send_timeout 30;
fastcgi_connect_timeout 5;
# 缓冲区调大,减少磁盘写入
fastcgi_buffer_size 128k;
fastcgi_buffers 8 128k;
}
}
5.3 Kubernetes 初始化容器调内核参数
# k8s-phpfpm.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-fpm
spec:
replicas: 3
selector:
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 -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
sysctl -w vm.swappiness=10
echo "内核参数调优完成"
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: 9000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 9000
initialDelaySeconds: 5
periodSeconds: 5
---
六、第五步:验证调优效果
6.1 压测对比脚本
#!/bin/bash
# 文件名: benchmark.sh
# 依赖: ab (Apache Bench) 或 wrk
TARGET_URL="http://127.0.0.1/index.php"
echo "===== 压测开始(100并发,共10000请求)====="
ab -n 10000 -c 100 -k "$TARGET_URL"
# 重点看这几行输出:
# Requests per second: 这个越大越好(每秒处理多少请求)
# Time per request: 这个越小越好(平均响应时间,ms)
# Failed requests: 这个应该是 0
6.2 实时监控脚本
#!/bin/bash
# 文件名: monitor_phpfpm.sh
# 大白话:每秒刷新一次关键指标
while true; do
clear
echo "===== $(date '+%Y-%m-%d %H:%M:%S') ====="
# PHP-FPM 进程数
TOTAL=$(pgrep -c php-fpm 2>/dev/null || echo 0)
echo "PHP-FPM 总进程数: $TOTAL"
# 内存占用
MEM=$(ps aux | grep php-fpm | grep -v grep | \
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 配置"
sleep 1
done
6.3 常见异常诊断表
# 问题1:PHP-FPM 状态页显示 listen queue > 0
# 原因:进程数不够用,请求在排队
# 解决:增大 pm.max_children
sed -i 's/pm.max_children = .*/pm.max_children = 200/' /usr/local/etc/php-fpm.d/www.conf
kill -USR2 $(cat /var/run/php-fpm.pid) # 平滑重载配置
# 问题2:max children reached 不为 0
# 原因:同上,进程数触及上限
# 解决:同上
# 问题3:内存不断增长(内存泄漏)
# 原因:PHP 扩展内存泄漏,进程没有定期回收
# 解决:减小 pm.max_requests(比如 1000)
# 检查:
watch -n 5 '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 # 检查权限
sysctl net.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%。
更多推荐

所有评论(0)