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%。

更多推荐