linux中级_php服务

TJCcc 发布于 2025-12-07 23 次阅读


笔记一: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);
        }
    }
}
?>

总结

关键要点:

  1. 动静态分离:Nginx处理静态,PHP-FPM处理动态
  2. 连接方式:Unix socket性能优于TCP
  3. 数据库连接:PDO是最安全的选择,支持预处理和事务
  4. 安全配置:限制文件访问、隐藏敏感信息、使用预处理语句
  5. 性能优化: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

常见问题解决:

  1. 502 Bad Gateway:检查PHP-FPM是否运行,socket权限
  2. File not found:检查SCRIPT_FILENAME路径
  3. 权限错误:确保Nginx和PHP-FPM用户一致
  4. 内存不足:调整php.ini中的memory_limit
  5. 连接超时:增加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(&param_name, &param_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(
                    ['&', '<', '>', '"', "'"],
                    ['&amp;', '&lt;', '&gt;', '&quot;', '&apos;'],
                    $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数据查询 → 原路返回结果

每个环节都可能有性能瓶颈和安全风险,需要针对性地优化和加固。