PHP 部署

php7 之后的 php 服务是我见过最便捷的部署方式, 基本上用自带的源安装就能处理完所有步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 直接首先安装应用和 fpm 解析程序, 同时建议安装的组件
sudo apt install php php-fpm
sudo apt install php-intl php-mysql php-redis php-curl php-mbstring php-dom php-gd php-zip

# 现在很多发行版默认安装 php 会自动帮你安装 apache2 这个 Web 服务
# 这个服务会和 Nginx 之类产生占用冲突, 所以建议关闭掉
# 如果没有这些服务单元就不用管
sudo systemctl stop apache2.service # 关闭服务
sudo systemctl diable apache2.service # 禁止开机自启动

# 启动解析服务, 这里后续发行版都带 php{版本号}-fpm.service 启动
# 具体需要看默认源安装的版本号, 我这边默认安装的 php8.2 版本
sudo systemctl start php8.2-fpm.service # 启动解析服务
sudo systemctl enable php8.2-fpm.service # 启动开机服务

# 这里需要创建我一般采用 Nginx 托管, 所以这里建议创建个统一配置
sudo vim /etc/nginx/php-fpm.conf

/etc/nginx/php-fpm.conf 配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# 这里默认发行版都是采用 unixdomain 方式监听
# 默认发行版的 fpm 配置目录在 /etc/php/8.2/fpm, 具体可以输出命令:
# cat /etc/php/8.2/fpm/pool.d/www.conf |grep "^listen ="
# -------------------------------------------------------------
# 还有需要注意的是系统用户归属, 有的发行版 php-fpm 没有运行在 www-data(nginx 网络用户组)
# 这样会导致 php 生成的一些缓存没有访问权限, 具体输入命令确认是否设定在同个用户分组:
# cat /etc/php/8.2/fpm/pool.d/www.conf |grep -e "^user =" -e "^group =" -e "^listen.owner =" -e "^listen.group ="
# 这几个确认分配和 nginx 同个组
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}

ngnix 引用也是很简单直接加载即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
# 其他略

# 默认页面需要追加 index.php
index index.php index.html index.htm;

# 首页默认跳转 index.php
location / {
try_files $uri $uri/ /index.php$is_args$args;
}

# 启用PHP配置, 没有路径默认是 /etc/nginx
include php-fpm.conf;

# 异常页面也同时映射交给 php 处理
error_page 404 /index.php;

# 禁止访问隐藏文件,如 .htaccess
location ~ /\. {
deny all;
}
}

这样就很轻松搭建出 php 环境配置, 后续主要问题的就是优化处理配置

优化说明

默认首先主要容易出问题的就是 fpm 配置, PHP-FPM 提供 3 种进程管理模式, 根据服务器负载选择:

模式 适用场景 特点
static 高并发、资源充足的服务器 进程数固定,无动态调度开销
dynamic 负载波动较大的服务器 进程数动态调整(按需创建)
ondemand 低负载、轻量应用 有请求时才创建进程,节省内存

具体通过命令可以看到目前适用的管理模式:

1
2
# 查看默认的管理模式, 一般默认是 dynamic
cat /etc/php/8.2/fpm/pool.d/www.conf |grep "^pm ="

默认 dynamic 保持不变即可, 主要问题就是怎么去分配数值:

1
2
3
4
5
6
7
8
# 这里求出目前的单个进程占用, 比如我这里输出 '单个PHP-FPM进程平均内存:40333.5 KB', 40333.5 KB 换算过来就是 40M 大小
# 如果服务器是台 4G(4096M) 内存的服务器, 需要预留扣除 10%~20% 内存作为让渡给系统占用(这里预留20%), 那么计算得出:
# 可用内存 = 总内存 × (1 - 预留比例) = 4096M × 80% = 3276.8M ≈ 3276M, 这就是 FPM 可用的内存大小
ps aux | grep php-fpm | grep -v grep | awk '{sum+=$6} END {print "单个PHP-FPM进程平均内存:" sum/NR " KB"}'

# 除了内存也需要去定内存核心数量和线程数量有多少
# 有些主机 CPU 采用 2核4线程的 CPU, 所以需要确认线程数量, 不过默认服务器都是单核心对应单线程, 所以不用管其他默认等于核心数即可
grep -c ^processor /proc/cpuinfo | awk '{print $1}'

那么目前这里可以调整的 /etc/php/8.2/fpm/pool.d/www.conf 配置:

  • pm.max_children: 最大子进程数
  • pm.start_servers: 启动时的进程数
  • pm.min_spare_servers: 最小空闲进程数
  • pm.max_spare_servers: 最大空闲进程数

按照之前上面提出的 FPM=40, Memory=3276M, CPU=2 来计算(服务器配置为 2U4G):

  • pm.max_children = 3276(内存) ÷ 40(单应用内存) = 81.9 → 向下取整81.9(预留少量冗余) = 80
  • pm.start_servers = 2 (CPU核心) = 2
  • pm.min_spare_servers = 2(CPU核心) ÷ 2(CPU核心) → 需小于 start_servers = 1(这个值可以微调, 主要牵涉启动 fpm 默认初始化多少进程)
  • pm.max_spare_servers = 2(CPU核心) * 2(CPU核心) → 需小于 max_children = 4(这个值可以微调, 如果服务器还有其他计算密集任务需降低)

之后就是 Linux 系统内核调优, 需要调整 /etc/security/limits.conf 文件配置, 不配置的话可能高并发会出现 504 异常:

1
2
3
# 默认文件描述符(fd)限制为 1024, 需要给指定用户提升
www-data soft nofile 65535
www-data hard nofile 65535

其他基本上就是 /etc/php/8.2/fpm/php.ini 里面设置, 这里摘取几个比较有用的:

1
2
3
4
5
6
7
8
9
10
11
; 隐藏PHP版本信息(响应头不返回X-Powered-By)
expose_php = Off

; 禁用危险函数(根据业务需求调整,如exec/system等, 有的第三方库会需要用到部分底层方法)
disable_functions = exec,system,passthru,shell_exec,escapeshellcmd,escapeshellarg,proc_open,proc_get_status,pcntl_alarm,pcntl_fork

; 关闭远程文件包含(高危)
allow_url_include = Off

; 若业务不需要,关闭远程文件打开
allow_url_fopen = Off

剩下基本上不需要去处理直接就可以跑起服务, 不过需要注意的是 php 的组件和配置修改都需要手动去重启 fpm 服务.

系统定时

实际上在 linux 各大发行版普遍集成 systemd 的情况, 已经不太需要利用 crontab 这种守护定时程序.

实际上是因为 crontab 调度最小单位只能到分钟, 有些时延任务要到秒级别的情况可能没办法使用

大部分情况都会利用 php 来调度任务来执行定时程序, 这里假设设定个定时通知功能:

1
2
sudo touch /etc/systemd/system/php-notify.service # 定时调用服务
sudo touch /etc/systemd/system/php-notify.timer # 定时器服务

这里首先编写启动脚本服务(php-notify.service):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Unit]
Description=PHP Notify Service
After=network.target

[Service]
Type=oneshot

# 这里采用 php 调度脚本, 这里就是我们需要修改的地方, 按照你的需求定义即可
ExecStart=/usr/bin/php -f {项目根目录路径}/notify.php

# 限制脚本超时时间(如 5 秒, 避免任务卡死)
TimeoutSec=5

# 脚本失败后重试(最多 3 次,间隔 3 秒)
Restart=on-failure
RestartSec=3

# 运行用户(非 root 更安全)
# 一般都以依赖 nginx 网络服务的, 可以挂靠在 www-data 这个用户运行
User=www-data
Group=www-data

之后编写具体定时器单元(php-notify.timer):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Unit]
Description=Timer For PHP Notify

# 依赖服务单元, 也就是我们自己编写调度 service
Requires=php-notify.service

[Timer]
# 定时器激活后立即执行第一次任务
OnActiveSec=0s

# 上次任务完成后, 间隔 3 秒再次执行(核心配置)
OnUnitActiveSec=3s

# 绑定到服务单元
# 也就是我们自己编写服务单元
Unit=php-notify.service

[Install]
WantedBy=timers.target

最后更新下系统服务并启动即可:

1
2
3
sudo systemctl daemon-reload
sudo systemctl start php-notify.timer # 启动
sudo systemctl enable php-notify.timer # 有需要可以开机自启

其他什么定时调度扩展功能可以参考相关 systemd 教程