笔记一:PHP 动态服务配置
一、动静态服务介绍
1.1 静态服务 vs 动态服务
# 静态服务示例 - Nginx直接处理
server {
location ~* \.(jpg|jpeg|png|gif|ico|css|js|html|txt)$ {
root /var/www/static;
expires 30d; # 缓存控制
access_log off;
}
}
# 动态服务示例 - PHP处理
server {
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
1.2 动静态服务对比
| 特性 | 静态服务 | 动态服务 (PHP) |
|---|---|---|
| 内容 | 固定文件 | 动态生成 |
| 处理 | Nginx直接处理 | PHP解释器处理 |
| 性能 | 高 (内存/磁盘IO) | 较低 (CPU计算) |
| 缓存 | 容易 (CDN/浏览器) | 需要特别处理 |
| 扩展性 | 简单 | 复杂 (会话/数据库) |
| 示例 | HTML/CSS/JS/图片 | 用户登录/购物车/API |
1.3 混合架构示例
请求流程:
客户端 → Nginx (静态文件) → PHP-FPM (动态脚本) → 数据库
┌─────────────┐
│ 客户端 │
└──────┬──────┘
│
┌──────▼──────┐
│ Nginx │
│ │
│ ┌──────┐ │
静态文件 ←─-┤ │ 静态 │ │
│ │ 服务 │ │
│ └──────┘ │
│ │ │
│ ┌────▼──┐ │
动态请求───► │ PHP │ │
│ │ 处理 │ │
│ └────┬──┘ │
└──────│────--─┘
│
┌────▼────┐
│ 数据库 │
└─────────┘
二、PHP服务安装
2.1 不同系统安装方法
# Ubuntu/Debian 系统
# 1. 添加PHP仓库
sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
# 2. 安装PHP及相关扩展
sudo apt install php8.1 php8.1-fpm php8.1-mysql php8.1-curl \
php8.1-gd php8.1-mbstring php8.1-xml php8.1-zip \
php8.1-opcache php8.1-redis php8.1-memcached
# 3. 启动服务
sudo systemctl start php8.1-fpm
sudo systemctl enable php8.1-fpm
# CentOS/RHEL 系统
# 1. 添加EPEL和Remi仓库
sudo yum install epel-release
sudo yum install http://rpms.remirepo.net/enterprise/remi-release-8.rpm
# 2. 安装PHP
sudo yum install php81 php81-php-fpm php81-php-mysqlnd \
php81-php-gd php81-php-mbstring php81-php-xml \
php81-php-opcache
# 3. 启动服务
sudo systemctl start php81-php-fpm
sudo systemctl enable php81-php-fpm
# macOS (Homebrew)
brew install php@8.1
brew services start php@8.1
# 验证安装
php -v
php -m # 查看已加载模块
2.2 PHP-FPM配置详解
# /etc/php/8.1/fpm/pool.d/www.conf
[www]
user = www-data # PHP进程运行用户
group = www-data # 用户组
listen = /run/php/php8.1-fpm.sock # Unix socket(推荐)
# listen = 127.0.0.1:9000 # TCP socket(备用)
# 进程管理
pm = dynamic # 动态进程管理
pm.max_children = 50 # 最大子进程数
pm.start_servers = 5 # 启动时子进程数
pm.min_spare_servers = 5 # 最小空闲进程数
pm.max_spare_servers = 10 # 最大空闲进程数
pm.max_requests = 500 # 每个进程处理请求数后重启
# 性能优化
request_terminate_timeout = 30s # 请求超时时间
request_slowlog_timeout = 5s # 慢请求日志阈值
slowlog = /var/log/php-fpm/slow.log # 安全设置
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# 环境变量
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp env[TMPDIR] = /tmp
env[TEMP] = /tmp
# 访问控制
php_admin_value[open_basedir] = /var/www:/tmp
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm/error.log
2.3 多版本PHP共存配置
# Nginx配置多版本PHP
server {
listen 80;
server_name example.com;
# PHP 8.1应用
location ~ ^/app81/(.*\.php)$ {
alias /var/www/app81/$1;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
# PHP 7.4应用
location ~ ^/app74/(.*\.php)$ {
alias /var/www/app74/$1;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
# 默认PHP版本
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
三、PHP内置变量详解
3.1 超全局变量
<?php
// 1. $_SERVER - 服务器和执行环境信息
echo "服务器IP: " . $_SERVER['SERVER_ADDR'] . "<br>";
echo "客户端IP: " . $_SERVER['REMOTE_ADDR'] . "<br>";
echo "请求方法: " . $_SERVER['REQUEST_METHOD'] . "<br>";
echo "请求URI: " . $_SERVER['REQUEST_URI'] . "<br>";
echo "用户代理: " . $_SERVER['HTTP_USER_AGENT'] . "<br>";
echo "HTTPS状态: " . (isset($_SERVER['HTTPS']) ? '是' : '否') . "<br>";
echo "脚本路径: " . $_SERVER['SCRIPT_FILENAME'] . "<br>";
// 2. $_GET - URL查询参数
// URL: http://example.com/page.php?id=123&name=test
$id = $_GET['id'] ?? '未指定'; // null合并运算符
$name = $_GET['name'] ?? '匿名';
// 3. $_POST - POST表单数据
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING);
}
// 4. $_FILES - 文件上传信息
if (isset($_FILES['upload'])) {
$fileName = $_FILES['upload']['name'];
$fileSize = $_FILES['upload']['size'];
$fileTmp = $_FILES['upload']['tmp_name'];
$fileError = $_FILES['upload']['error'];
}
// 5. $_COOKIE - HTTP Cookies
$user_pref = $_COOKIE['theme'] ?? 'light';
// 6. $_SESSION - 会话变量
session_start();
$_SESSION['user_id'] = 123;
$_SESSION['last_login'] = time();
// 7. $_REQUEST - GET, POST, COOKIE的集合(不推荐使用)
// 8. $GLOBALS - 全局作用域中的全部变量
// 重要环境变量
echo "PHP版本: " . PHP_VERSION . "<br>";
echo "操作系统: " . PHP_OS . "<br>";
echo "最大内存: " . ini_get('memory_limit') . "<br>";
echo "时区: " . date_default_timezone_get() . "<br>";
?>
3.2 完整的PHP信息脚本
<?php
// info.php - 显示PHP配置信息
phpinfo();
// 或自定义信息展示
function getPhpInfo() {
$info = [
'系统信息' => [
'PHP版本' => PHP_VERSION,
'服务器软件' => $_SERVER['SERVER_SOFTWARE'],
'操作系统' => PHP_OS,
'服务器IP' => $_SERVER['SERVER_ADDR'],
'客户端IP' => $_SERVER['REMOTE_ADDR'],
],
'配置信息' => [
'内存限制' => ini_get('memory_limit'),
'最大执行时间' => ini_get('max_execution_time'),
'上传文件大小' => ini_get('upload_max_filesize'),
'时区' => date_default_timezone_get(),
],
'模块信息' => [
'MySQL扩展' => extension_loaded('mysqli') ? '已加载' : '未加载',
'PDO扩展' => extension_loaded('pdo') ? '已加载' : '未加载',
'GD库' => extension_loaded('gd') ? '已加载' : '未加载',
],
];
return $info;
}
// 安全检查:仅在开发环境显示
if ($_SERVER['REMOTE_ADDR'] === '127.0.0.1') {
echo "<pre>";
print_r(getPhpInfo());
echo "</pre>";
} else {
header('HTTP/1.1 403 Forbidden');
echo "Access Denied";
}
?>
四、Nginx和PHP连接配置
4.1 FastCGI连接方式
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html index.htm;
# 基础配置
charset utf-8;
client_max_body_size 100M;
# 日志配置
access_log /var/log/nginx/example.com.access.log main;
error_log /var/log/nginx/example.com.error.log warn;
# 静态文件缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# PHP处理配置
location ~ \.php$ {
# 安全检查:避免执行不存在的PHP文件
try_files $uri =404;
# FastCGI配置
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
# 重要参数
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
# 包含标准参数
include fastcgi_params;
# 自定义参数
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type if_not_empty;
fastcgi_param CONTENT_LENGTH $content_length if_not_empty;
# 超时设置
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
fastcgi_connect_timeout 300;
# 缓冲区设置
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
# 关闭响应缓存(用于调试)
fastcgi_buffering off;
}
# 隐藏敏感文件
location ~ /\.(ht|git|svn) {
deny all;
}
# 防止直接访问PHP源文件
location ~* \.php\.(txt|html|bak)$ {
deny all;
}
# 优雅的404处理
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# 自定义错误页面
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
4.2 FastCGI参数详解
# fastcgi_params 文件内容示例
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP特殊参数
fastcgi_param PHP_VALUE "upload_max_filesize=100M \n post_max_size=100M";
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/example.com:/tmp";
# 真实IP传递(当使用反向代理时)
fastcgi_param HTTP_X_REAL_IP $remote_addr;
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
4.3 优化PHP-FPM和Nginx连接
# 连接池优化配置
upstream php_backend {
# Unix socket(推荐,性能更好)
server unix:/run/php/php8.1-fpm.sock;
# 或TCP连接(多服务器时)
# server 127.0.0.1:9000;
# server backend2.example.com:9000;
# 负载均衡策略
# least_conn; # 最少连接数
# ip_hash; # IP哈希(会话保持)
# 健康检查
keepalive 32;
}
server {
location ~ \.php$ {
# 使用连接池
fastcgi_pass php_backend;
# keepalive配置
fastcgi_keep_conn on;
# 缓冲区优化
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 256k;
# 缓存
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=phpcache:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache phpcache;
fastcgi_cache_valid 200 301 302 5m;
fastcgi_cache_valid 404 1m;
# 缓存绕过条件
fastcgi_cache_bypass $http_cache_control;
fastcgi_no_cache $http_pragma $http_authorization;
# 添加缓存头
add_header X-Cache $upstream_cache_status;
}
}
五、PHP和数据库连接
5.1 MySQL连接方式对比
<?php
// 1. MySQLi (面向过程)
$host = 'localhost';
$user = 'root';
$pass = 'password';
$db = 'test_db';
// 连接数据库
$conn = mysqli_connect($host, $user, $pass, $db);
if (!$conn) {
die("连接失败: " . mysqli_connect_error());
}
// 查询示例
$sql = "SELECT id, name, email FROM users";
$result = mysqli_query($conn, $sql);
while ($row = mysqli_fetch_assoc($result)) {
echo "ID: {$row['id']}, Name: {$row['name']}<br>";
}
// 关闭连接
mysqli_close($conn);
// ===========================================
// 2. MySQLi (面向对象)
class Database {
private $conn;
public function __construct($host, $user, $pass, $db) {
$this->conn = new mysqli($host, $user, $pass, $db);
if ($this->conn->connect_error) {
die("连接失败: " . $this->conn->connect_error);
}
// 设置字符集
$this->conn->set_charset("utf8mb4");
}
public function query($sql, $params = []) {
$stmt = $this->conn->prepare($sql);
if (!empty($params)) {
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
return $stmt->get_result();
}
public function close() {
$this->conn->close();
}
}
// 使用示例
$db = new Database('localhost', 'root', 'password', 'test_db');
$result = $db->query("SELECT * FROM users WHERE active = ?", [1]);
// ===========================================
// 3. PDO (PHP Data Objects) - 推荐
class PdoDatabase {
private $pdo;
private static $instance = null;
// 单例模式
private function __construct() {
$dsn = "mysql:host=localhost;dbname=test_db;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => true, // 持久连接
];
try {
$this->pdo = new PDO($dsn, 'root', 'password', $options);
} catch (PDOException $e) {
throw new PDOException($e->getMessage(), (int)$e->getCode());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->pdo;
}
}
?>
5.2 完整的数据库操作类
<?php
/**
* 数据库操作类 (PDO方式)
*/
class Database {
private static $instance = null;
private $pdo;
private $stmt;
private function __construct() {
// 从配置读取数据库信息
$config = [
'host' => 'localhost',
'dbname' => 'app_db',
'username' => 'app_user',
'password' => 'secure_password',
'charset' => 'utf8mb4',
];
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '+08:00'",
];
try {
$this->pdo = new PDO($dsn, $config['username'], $config['password'], $options);
} catch (PDOException $e) {
// 生产环境记录日志,不显示错误详情
error_log("数据库连接失败: " . $e->getMessage());
die("数据库连接失败");
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 执行查询
*/
public function query($sql, $params = []) {
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($params);
return $this;
} catch (PDOException $e) {
$this->handleException($e, $sql, $params);
}
}
/**
* 获取单条记录
*/
public function fetch() {
return $this->stmt->fetch();
}
/**
* 获取所有记录
*/
public function fetchAll() {
return $this->stmt->fetchAll();
}
/**
* 获取单列值
*/
public function fetchColumn($column = 0) {
return $this->stmt->fetchColumn($column);
}
/**
* 获取最后插入的ID
*/
public function lastInsertId() {
return $this->pdo->lastInsertId();
}
/**
* 开始事务
*/
public function beginTransaction() {
return $this->pdo->beginTransaction();
}
/**
* 提交事务
*/
public function commit() {
return $this->pdo->commit();
}
/**
* 回滚事务
*/
public function rollBack() {
return $this->pdo->rollBack();
}
/**
* 获取行数
*/
public function rowCount() {
return $this->stmt->rowCount();
}
/**
* 错误处理
*/
private function handleException($e, $sql = '', $params = []) {
// 记录错误日志
$errorMsg = "SQL错误: " . $e->getMessage();
$errorMsg .= "\nSQL语句: " . $sql;
$errorMsg .= "\n参数: " . print_r($params, true);
error_log($errorMsg);
// 开发环境显示错误
if (defined('APP_ENV') && APP_ENV === 'development') {
die($errorMsg);
}
// 生产环境返回通用错误
die("数据库操作失败");
}
/**
* 防止克隆
*/
private function __clone() {}
/**
* 防止反序列化
*/
private function __wakeup() {}
}
// 使用示例
class UserModel {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
public function getUserById($id) {
return $this->db
->query("SELECT * FROM users WHERE id = ? AND status = 'active'", [$id])
->fetch();
}
public function createUser($data) {
$this->db->beginTransaction();
try {
$this->db->query(
"INSERT INTO users (username, email, password_hash, created_at)
VALUES (?, ?, ?, NOW())",
[$data['username'], $data['email'], password_hash($data['password'], PASSWORD_DEFAULT)]
);
$userId = $this->db->lastInsertId();
$this->db->query(
"INSERT INTO user_profiles (user_id, full_name) VALUES (?, ?)",
[$userId, $data['full_name']]
);
$this->db->commit();
return $userId;
} catch (Exception $e) {
$this->db->rollBack();
throw $e;
}
}
public function searchUsers($keyword, $page = 1, $limit = 10) {
$offset = ($page - 1) * $limit;
return $this->db
->query(
"SELECT u.id, u.username, u.email, p.full_name
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
WHERE u.username LIKE ? OR u.email LIKE ? OR p.full_name LIKE ?
ORDER BY u.created_at DESC
LIMIT ? OFFSET ?",
["%{$keyword}%", "%{$keyword}%", "%{$keyword}%", $limit, $offset]
)
->fetchAll();
}
}
?>
5.3 数据库配置文件
<?php
// config/database.php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => 'InnoDB',
'options' => extension_loaded('pdo_mysql') ? [
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_TIMEOUT => 5,
] : [],
],
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
],
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
],
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],
];
?>
5.4 连接池和读写分离
<?php
/**
* 数据库连接池和读写分离
*/
class DatabaseManager {
private $readConnections = [];
private $writeConnection;
private $currentReadIndex = 0;
public function __construct() {
// 写连接(主库)
$this->writeConnection = $this->createConnection([
'host' => 'master.db.example.com',
'username' => 'write_user',
'password' => 'write_password',
'database' => 'app_db',
]);
// 读连接池(从库)
$slaves = [
['host' => 'slave1.db.example.com', 'username' => 'read_user', 'password' => 'read_password'],
['host' => 'slave2.db.example.com', 'username' => 'read_user', 'password' => 'read_password'],
['host' => 'slave3.db.example.com', 'username' => 'read_user', 'password' => 'read_password'],
];
foreach ($slaves as $slave) {
$this->readConnections[] = $this->createConnection(array_merge($slave, [
'database' => 'app_db',
]));
}
}
private function createConnection($config) {
$dsn = "mysql:host={$config['host']};dbname={$config['database']};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $config['username'], $config['password'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_PERSISTENT => false, // 连接池不用持久连接
]);
return $pdo;
} catch (PDOException $e) {
error_log("数据库连接失败: {$config['host']} - " . $e->getMessage());
return null;
}
}
/**
* 获取读连接(负载均衡)
*/
public function getReadConnection() {
if (empty($this->readConnections)) {
return $this->writeConnection;
}
// 简单的轮询负载均衡
$connection = $this->readConnections[$this->currentReadIndex];
$this->currentReadIndex = ($this->currentReadIndex + 1) % count($this->readConnections);
// 如果连接失败,尝试下一个
if (!$this->testConnection($connection)) {
return $this->getReadConnection();
}
return $connection;
}
/**
* 获取写连接
*/
public function getWriteConnection() {
return $this->writeConnection;
}
/**
* 根据SQL类型选择连接
*/
public function getConnection($sql) {
$sql = strtolower(trim($sql));
// 写操作使用主库
if (strpos($sql, 'insert') === 0 ||
strpos($sql, 'update') === 0 ||
strpos($sql, 'delete') === 0 ||
strpos($sql, 'replace') === 0 ||
strpos($sql, 'create') === 0 ||
strpos($sql, 'alter') === 0 ||
strpos($sql, 'drop') === 0 ||
strpos($sql, 'truncate') === 0) {
return $this->getWriteConnection();
}
// 读操作使用从库
return $this->getReadConnection();
}
private function testConnection($pdo) {
if (!$pdo) return false;
try {
$pdo->query('SELECT 1');
return true;
} catch (PDOException $e) {
return false;
}
}
}
// 使用示例
class Repository {
private $dbManager;
public function __construct() {
$this->dbManager = new DatabaseManager();
}
public function find($id) {
$pdo = $this->dbManager->getConnection("SELECT * FROM users WHERE id = ?");
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
public function create($data) {
$pdo = $this->dbManager->getConnection("INSERT INTO users ...");
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$data['name'], $data['email']]);
return $pdo->lastInsertId();
}
}
?>
六、安全配置和最佳实践
6.1 PHP安全配置
; php.ini 安全配置
; ==========================================
; 错误处理
display_errors = Off ; 生产环境关闭错误显示
log_errors = On ; 开启错误日志
error_log = /var/log/php_error.log
; 文件上传
file_uploads = On
upload_max_filesize = 20M
post_max_size = 20M
max_file_uploads = 20
; 执行限制
max_execution_time = 30 ; 脚本最大执行时间
max_input_time = 60 ; 脚本解析输入数据的最大时间
memory_limit = 128M
; 安全模式相关(PHP 7.4+已移除,用其他方式替代)
open_basedir = /var/www:/tmp ; 限制PHP可访问目录
disable_functions = exec,system,passthru,shell_exec,proc_open,popen
disable_classes =
; 会话安全
session.cookie_httponly = 1
session.cookie_secure = 1 ; 仅HTTPS
session.use_strict_mode = 1
session.cookie_samesite = Strict
; 其他安全设置
expose_php = Off ; 隐藏PHP版本
allow_url_fopen = Off ; 禁用远程文件打开
allow_url_include = Off ; 禁用远程文件包含
; 时区设置
date.timezone = Asia/Shanghai
6.2 Nginx PHP安全配置
# Nginx安全配置
server {
location ~ \.php$ {
# 基础安全
try_files $uri =404; # 防止直接执行不存在的PHP文件
# 隐藏PHP版本
fastcgi_hide_header X-Powered-By;
fastcgi_hide_header X-PHP-Version;
# 添加安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 限制请求方法
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 405;
}
# 防止路径遍历攻击
if ($uri ~ "\.\.") {
return 403;
}
}
# 防止访问敏感文件
location ~* ^/(\.git|\.env|composer\.json|composer\.lock|\.htaccess|\.user.ini) {
deny all;
return 404;
}
# 防止访问PHP备份文件
location ~* \.(php|log|sql|tar|gz|zip)$\.bak$ {
deny all;
}
}
6.3 完整的部署脚本示例
#!/bin/bash
# deploy_php_app.sh
set -e
# 配置变量
APP_NAME="myapp"
APP_DIR="/var/www/${APP_NAME}"
PHP_VERSION="8.1"
DB_NAME="${APP_NAME}_db"
DB_USER="${APP_NAME}_user"
DB_PASS=$(openssl rand -base64 16)
echo "开始部署 ${APP_NAME}..."
# 1. 创建目录
sudo mkdir -p ${APP_DIR}
sudo chown -R www-data:www-data ${APP_DIR}
sudo chmod 755 ${APP_DIR}
# 2. 配置Nginx
cat > /tmp/${APP_NAME}.conf << EOF
server {
listen 80;
server_name ${APP_NAME}.example.com;
root ${APP_DIR}/public;
index index.php index.html;
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php${PHP_VERSION}-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
EOF
sudo mv /tmp/${APP_NAME}.conf /etc/nginx/sites-available/
sudo ln -sf /etc/nginx/sites-available/${APP_NAME}.conf /etc/nginx/sites-enabled/
# 3. 创建数据库
sudo mysql -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';"
sudo mysql -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"
# 4. 创建环境文件
cat > ${APP_DIR}/.env << EOF
APP_ENV=production
APP_DEBUG=false
APP_KEY=$(openssl rand -base64 32)
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=${DB_NAME}
DB_USERNAME=${DB_USER}
DB_PASSWORD=${DB_PASS}
REDIS_HOST=localhost
REDIS_PASSWORD=null
REDIS_PORT=6379
EOF
sudo chown www-data:www-data ${APP_DIR}/.env
sudo chmod 600 ${APP_DIR}/.env
# 5. 重启服务
sudo systemctl reload nginx
sudo systemctl restart php${PHP_VERSION}-fpm
echo "部署完成!"
echo "数据库信息:"
echo " 数据库名: ${DB_NAME}"
echo " 用户名: ${DB_USER}"
echo " 密码: ${DB_PASS}"
七、性能监控和调试
7.1 PHP性能监控配置
<?php
// 性能监控类
class PerformanceMonitor {
private $startTime;
private $memoryUsage;
private $queries = [];
public function __construct() {
$this->startTime = microtime(true);
$this->memoryUsage = memory_get_usage();
// 注册shutdown函数
register_shutdown_function([$this, 'shutdown']);
// 设置错误处理
set_error_handler([$this, 'errorHandler']);
set_exception_handler([$this, 'exceptionHandler']);
}
public function logQuery($sql, $time) {
$this->queries[] = [
'sql' => $sql,
'time' => $time,
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
];
}
public function shutdown() {
$totalTime = microtime(true) - $this->startTime;
$peakMemory = memory_get_peak_usage() / 1024 / 1024; // MB
$totalMemory = (memory_get_usage() - $this->memoryUsage) / 1024; // KB
$logData = [
'timestamp' => date('Y-m-d H:i:s'),
'execution_time' => round($totalTime, 4) . 's',
'peak_memory' => round($peakMemory, 2) . 'MB',
'memory_used' => round($totalMemory, 2) . 'KB',
'query_count' => count($this->queries),
'queries' => $this->queries,
'request_uri' => $_SERVER['REQUEST_URI'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
];
// 记录到文件或发送到监控系统
$this->saveLog($logData);
// 如果是慢请求,额外记录
if ($totalTime > 1) { // 超过1秒
$this->logSlowRequest($logData);
}
}
private function saveLog($data) {
$logFile = '/var/log/php/performance_' . date('Y-m-d') . '.log';
file_put_contents($logFile, json_encode($data) . PHP_EOL, FILE_APPEND);
}
private function logSlowRequest($data) {
$logFile = '/var/log/php/slow_requests_' . date('Y-m-d') . '.log';
file_put_contents($logFile, json_encode($data) . PHP_EOL, FILE_APPEND);
}
public function errorHandler($errno, $errstr, $errfile, $errline) {
$error = [
'type' => 'error',
'code' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'time' => date('Y-m-d H:i:s')
];
error_log(json_encode($error));
// 生产环境不显示错误
if (APP_ENV !== 'production') {
return false; // 继续执行默认错误处理
}
}
public function exceptionHandler($exception) {
$error = [
'type' => 'exception',
'class' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString(),
'time' => date('Y-m-d H:i:s')
];
error_log(json_encode($error));
// 生产环境显示友好错误页面
if (APP_ENV === 'production') {
header('HTTP/1.1 500 Internal Server Error');
include __DIR__ . '/views/errors/500.html';
} else {
echo "<pre>";
print_r($error);
echo "</pre>";
}
exit(1);
}
}
// 在应用入口文件初始化
$monitor = new PerformanceMonitor();
?>
7.2 OPcache配置优化
; opcache.ini 配置
[opcache]
opcache.enable=1 opcache.enable_cli=0 opcache.memory_consumption=256 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=10000 opcache.max_wasted_percentage=5 opcache.use_cwd=1 opcache.validate_timestamps=0 ; 生产环境关闭,重启PHP-FPM时清空 opcache.revalidate_freq=60 ; 开发环境可以设置60秒 opcache.fast_shutdown=1 opcache.enable_file_override=1 opcache.optimization_level=0x7FFFBFFF opcache.blacklist_filename=/etc/php/8.1/opcache-blacklist.txt ; 保存编译结果到共享内存,加速重启 opcache.save_comments=1 opcache.load_comments=1 ; 预加载配置(PHP 7.4+) opcache.preload=/var/www/preload.php opcache.preload_user=www-data
<?php
// preload.php - OPcache预加载脚本
if (function_exists('opcache_compile_file')) {
// 预加载常用框架文件
$files = [
'/var/www/vendor/autoload.php',
'/var/www/app/helpers.php',
'/var/www/app/Models/User.php',
'/var/www/app/Models/Product.php',
// 添加更多常用文件...
];
foreach ($files as $file) {
if (file_exists($file)) {
opcache_compile_file($file);
}
}
}
?>
总结
关键要点:
- 动静态分离:Nginx处理静态,PHP-FPM处理动态
- 连接方式:Unix socket性能优于TCP
- 数据库连接:PDO是最安全的选择,支持预处理和事务
- 安全配置:限制文件访问、隐藏敏感信息、使用预处理语句
- 性能优化:OPcache、连接池、读写分离、缓存策略
部署检查清单:
# 1. 检查服务状态
sudo systemctl status nginx
sudo systemctl status php8.1-fpm
sudo systemctl status mysql
# 2. 检查配置语法
sudo nginx -t
sudo php-fpm8.1 -t
# 3. 检查文件权限
ls -la /var/www/
ps aux | grep php-fpm # 查看运行用户
# 4. 测试PHP
curl -I http://localhost/info.php
php -m | grep -E "(mysql|pdo|opcache|redis)"
# 5. 检查错误日志
tail -f /var/log/nginx/error.log
tail -f /var/log/php8.1-fpm.log
常见问题解决:
- 502 Bad Gateway:检查PHP-FPM是否运行,socket权限
- File not found:检查SCRIPT_FILENAME路径
- 权限错误:确保Nginx和PHP-FPM用户一致
- 内存不足:调整php.ini中的memory_limit
- 连接超时:增加fastcgi_read_timeout
笔记二:从客户端到MySQL的完整数据流通详解
一、整体数据流通架构

二、详细数据流通流程
2.1 网络层面:客户端到Nginx
TCP连接建立(三次握手)
# 1. SYN: 客户端 -> 服务器
客户端:SYN=1, Seq=x, Port=80
# 2. SYN-ACK: 服务器 -> 客户端
Nginx:SYN=1, ACK=1, Seq=y, Ack=x+1
# 3. ACK: 客户端 -> 服务器
客户端:ACK=1, Seq=x+1, Ack=y+1
HTTP请求解析
# 客户端发送的HTTP请求
GET /index.php?id=123 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml
Cookie: session_id=abc123
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Connection: keep-alive
# 二进制数据流:
47 45 54 20 2f 69 6e 64 65 78 2e 70 68 70 3f 69 64 3d 31 32 33 20 48 54 54 50 2f 31 2e 31 0d 0a
48 6f 73 74 3a 20 65 78 61 6d 70 6c 65 2e 63 6f 6d 0d 0a
...
Nginx接收和解析
# Nginx的事件驱动模型(epoll/kqueue)
worker_processes auto; # 自动设置工作进程数
events {
worker_connections 1024; # 每个进程最大连接数
use epoll; # Linux使用epoll,FreeBSD使用kqueue
multi_accept on;
}
# 请求处理阶段(11个阶段)
# 1. POST_READ阶段:读取请求头后立即执行
# 2. SERVER_REWRITE阶段:server级别的rewrite
# 3. FIND_CONFIG阶段:查找匹配的location
# 4. REWRITE阶段:location级别的rewrite
# 5. POST_REWRITE阶段:rewrite后的处理
# 6. PREACCESS阶段:访问控制前准备
# 7. ACCESS阶段:访问权限检查
# 8. POST_ACCESS阶段:访问后处理
# 9. PRECONTENT阶段:内容处理前
# 10. CONTENT阶段:内容处理
# 11. LOG阶段:日志记录
2.2 Nginx到PHP-FPM的数据流通
FastCGI协议详解
// FastCGI记录格式(二进制协议)
typedef struct {
unsigned char version; // FastCGI协议版本,通常为1
unsigned char type; // 记录类型
unsigned char requestIdB1; // 请求ID高8位
unsigned char requestIdB0; // 请求ID低8位
unsigned char contentLengthB1; // 内容长度高8位
unsigned char contentLengthB0; // 内容长度低8位
unsigned char paddingLength; // 填充长度
unsigned char reserved; // 保留字节
unsigned char contentData[contentLength]; // 内容数据
unsigned char paddingData[paddingLength]; // 填充数据
} FCGI_Record;
// 记录类型:
// 1: FCGI_BEGIN_REQUEST 开始请求
// 2: FCGI_ABORT_REQUEST 中止请求
// 3: FCGI_END_REQUEST 结束请求
// 4: FCGI_PARAMS 参数传递
// 5: FCGI_STDIN 标准输入(POST数据)
// 6: FCGI_STDOUT 标准输出
// 7: FCGI_STDERR 标准错误
// 8: FCGI_DATA 额外数据
Nginx到PHP-FPM的数据转换
# 1. Nginx将HTTP请求转换为FastCGI参数
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php-fpm.sock;
# 关键参数转换(实际发送的FastCGI记录)
fastcgi_param REQUEST_METHOD $request_method; # GET/POST等
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string; # ?id=123
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param REMOTE_ADDR $remote_addr; # 客户端IP
# 其他参数(共100+个)
include fastcgi_params;
}
# 2. 转换后的FastCGI数据包示例:
#
# 包1: FCGI_BEGIN_REQUEST
# 角色: FCGI_RESPONDER (1)
# 标志: FCGI_KEEP_CONN (1) 保持连接
#
# 包2: FCGI_PARAMS (多个)
# 名称-值对:
# REQUEST_METHOD: GET
# SCRIPT_FILENAME: /var/www/index.php
# QUERY_STRING: id=123
# CONTENT_TYPE: text/html
# HTTP_HOST: example.com
# HTTP_USER_AGENT: Mozilla/5.0
#
# 包3: FCGI_PARAMS结束 (空记录)
#
# 包4: FCGI_STDIN (如果有POST数据)
# 数据: username=admin&password=123456
#
# 包5: FCGI_STDIN结束 (空记录)
PHP-FPM处理流程
// PHP-FPM进程状态机
enum fpm_state {
FPM_STATE_INIT, // 初始化
FPM_STATE_READY, // 就绪,等待请求
FPM_STATE_ACCEPTING, // 接受连接
FPM_STATE_RUNNING, // 执行PHP脚本
FPM_STATE_FINISHED, // 执行完成
FPM_STATE_ENDING // 结束中
};
// 进程管理
static void fpm_child_main(...) {
while (1) {
// 1. 等待FastCGI请求
fcgi_accept_request(&request);
// 2. 解析FastCGI参数
char *php_script_path = fcgi_get_param("SCRIPT_FILENAME");
char *query_string = fcgi_get_param("QUERY_STRING");
// 3. 准备PHP执行环境
SG(server_context) = (void *) &request;
init_request_info();
// 4. 执行PHP脚本
php_execute_script(&file_handle);
// 5. 发送响应
fcgi_finish_request(&request, 1);
// 6. 清理并回到就绪状态
php_request_shutdown(NULL);
}
}
2.3 PHP到MySQL的数据流通
MySQL协议详解
# MySQL客户端/服务器协议流程
# 阶段1: 连接建立
# 客户端 -> 服务器: 握手初始包 (协议版本、客户端能力)
# 服务器 -> 客户端: 握手响应包 (协议版本、服务器版本、连接ID)
# 客户端 -> 服务器: 认证响应 (用户名、加密密码)
# 服务器 -> 客户端: 认证结果 (OK/ERROR)
# 阶段2: 命令执行
# 客户端 -> 服务器: COM_QUERY (查询命令)
# 服务器 -> 客户端: 结果集
# 列数量 -> 列定义 -> EOF包 -> 行数据 -> EOF包
# 阶段3: 连接关闭
# 客户端 -> 服务器: COM_QUIT
PHP的MySQL连接实现
// PHP的PDO实现(pdo_mysql扩展)
PHP_FUNCTION(pdo_mysql_connect) {
// 1. 建立TCP连接
socket = php_network_connect(host, port, timeout);
// 2. MySQL握手
mysql_handshake(socket);
// 3. 认证
mysql_authenticate(socket, username, password, database);
// 4. 设置字符集
mysql_set_charset(socket, "utf8mb4");
}
// PDO预处理语句执行
PHP_METHOD(PDOStatement, execute) {
// 1. 发送COM_STMT_PREPARE
send_prepare_packet(sql);
// 2. 接收预处理结果(语句ID、参数数量、列信息)
receive_prepare_result();
// 3. 绑定参数
for (i = 0; i < param_count; i++) {
send_long_data(param_id, param_value);
}
// 4. 发送COM_STMT_EXECUTE
send_execute_packet(statement_id, params);
// 5. 接收结果集
receive_result_set();
}
实际数据包示例
# 1. 查询请求包
# 格式: [包长度(3字节)][序列号(1字节)][命令(1字节)][SQL语句]
query_packet = b'\x0c\x00\x00\x00\x03SELECT * FROM users WHERE id = 1'
# 2. 预处理语句请求
prepare_packet = b'\x16\x00\x00\x00\x16SELECT * FROM users WHERE id = ?'
# 3. 响应结果包
result_packet = [
b'\x01\x00\x00\x01', # 列数量包
b'\x03def\x06testdb\x05users\x02id', # 列定义
b'\xfe\x00\x00\x02\x00', # EOF包
b'\x01\x31', # 行数据: "1"
b'\xfe\x00\x00\x02\x00' # EOF包
]
三、关键技术组件详解
3.1 连接池管理
Nginx连接池
# Nginx upstream连接池
upstream php_backend {
server unix:/var/run/php/php-fpm.sock;
# 连接池配置
keepalive 32; # 每个worker保持的连接数
keepalive_timeout 60s; # 连接空闲超时
keepalive_requests 100; # 每个连接处理的请求数
}
# FastCGI连接池
location ~ \.php$ {
fastcgi_keep_conn on; # 启用keepalive
fastcgi_pass php_backend;
# 连接超时控制
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 10s;
fastcgi_read_timeout 30s;
# 缓冲区优化
fastcgi_buffers 8 4k; # 响应缓冲区数量及大小
fastcgi_buffer_size 4k; # 响应缓冲区大小
}
PHP-FPM进程池
; /etc/php/8.1/fpm/pool.d/www.conf
[www]
; 进程管理策略 pm = dynamic ; 动态进程管理 pm.max_children = 50 ; 最大进程数 pm.start_servers = 5 ; 启动时的进程数 pm.min_spare_servers = 5 ; 最小空闲进程数 pm.max_spare_servers = 10 ; 最大空闲进程数 pm.max_requests = 500 ; 每个进程处理请求数后重启 ; 每个进程的MySQL连接池 ; PHP本身不提供MySQL连接池,但PDO可以配置持久连接 pdo_mysql.default_socket = /var/run/mysqld/mysqld.sock pdo_mysql.cache_size = 2000
MySQL连接池(服务器端)
-- MySQL连接线程池配置
-- 1. 查看当前连接状态
SHOW STATUS LIKE 'Threads_%';
/*
Threads_cached: 0 # 缓存的线程数
Threads_connected: 10 # 当前打开的连接数
Threads_created: 100 # 已创建的线程总数
Threads_running: 3 # 非睡眠状态的线程数
*/
-- 2. 连接池相关配置
SHOW VARIABLES LIKE '%thread%';
/*
thread_cache_size: 9 # 线程缓存大小
thread_handling: one-thread-per-connection # 线程处理模式
thread_stack: 262144 # 每个线程的栈大小(256KB)
*/
-- 3. 最大连接数配置
SHOW VARIABLES LIKE 'max_connections';
-- max_connections: 151
-- 4. 连接超时设置
SHOW VARIABLES LIKE '%timeout%';
/*
connect_timeout: 10 # 连接超时(秒)
interactive_timeout: 28800 # 交互式连接超时(8小时)
wait_timeout: 28800 # 非交互式连接超时(8小时)
*/
3.2 数据缓冲机制
Nginx缓冲配置
# 缓冲层1:客户端到Nginx
client_body_buffer_size 128k; # 请求体缓冲区
client_header_buffer_size 4k; # 请求头缓冲区
large_client_header_buffers 4 8k; # 大请求头缓冲区
# 缓冲层2:Nginx到PHP-FPM
fastcgi_buffering on; # 启用缓冲
fastcgi_buffers 8 4k; # 缓冲区数量和大小
fastcgi_buffer_size 4k; # 响应第一部分缓冲区
fastcgi_busy_buffers_size 8k; # 忙时缓冲区大小
fastcgi_temp_file_write_size 32k; # 临时文件写入大小
# 缓冲层3:Nginx到客户端
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
# 内存中的缓冲 vs 磁盘临时文件
fastcgi_max_temp_file_size 1024m; # 最大临时文件大小
fastcgi_temp_path /var/cache/nginx/fastcgi_temp; # 临时文件路径
PHP缓冲机制
<?php
// PHP的多层缓冲机制
// 1. 输出缓冲(ob_start)
ob_start(); // 开启输出缓冲
echo "内容不会立即发送...";
$content = ob_get_contents(); // 获取缓冲内容
ob_end_clean(); // 清空并关闭缓冲
// 2. 默认输出缓冲
// php.ini配置: output_buffering = 4096
// 3. 刷新缓冲的时机
flush(); // 刷新PHP缓冲到Web服务器
ob_flush(); // 刷新输出缓冲
fastcgi_finish_request(); // 结束请求,继续执行脚本
// 4. 缓冲的实际流程
// 脚本输出 → PHP输出缓冲 → FastCGI缓冲 → Nginx缓冲 → 客户端TCP缓冲 → 浏览器渲染
// 5. 禁用缓冲(调试用)
ini_set('output_buffering', '0');
ini_set('zlib.output_compression', '0');
while (ob_get_level()) ob_end_flush();
?>
MySQL缓冲池
-- InnoDB缓冲池(最重要的MySQL性能组件)
SHOW VARIABLES LIKE 'innodb_buffer_pool%';
/*
innodb_buffer_pool_size: 134217728 # 缓冲池大小(128MB)
innodb_buffer_pool_instances: 1 # 缓冲池实例数
innodb_buffer_pool_chunk_size: 134217728
*/
-- 查询缓存(MySQL 8.0已移除,用其他方案替代)
-- 旧版本配置:
-- query_cache_type = 1
-- query_cache_size = 64M
-- 使用状态查看缓冲效率
SHOW STATUS LIKE 'Innodb_buffer_pool%';
/*
Innodb_buffer_pool_read_requests: 1000 # 逻辑读取次数
Innodb_buffer_pool_reads: 50 # 物理磁盘读取次数
命中率 = (1000 - 50) / 1000 = 95%
*/
3.3 协议转换点
HTTP到FastCGI的转换
# 伪代码:HTTP请求到FastCGI的转换
def http_to_fastcgi(http_request):
"""将HTTP请求转换为FastCGI记录"""
# 1. 创建BEGIN_REQUEST记录
begin_record = FastCGI_Record(
type=FCGI_BEGIN_REQUEST,
content={
'role': FCGI_RESPONDER,
'flags': FCGI_KEEP_CONN
}
)
# 2. 创建PARAMS记录(HTTP头部转换为CGI变量)
params = []
# 映射表:HTTP头 -> CGI变量
mapping = {
'Host': 'HTTP_HOST',
'User-Agent': 'HTTP_USER_AGENT',
'Cookie': 'HTTP_COOKIE',
'Content-Type': 'CONTENT_TYPE',
'Content-Length': 'CONTENT_LENGTH',
}
for http_header, value in http_request.headers.items():
cgi_var = mapping.get(http_header)
if cgi_var:
params.append((cgi_var, value))
# 固定参数
params.extend([
('REQUEST_METHOD', http_request.method),
('SCRIPT_FILENAME', document_root + http_request.path),
('QUERY_STRING', http_request.query_string),
('SERVER_PROTOCOL', 'HTTP/1.1'),
('REMOTE_ADDR', http_request.client_ip),
])
# 3. 创建STDIN记录(POST数据)
stdin_record = None
if http_request.method == 'POST':
stdin_record = FastCGI_Record(
type=FCGI_STDIN,
content=http_request.body
)
return [begin_record] + params_records + [stdin_record]
FastCGI到PHP环境的转换
// PHP内部:将FastCGI参数设置到$_SERVER超全局变量
void fcgi_setup_env(zval *server_array) {
// 从FastCGI请求中读取所有参数
char *param_name, *param_value;
while (fcgi_get_next_param(¶m_name, ¶m_value)) {
// 转换为PHP变量名格式(HTTP_前缀、大写、下划线)
char *php_var_name = convert_to_php_var_name(param_name);
// 添加到$_SERVER数组
add_assoc_string(server_array, php_var_name, param_value);
// 特殊处理一些变量到其他超全局变量
if (strcmp(param_name, "REQUEST_METHOD") == 0) {
// 设置到$_SERVER['REQUEST_METHOD']
} else if (strcmp(param_name, "QUERY_STRING") == 0) {
// 解析到$_GET数组
parse_query_string(param_value, &_GET);
}
}
// 设置PHP脚本路径
char *script_filename = fcgi_get_param("SCRIPT_FILENAME");
SG(request_info).path_translated = script_filename;
// 设置其他执行环境
SG(request_info).request_method = get_request_method();
SG(sapi_headers).http_response_code = 200;
}
PHP到MySQL协议的转换
// PDO预处理语句的协议转换
static int pdo_mysql_stmt_execute(pdo_stmt_t *stmt) {
// 1. 构建COM_STMT_EXECUTE包
MYSQL_STMT *S = stmt->driver_data;
MYSQL_BIND *b = S->params;
// 2. 参数绑定和转换
for (i = 0; i < stmt->bound_params; i++) {
pdo_bound_param_data *param = stmt->bound_params[i];
// PHP类型到MySQL类型的转换
switch (Z_TYPE(param->parameter)) {
case IS_LONG:
b[i].buffer_type = MYSQL_TYPE_LONG;
b[i].buffer = &intval;
break;
case IS_DOUBLE:
b[i].buffer_type = MYSQL_TYPE_DOUBLE;
b[i].buffer = &doubleval;
break;
case IS_STRING:
b[i].buffer_type = MYSQL_TYPE_STRING;
b[i].buffer = Z_STRVAL(param->parameter);
b[i].buffer_length = Z_STRLEN(param->parameter);
break;
case IS_NULL:
b[i].buffer_type = MYSQL_TYPE_NULL;
break;
// ... 其他类型
}
}
// 3. 发送执行命令
mysql_stmt_execute(S);
// 4. 获取结果并转换回PHP类型
fetch_and_convert_results(stmt);
return SUCCESS;
}
四、性能优化关键点
4.1 关键路径优化
数据流通的瓶颈点
# 性能瓶颈通常出现在这些位置:
瓶颈点 = {
1: "网络延迟": "客户端到Nginx的往返时间",
2: "DNS解析": "域名到IP的解析时间",
3: "SSL握手": "HTTPS连接的TLS协商",
4: "Nginx配置": "复杂的重写规则、过多的location匹配",
5: "进程间通信": "Nginx到PHP-FPM的Unix/TCP socket传输",
6: "PHP解释": "脚本编译和执行时间",
7: "数据库连接": "MySQL连接的建立和认证",
8: "SQL执行": "查询优化、索引使用、锁竞争",
9: "结果集传输": "大量数据从MySQL到PHP的内存复制",
10: "序列化": "PHP对象到JSON/XML的转换",
11: "输出缓冲": "多层缓冲区的flush操作"
}
# 优化策略对应表
优化策略 = {
"网络延迟": "使用CDN、HTTP/2、连接复用",
"进程间通信": "使用Unix socket、调整缓冲区大小",
"PHP解释": "使用OPcache、预编译、JIT(PHP 8.0+)",
"数据库连接": "使用连接池、持久连接、读写分离",
"SQL执行": "添加索引、查询优化、使用缓存"
}
延迟分布示例
一个典型的请求延迟分布(100ms总时间):
1. 网络传输: 20ms (20%)
├── DNS查询: 5ms
├── TCP握手: 5ms
├── TLS协商: 8ms (如果HTTPS)
└── 数据传输: 2ms
2. Nginx处理: 10ms (10%)
├── 请求解析: 2ms
├── Location匹配: 1ms
├── 静态文件检查: 1ms
├── FastCGI转发: 5ms
└── 日志写入: 1ms
3. PHP-FPM处理: 40ms (40%)
├── 进程调度: 5ms
├── PHP解释执行: 25ms
├── 数据库交互: 8ms
└── 响应构建: 2ms
4. 数据库处理: 25ms (25%)
├── 连接建立: 3ms
├── SQL解析: 2ms
├── 查询执行: 15ms
├── 结果集获取: 4ms
└── 连接释放: 1ms
5. 响应返回: 5ms (5%)
└── 网络传输: 5ms
4.2 监控和调试
全链路跟踪配置
# Nginx配置:添加跟踪ID
http {
# 生成或传递跟踪ID
map $http_x_trace_id $trace_id {
default $request_id; # 使用Nginx生成的request_id
"~." $http_x_trace_id; # 如果客户端提供了则使用
}
log_format trace_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"trace_id=$trace_id" '
'"nginx_time=$request_time" '
'"upstream_time=$upstream_response_time"';
access_log /var/log/nginx/access.log trace_log;
# 传递跟踪ID到后端
location ~ \.php$ {
fastcgi_param HTTP_X_TRACE_ID $trace_id;
# ... 其他配置
}
}
<?php
// PHP接收并传递跟踪ID
class TraceMiddleware {
public function handle($request) {
// 从HTTP头获取跟踪ID
$traceId = $_SERVER['HTTP_X_TRACE_ID'] ?? uniqid('php_', true);
// 记录开始时间
define('REQUEST_START_TIME', microtime(true));
// 在响应头中添加跟踪ID
header("X-Trace-ID: $traceId");
// 记录到日志
error_log(sprintf(
"[%s] PHP Start: %s %s",
$traceId,
$_SERVER['REQUEST_METHOD'],
$_SERVER['REQUEST_URI']
));
// 数据库查询时也记录跟踪ID
DB::listen(function ($query) use ($traceId) {
error_log(sprintf(
"[%s] SQL: %s | Time: %sms",
$traceId,
$query->sql,
$query->time
));
});
}
public function terminate($request, $response) {
$traceId = $_SERVER['HTTP_X_TRACE_ID'] ?? '';
$executionTime = (microtime(true) - REQUEST_START_TIME) * 1000;
error_log(sprintf(
"[%s] PHP End: %dms | Memory: %dKB",
$traceId,
$executionTime,
memory_get_peak_usage() / 1024
));
// 在响应头中添加执行时间
header("X-Execution-Time: {$executionTime}ms");
}
}
?>
MySQL查询跟踪
-- 启用慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 1; -- 1秒以上的查询
-- 查看当前连接状态
SHOW PROCESSLIST;
/*
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+-------+------------------+
| 5 | root | localhost | test | Query | 0 | init | SHOW PROCESSLIST |
| 6 | app | localhost | app | Execute | 0.5 | Sending data | SELECT ... |
+----+------+-----------+------+---------+------+-------+------------------+
*/
-- 性能分析
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
/*
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| 1 | SIMPLE | users | ref | email_idx | const| 767 | const | 1 | NULL |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
*/
-- 启用查询性能分析
SET profiling = 1;
SELECT * FROM users WHERE id = 1;
SHOW PROFILES;
/*
+----------+------------+---------------------------------+
| Query_ID | Duration | Query |
+----------+------------+---------------------------------+
| 1 | 0.00012300 | SELECT * FROM users WHERE id=1 |
+----------+------------+---------------------------------+
*/
五、安全加固关键点
5.1 数据传输安全
# TLS/SSL配置(HTTPS)
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# 协议和密码套件配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# FastCGI数据也要加密传输(如果是TCP socket)
# Unix socket更安全,因为不经过网络
}
# PHP安全配置
# php.ini
session.cookie_secure = 1 # 仅HTTPS传输cookie
session.cookie_httponly = 1 # 防止JavaScript访问
session.use_strict_mode = 1 # 严格的会话管理
# MySQL安全连接
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca-cert.pem',
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
]);
5.2 输入验证和输出过滤
<?php
// 安全数据处理流程
class SecurityFilter {
// 1. 输入验证
public static function validateInput($input, $rules) {
$filtered = [];
foreach ($rules as $field => $rule) {
if (!isset($input[$field])) {
continue;
}
$value = $input[$field];
switch ($rule['type']) {
case 'int':
$filtered[$field] = filter_var($value, FILTER_VALIDATE_INT);
break;
case 'email':
$filtered[$field] = filter_var($value, FILTER_VALIDATE_EMAIL);
break;
case 'string':
$filtered[$field] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
break;
case 'sql':
// 使用预处理语句,而不是过滤
$filtered[$field] = $value; // 实际在查询时绑定
break;
}
}
return $filtered;
}
// 2. SQL预处理(防止注入)
public static function executeQuery($pdo, $sql, $params) {
$stmt = $pdo->prepare($sql);
foreach ($params as $key => $value) {
$type = is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR;
$stmt->bindValue($key, $value, $type);
}
$stmt->execute();
return $stmt;
}
// 3. 输出编码
public static function output($data, $type = 'html') {
switch ($type) {
case 'html':
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
case 'json':
return json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
case 'xml':
// XML特殊字符转义
return str_replace(
['&', '<', '>', '"', "'"],
['&', '<', '>', '"', '''],
$data
);
}
}
}
?>
六、总结:数据流通的关键组件
关键组件和它们的作用:
1. 网络协议栈 (TCP/IP)
- 建立可靠的数据传输通道
- 处理分包、重组、流量控制、拥塞控制
2. HTTP协议
- 定义客户端和服务器之间的通信格式
- 支持请求/响应模型、状态码、头部字段
3. Nginx
- 高性能的HTTP服务器和反向代理
- 静态文件服务、负载均衡、SSL终止
- HTTP到FastCGI的协议转换
4. FastCGI协议
- Web服务器和应用程序之间的通信协议
- 支持持久连接、多路复用
- 将HTTP请求转换为应用程序可理解的参数
5. PHP-FPM
- FastCGI进程管理器
- 进程池管理、资源隔离
- 请求分发到PHP解释器
6. PHP解释器
- 执行PHP脚本
- 内存管理、扩展加载
- 超全局变量初始化
7. PDO/MySQLi
- 数据库访问抽象层
- 连接管理、查询预处理
- 数据类型转换
8. MySQL协议
- 客户端/服务器通信协议
- 认证、查询、结果集传输
- 二进制协议,高效传输
9. InnoDB存储引擎
- 数据存储和检索
- 事务处理、锁管理
- 缓冲池管理
10. 操作系统内核
- 进程调度、内存管理
- 文件系统、网络栈
- I/O多路复用(epoll/kqueue)
数据流通路径总结:
客户端HTTP请求 → TCP/IP → Nginx接收解析 → FastCGI协议转换 →
PHP-FPM进程处理 → PHP脚本执行 → PDO数据库连接 →
MySQL协议传输 → InnoDB数据查询 → 原路返回结果
每个环节都可能有性能瓶颈和安全风险,需要针对性地优化和加固。
Comments NOTHING