灯下哥谭 灯下哥谭
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

灯下哥谭

灯还亮着
首页
关于
  • Hermes Agent 平台
  • Claude Code
  • OpenClaw
  • GPU 推理节点运维
  • DeepSeek Harness
  • MySQL 运维知识地图
  • Elasticsearch 运维知识地图
  • Redis 运维知识地图
  • TiDB 体系
  • DBA 常用 SQL 与命令
  • Nginx 运维知识地图
  • Prometheus 监控
  • Docker
  • Systemd
  • Iptables
  • Firewalld
  • Sshd
  • MySQL8 运维 SOP 手册
  • MySQL 实战 45 讲(读书笔记)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 工作笔记

  • 容器与编排

  • Nginx

    • Nginx 运维知识地图:从配置基础到反向代理实战
    • Nginx 给内部服务加认证:Basic Auth / LDAP / OIDC 三档怎么选
    • 利用nginx+sftp实现一个可供用户下载的服务
    • nginx配置文件及模块
    • Nginx 端口转发与复用:四层代理与 SO_REUSEPORT 实战
    • OpenResty 版本升级与模块管理:从 1.13 到 1.19 的实践
    • NGINX基于cookie针对同一域名进行分流转发
    • nginx利用内置模块配置限速限流
    • 利用NGINX内置模块mirror进行流量复制等操作
    • Nginx 常用配置速查
    • Nginx 日志切割两种方案对比:外部脚本 vs 配置文件原生切割
    • nginx配置微信小程序校验及其他
    • 由Nginx集中代理分散的PHP集群的实践
      • 1,以往
      • 2,现在
      • 3,另外
      • 3. 架构演进历程
        • 3.1 传统模式:每个 PHP 配备独立 Nginx
        • 3.2 改进模式:集中式 Nginx 代理
      • 4. 集中代理的配置要点
        • 4.1 PHP-FPM 配置
        • 4.2 Nginx 路径分发
        • 4.3 超时与缓冲配置
      • 5. 坑与边界
      • 6. 监控与告警
      • 7. 性能优化
        • 7.1 连接池复用
        • 7.2 静态资源分离
        • 7.3 Gzip 压缩
      • 8. 参考
      • 9. 扩展:多机房部署
      • 10. 容器化改造
      • 11. 总结
    • http状态码详解
    • 排查NGINX的open_file_cache导致发布后访问404的问题
    • Nginx Proxy Manager 使用笔记
  • 监控

  • 网络安全

  • 其他

  • Linux笔记
  • Nginx
灯下哥谭
2021-01-24
目录

由Nginx集中代理分散的PHP集群的实践

# 1,以往

以往的 PHP 项目处理方案大多沿用了经典的 lnmp,然后几乎有 PHP 的主机都会配套一个 Nginx,接着有多少个 PHP,就会有多少个 Nginx,然后 lb 后边就得挂上这所有的 Nginx。

版本说明

本文写于 2021-01。
未在更高版本上重新验证,请以对应版本的官方文档为准;具体 PHP-FPM 版本细节请结合实际环境核对。

但事实上还可以用如下办法来将 PHP 分散管理。

# 2,现在

Nginx 主配置像配置 Java 应用一般反代转发给 PHP 服务:

upstream php-fpm {
   server 127.0.0.1:9006 weight=6 max_fails=300 fail_timeout=5s;
   server 127.0.0.1:9007 weight=6 max_fails=300 fail_timeout=5s;
}
server{
    listen 80;
    server_name 192.0.2.11;
    proxy_connect_timeout 300;
    proxy_read_timeout 300;
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    location / {
        root /data/www/liql;
        index index.php index.html index.htm;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        try_files $uri $uri/ /index.php?$query_string;
    }
   location ~ \.php($|/) {
        root /data/www/liql;
        index index.php index.html index.htm;
        fastcgi_pass  php-fpm;
        fastcgi_index  index.php;
        fastcgi_split_path_info ^(.+\.php)(.*)$;
        include  fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

这样只需要将代码放入到 PHP 容器内即可:

$ cat liql/index.php
<?php
    phpinfo();
?>
$ cat Dockerfile
FROM reg.eryajf.net/multienv/wpt_phpfpm:7.2.34
COPY . /data/www/
1
2
3
4
5
6
7

然后重新打镜像:

docker build -t aaa .
1

启动两个容器进行访问,可以发现权重很均衡的取到了两个 PHP 容器之内:

{"remote_addr": "192.0.2.12","@timestamp": "2021-01-24T17:14:27+08:00","upstream_addr": "127.0.0.1:9006","request_uri": "/","verb": "GET","httpversion": "HTTP/1.1","response": "200", "body_bytes_sent": "28892", "referrer": "", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "http_x_forwarded_for": "", "server_name": "192.0.2.11","request_time": "0.003","upstream_response_time": "0.003","realpath_root": "","cookie": "grafana_session=02bc6ce5e1489781ff5ed00ef9fb61ca","request_body": "","nginx_version": "1.13.6","scheme": "http"}
{"remote_addr": "192.0.2.12","@timestamp": "2021-01-24T17:14:28+08:00","upstream_addr": "127.0.0.1:9007","request_uri": "/","verb": "GET","httpversion": "HTTP/1.1","response": "200", "body_bytes_sent": "28895", "referrer": "", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "http_x_forwarded_for": "", "server_name": "192.0.2.11","request_time": "0.004","upstream_response_time": "0.004","realpath_root": "","cookie": "grafana_session=02bc6ce5e1489781ff5ed00ef9fb61ca","request_body": "","nginx_version": "1.13.6","scheme": "http"}
1
2

img

# 3,另外

  • 在测试过程中发现,如果代理的两个容器有一个没有起来,竟丝毫不影响服务的访问,转发的方式也非常有意思,从日志中观察:

    {"remote_addr": "192.0.2.12","@timestamp": "2021-01-24T17:36:27+08:00","upstream_addr": "127.0.0.1:9006","request_uri": "/","verb": "GET","httpversion": "HTTP/1.1","response": "200", "body_bytes_sent": "28898", "referrer": "", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "http_x_forwarded_for": "", "server_name": "192.0.2.11","request_time": "0.004","upstream_response_time": "0.004","realpath_root": "/data/www/liql","cookie": "grafana_session=02bc6ce5e1489781ff5ed00ef9fb61ca","request_body": "","nginx_version": "1.13.6","scheme": "http"}
    {"remote_addr": "192.0.2.12","@timestamp": "2021-01-24T17:36:29+08:00","upstream_addr": "127.0.0.1:9007, 127.0.0.1:9006","request_uri": "/","verb": "GET","httpversion": "HTTP/1.1","response": "200", "body_bytes_sent": "28899", "referrer": "", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "http_x_forwarded_for": "", "server_name": "192.0.2.11","request_time": "0.004","upstream_response_time": "0.000, 0.004","realpath_root": "/data/www/liql","cookie": "grafana_session=02bc6ce5e1489781ff5ed00ef9fb61ca","request_body": "","nginx_version": "1.13.6","scheme": "http"}
    
    1
    2

    注意日志中的 upstream_addr字段,第一条请求被 9006 端口正常解析,第二条的解析竟然两个端口都有,也就是说,9007 端口不存在或者无法正常解析的时候,此请求会被正常的 9006 给解析掉,看错误日志里记录下了 9007 的错误:

    2021/01/24 17:36:29 [error] 24165#24165: *394 connect() failed (111: Connection refused) while connecting to upstream, client: 192.0.2.12, server: 192.0.2.11, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9007", host: "192.0.2.11"
    
    1
  • 注意代码的存放位置

    • 这里有两点需要注意,一个是 root指令不要放在 location 外边,这里是纯 PHP 解析,如果放到外边,请求的时候总会报一个本地找不到的错误:
    2021/01/24 17:21:51 [crit] 23563#23563: *347 realpath() "/data/www/liql" failed (2: No such file or directory) while logging request, client: 192.0.2.12, server: 192.0.2.11, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9006", host: "192.0.2.11"
    
    1

    这个时候把 root指令放到 location 内部就不会报这个错误了。

    • 另外注意,代码是存放在 PHP 所在主机上的,而非 Nginx 所在主机,因为.php结尾的请求都是通过 fast_cgi 解析的,而非 Nginx,这里千万不要用传统的 http 代理来理解了。

    所以这个地方的处理流程应该是这样的:

    www.example.com
            |
            |
          Nginx
            |
            |
    路由到www.example.com/index.php
            |
            |
    加载nginx的fast-cgi模块
            |
            |
    fast-cgi监听127.0.0.1:9006地址
            |
            |
    www.example.com/index.php请求到达127.0.0.1:9000
            |
            |
    php-fpm 监听127.0.0.1:9000
            |
            |
    php-fpm 接收到请求,启用worker进程处理请求
            |
            |
    php-fpm 在给定的root地址下解析PHP源码,将结果返回给nginx
            |
            |
    nginx将结果通过http返回给浏览器
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    这样,就能很清晰地解析整个处理链路了。

    本文实验探索的主要意义在于,在这种基础之下,我们就可以摒弃掉以往处理 PHP 业务场景的固化思维,从而将 Nginx 这层网关像处理 Java 应用那样一般,集中对 PHP 应用进行处理了,以实现集中化管理,更加便于横向扩展与运维。


# 3. 架构演进历程

# 3.1 传统模式:每个 PHP 配备独立 Nginx

早期的 PHP 部署模式通常是 LNMP(Linux + Nginx + MySQL + PHP),每个 PHP-FPM 实例都会配套一个 Nginx。这种模式的优势是架构简单,缺点是:

  1. 资源浪费:每个 Nginx 都要维护独立的 worker 进程和连接池
  2. 配置分散:20 个 PHP 服务就有 20 份 Nginx 配置,难以统一管理
  3. 负载均衡复杂:LB 层需要对接所有这些 Nginx,增加了复杂度

# 3.2 改进模式:集中式 Nginx 代理

改进后的模式使用单一 Nginx 集中代理所有 PHP 服务:

# 唯一的 Nginx 配置
upstream php_pool_1 {
    server 127.0.0.1:9006 weight=6 max_fails=300 fail_timeout=5s;
    server 127.0.0.1:9007 weight=6 max_fails=300 fail_timeout=5s;
}

upstream php_pool_2 {
    server 127.0.0.1:9008;
    server 127.0.0.1:9009;
}

server {
    listen 80;
    server_name phpapp.example.com;
    
    # 根据路径转发到不同 PHP 池
    location /app1/ {
        fastcgi_pass php_pool_1;
        include fastcgi_params;
    }
    
    location /app2/ {
        fastcgi_pass php_pool_2;
        include fastcgi_params;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

优势:

  • 统一入口,简化运维
  • 减少资源占用
  • 集中管理配置
  • 便于做统一的访问控制

# 4. 集中代理的配置要点

# 4.1 PHP-FPM 配置

每个 PHP-FPM 实例使用不同的端口:

; pool 1
[www]
listen = 127.0.0.1:9006
pm = static
pm.max_children = 50

; pool 2  
[www]
listen = 127.0.0.1:9008
pm = static
pm.max_children = 50
1
2
3
4
5
6
7
8
9
10
11

# 4.2 Nginx 路径分发

根据 URI 路径或域名转发到不同 PHP 池:

server {
    listen 80;
    server_name app1.example.com;
    
    location / {
        fastcgi_pass 127.0.0.1:9006;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

server {
    listen 80;
    server_name app2.example.com;
    
    location / {
        fastcgi_pass 127.0.0.1:9008;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4.3 超时与缓冲配置

大文件上传或慢请求需要合理配置超时时间:

location / {
    fastcgi_pass 127.0.0.1:9006;
    
    # 超时配置
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    
    # 缓冲配置
    fastcgi_buffer_size 128k;
    fastcgi_buffers 4 256k;
    fastcgi_busy_buffers_size 256k;
    
    include fastcgi_params;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5. 坑与边界

常见问题一:PHP-FPM 进程耗尽

如果某个 PHP 应用有内存泄漏,长时间运行后 PHP-FPM 进程可能耗尽。解决方案是配置 pm.max_requests 强制回收进程:

pm.max_requests = 500
1

常见问题二:Session 共享问题

不同 PHP 池需要共享 Session,否则用户可能丢失登录状态。解决方案:

  1. 使用 Redis/Memcached 存储 Session
  2. 在 PHP 配置中指定共享存储:
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"
1
2

常见问题三:文件上传路径

文件上传通常存储在共享存储(如 NFS、Ceph),确保所有 PHP-FPM 节点都能访问相同的存储路径。

常见问题四:定时任务一致性

如果是多节点部署,定时任务(如 crontab)需要在所有节点运行还是只运行一次,需要根据业务逻辑设计。

# 6. 监控与告警

集中代理模式下,监控要点包括:

  1. 后端健康检查:监控各 PHP-FPM 端口是否可达
  2. 响应时间:关注 fastcgi_response_time 指标
  3. 错误率:监控 502、504 错误
  4. 连接数:监控与各 PHP-FPM 的连接数

建议配置 Upstream 状态页:

location /nginx_status {
    stub_status on;
    allow 127.0.0.1;
    deny all;
}

location /php_fpm_status {
    fastcgi_pass 127.0.0.1:9006;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
1
2
3
4
5
6
7
8
9
10
11

# 7. 性能优化

# 7.1 连接池复用

PHP-FPM 的连接是短连接,每次请求都会创建新连接。可以优化为持久连接:

fastcgi_keep_conn on;
1

# 7.2 静态资源分离

图片、CSS、JS 等静态资源不应经过 PHP-FPM,直接由 Nginx 提供:

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    root /data/www/static;
    expires 30d;
    add_header Cache-Control "public, immutable";
}

location / {
    fastcgi_pass 127.0.0.1:9006;
    # ...
}
1
2
3
4
5
6
7
8
9
10

# 7.3 Gzip 压缩

对响应内容进行压缩,减少传输量:

gzip on;
gzip_types text/plain application/json application/javascript text/css;
gzip_min_length 1024;
1
2
3

# 8. 参考

  • Nginx fastcgi 模块文档 (opens new window)
  • PHP-FPM 配置文档 (opens new window)

# 9. 扩展:多机房部署

如果服务部署在多个机房,需要考虑流量调度:

方案一:DNS 轮询

不同地域的用户解析到不同机房的 Nginx 入口。

方案二:Anycast

使用 Anycast IP,整个网络共享同一个 IP 地址,就近接入。

方案三:Globe Load Balancer

在 Nginx 前面加一层全局负载均衡(如 Cloudflare、阿里云 GSLB),根据地域选择后端。

多机房部署的注意事项:

  1. 数据一致性:确保各机房数据同步(如 MySQL 主从、Redis 集群)
  2. 延迟考虑:跨机房调用延迟较高,架构上应尽量减少跨机房依赖
  3. 成本考量:跨机房带宽成本较高,需要评估流量模式

# 10. 容器化改造

在 Kubernetes 环境中,PHP 应用的部署方式会有变化:

传统部署

Nginx → PHP-FPM (Host 网络)
1

容器部署

Nginx (Host/Cluster) → PHP-FPM (Pod 网络)
1

容器环境中的关键配置:

# PHP-FPM Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-fpm
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: php-fpm
        ports:
        - containerPort: 9000
---
# Nginx ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  default.conf: |
    upstream php_backend {
        server php-fpm:9000;
    }
    server {
        location / {
            fastcgi_pass php_backend;
            include fastcgi_params;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

容器化优势:

  • 自动扩缩容
  • 故障自愈
  • 环境一致性
  • 资源隔离

# 11. 总结

Nginx 集中代理 PHP 集群的架构,适合以下场景:

  1. 多业务统一入口:统一管理、安全策略
  2. 简化运维:减少组件数量,统一配置
  3. 降低成本:复用 Nginx 资源,减少复杂度

关键配置点:

  1. PHP-FPM 多实例端口区分
  2. Nginx upstream 负载策略
  3. 超时与缓冲参数调优
  4. Session 共享机制
  5. 监控告警体系

架构选型建议:

  • 10 个以下 PHP 服务:可用集中代理
  • 10-50 个 PHP 服务:考虑分层(前端 Nginx + 后端 Nginx)
  • 50+ 个 PHP 服务:考虑微服务化或 API 网关
#架构设计#Nginx
上次更新: 9/11/2026

← nginx配置微信小程序校验及其他 http状态码详解→

最近更新
01
DeepSeek Harness 实战 06|学习笔记:插件、工具、技能不在同一个维度上 原创
09-11
02
DeepSeek Harness 实战 05|让两个编码 Agent 共用一份长期记忆 原创
09-09
03
DeepSeek Harness 实战 04|学习笔记:从「已知限制」里读出三处设计张力 原创
09-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式