由Nginx集中代理分散的PHP集群的实践
文章发布较早,内容可能过时,阅读注意甄别。
# 1,以往
以往的 PHP 项目处理方案大多沿用了经典的 lnmp,然后几乎有 PHP 的主机都会配套一个 Nginx,接着有多少个 PHP,就会有多少个 Nginx,然后 lb 后边就得挂上这所有的 Nginx。
但事实上还可以用如下办法来将 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 10.3.7.7;
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
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
2
3
4
5
6
7
然后重新打镜像:
docker build -t aaa .
1
启动两个容器进行访问,可以发现权重很均衡的取到了两个 PHP 容器之内:
{"remote_addr": "10.105.201.43","@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": "10.3.7.7","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": "10.105.201.43","@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": "10.3.7.7","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
2
# 3,另外
在测试过程中发现,如果代理的两个容器有一个没有起来,竟丝毫不影响服务的访问,转发的方式也非常有意思,从日志中观察:
{"remote_addr": "10.105.201.43","@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": "10.3.7.7","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": "10.105.201.43","@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": "10.3.7.7","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: 10.105.201.43, server: 10.3.7.7, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9007", host: "10.3.7.7"
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: 10.105.201.43, server: 10.3.7.7, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9006", host: "10.3.7.7"
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 应用进行处理了,以实现集中化管理,更加便于横向扩展与运维。
- 这里有两点需要注意,一个是
上次更新: 2024/11/28, 21:21:13