上一篇 下一篇 分享链接 返回 返回顶部

如何用两台香港服务器分层部署数据库与Web,减少磁盘I/O竞争?

发布人:Minchunlin 发布时间:2026-10-08 11:29 阅读量:1

把两台香港服务器分别作为 Web 节点和数据库节点,可以让静态资源、应用日志、上传文件与数据库数据页、redo log、binlog 分别落在不同服务器的磁盘上,减少同一块磁盘上的读写竞争。实施时不仅要修改数据库连接地址,还要完成内网通信、数据库访问控制、数据迁移和切换验证,否则只是把本机 I/O 等待换成了网络等待。

下面以 Ubuntu Server 22.04 LTS、Nginx、PHP 8.1-FPM、MySQL 8.0 为部署环境:Web 服务保留在第一台香港服务器,MySQL 迁移到第二台,应用通过私网连接数据库。示例适用于中小型 PHP 网站的新部署或停写迁移;已有业务可替换应用目录、数据库名和域名,但应保留验证及回滚步骤。

部署环境与方案概述配图

围绕 Web 与数据库分层部署,A5数据提供香港物理服务器租用,覆盖常规建站、Xeon Gold及AMD EPYC等配置,可为两个节点分别配置计算、内存与磁盘资源。SSD、NVMe存储与大内存配置,为网站文件读写、数据库缓存及事务持久化提供硬件基础;不同套餐的CN2或国际带宽则承接网站与接口的公网访问需求,为业务分层和后续资源扩展提供配置空间。

一、准备条件:确认拆分的是磁盘压力,而不是只增加一台机器

1. 节点分工与上线目标

本文使用以下示例地址,执行前必须替换为实际配置。

项目Web 节点数据库节点
主机名hk-web-01hk-db-01
私网地址10.20.0.1110.20.0.12
运行组件Nginx、PHP-FPM、业务程序MySQL 8.0
主要磁盘读写静态文件、上传、应用日志、缓存数据文件、事务日志、临时表、备份
对外访问网站的 HTTP/HTTPS不向公网开放数据库端口
示例资源4 vCPU、4 GiB 内存4 vCPU、8 GiB 内存

资源配置只是演示起点,不是容量承诺。数据库需要多少内存,应根据活跃数据量、连接数、排序和临时表开销确定。

分层部署减少的是不同服务之间的磁盘竞争,不会消除数据库内部的 I/O 压力,也不等于数据库高可用。 数据库节点故障仍可能使网站不可用,两台服务器的备份和恢复能力仍需单独建设。

部署前还要向服务商确认:

  • 两台香港服务器是否有互通的私网,以及私网带宽、流量计费和访问限制。
  • 私网是否需要额外开通,安全组是否允许两台机器通信。
  • 云盘是否存在共享存储后端、单卷 IOPS 或吞吐上限。拆成两台虚拟机,不一定意味着底层存储完全独立。
  • 是否具备控制台或带外登录能力,避免防火墙配置错误后失联。

没有可信私网时,不应直接把 MySQL 3306 端口开放给所有公网来源。

2. 记录原环境和性能基线

以下命令适用于 Ubuntu 22.04。安装软件会写入磁盘并可能启动服务,生产节点应在维护窗口执行。

sudo apt update
sudo apt install -y sysstat iputils-ping netcat-openbsd

lsb_release -ds
ip -br address
ip route
lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS
df -h
free -h

在原单机环境中,选择有代表性的业务时段记录:

iostat -xz 1 10
pidstat -d 1 10

重点观察:

  • 数据盘的 r_await、w_await 与读写吞吐。
  • MySQL、PHP、日志写入进程分别产生多少 I/O。
  • 同期接口响应时间、慢查询和错误率。

%util 不能单独证明所有类型磁盘都已饱和,尤其是支持并行请求的 SSD 和虚拟磁盘。应结合等待时间、队列和业务延迟判断。

3. 验证私网并保留配置备份

在 Web 节点执行:

ping -c 5 10.20.0.12
ip route get 10.20.0.12

路由输出应指向预期私网接口,源地址应为 Web 私网地址。ICMP 被禁用时,后续还需用 TCP 和实际 SQL 验证,不能仅凭 ping 失败判断网络中断。

两台节点分别建立备份目录:

sudo install -d -m 700 /root/a5-split-backup

数据库节点安装 MySQL 后备份 /etc/mysql;Web 节点修改站点前备份 Nginx、PHP-FPM 和应用连接配置。备份目录包含口令和连接信息,不得放进网站目录或公开代码仓库。

二、部署数据库节点:私网监听、受限账号与 TLS

1. 安装并核对 MySQL 版本

在数据库节点执行:

sudo apt install -y mysql-server openssl
sudo cp -a /etc/mysql /root/a5-split-backup/mysql.before

mysql --version
sudo systemctl status mysql --no-pager
sudo mysql -e "SELECT VERSION();"

本文配置针对 MySQL 8.0,不应直接用于 MariaDB。若查询结果不是 8.0,应先检查软件源和兼容性。

2. 为数据库生成 TLS 证书

私网访问控制用于限制来源,TLS 用于保护连接。下面使用自建 CA 签发服务器证书,客户端后续同时验证 CA 和目标地址。

CA 私钥只保存在数据库节点的受保护目录,不复制到 Web 节点。示例证书有效期为 365 天,上线后应安排到期监控与轮换。

sudo -i
install -d -m 700 /root/db-tls-ca
install -d -o mysql -g mysql -m 750 /etc/mysql/tls
cd /root/db-tls-ca
umask 077

openssl req -x509 -newkey rsa:3072 -nodes \
  -keyout ca-key.pem -out ca.pem -days 365 \
  -subj "/CN=A5-DB-Internal-CA" \
  -addext "basicConstraints=critical,CA:TRUE" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"

openssl req -newkey rsa:3072 -nodes \
  -keyout server-key.pem -out server.csr \
  -subj "/CN=10.20.0.12"

cat > server.ext <<'EOF'
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=IP:10.20.0.12
EOF

openssl x509 -req -in server.csr \
  -CA ca.pem -CAkey ca-key.pem -CAcreateserial \
  -out server-cert.pem -days 365 -sha256 -extfile server.ext

install -o mysql -g mysql -m 644 ca.pem /etc/mysql/tls/ca.pem
install -o mysql -g mysql -m 644 server-cert.pem /etc/mysql/tls/server-cert.pem
install -o mysql -g mysql -m 600 server-key.pem /etc/mysql/tls/server-key.pem
exit

如果应用改用内部域名连接,应在证书的 SAN 中加入对应 DNS 名称,而不是关闭证书身份验证来绕过错误。

3. 配置监听地址和内存

创建新的配置文件,不覆盖发行版原文件:

sudo tee /etc/mysql/mysql.conf.d/90-a5-split.cnf >/dev/null <<'EOF'
[mysqld]
bind-address = 10.20.0.12
mysqlx-bind-address = 127.0.0.1

ssl-ca = /etc/mysql/tls/ca.pem
ssl-cert = /etc/mysql/tls/server-cert.pem
ssl-key = /etc/mysql/tls/server-key.pem
require_secure_transport = ON

innodb_buffer_pool_size = 4G
max_connections = 100
slow_query_log = ON
long_query_time = 1
EOF

sudo mysqld --validate-config
sudo systemctl restart mysql
sudo mysqladmin --protocol=socket ping
sudo ss -lntp | grep -E ':(3306|33060)\b'

示例中的 4 GiB Buffer Pool 面向 8 GiB 内存的专用数据库节点,仍需为连接缓冲、系统和其他组件留出空间,不能在 4 GiB 内存机器上照抄。

验证应看到:

  • 配置校验没有报错。
  • mysqladmin 返回 mysqld is alive。
  • 3306 监听在 10.20.0.12,而不是 0.0.0.0。
  • 启用 MySQL X Protocol 时,33060 仅监听回环地址。

不要为降低写入量随意关闭事务持久化。先完成隔离,再依据数据可靠性要求评估日志参数。

4. 创建业务库和最小权限账号

在数据库节点通过本机 socket 登录:

sudo env MYSQL_HISTFILE=/dev/null mysql

执行以下 SQL,口令必须替换为独立生成的强口令:

CREATE DATABASE shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

CREATE USER 'shop_app'@'10.20.0.11'
  IDENTIFIED BY 'REPLACE_WITH_A_UNIQUE_STRONG_PASSWORD'
  REQUIRE SSL;

GRANT SELECT, INSERT, UPDATE, DELETE
  ON shop.* TO 'shop_app'@'10.20.0.11';

SHOW GRANTS FOR 'shop_app'@'10.20.0.11';

这里以应用只需 CRUD 权限为前提。框架数据库迁移应使用独立管理账号;需要存储过程调用时,再针对实际对象授予 EXECUTE。不要把 root 远程账号用于网站连接。

已有业务应沿用经确认的字符集和排序规则,避免迁移时无意改变排序或唯一索引语义。

5. 限制防火墙来源

数据库节点只允许 Web 私网地址访问 3306:

sudo ufw allow from 10.20.0.11 to 10.20.0.12 port 3306 proto tcp
sudo ufw status verbose

如果 UFW 尚未启用,应先放行实际 SSH 端口和管理来源、确认控制台可用,再执行:

sudo ufw enable

不要盲目添加默认 22 端口规则;已改端口的主机会因此失联。若现有规则允许任意来源访问 3306,应在确认新规则生效后按规则编号删除旧规则,并同步检查服务商安全组。

三、部署 Web 节点:验证真实应用连接

1. 安装环境并复制 CA 公钥证书

在 Web 节点执行:

sudo apt install -y nginx php8.1-fpm php8.1-mysql mysql-client
sudo cp -a /etc/nginx /root/a5-split-backup/nginx.before
sudo cp -a /etc/php/8.1/fpm /root/a5-split-backup/php-fpm.before

php -v
php -m | grep -E 'PDO|pdo_mysql'
sudo systemctl status php8.1-fpm --no-pager

通过已确认主机身份的 SSH/SCP 或管理渠道,把数据库节点的 /etc/mysql/tls/ca.pem 复制到 Web 节点。这里只传输 CA 公钥证书,不传输 CA 私钥或服务器私钥。

假设文件已放在 /tmp/db-ca.pem:

sudo install -d -m 755 /etc/a5demo
sudo install -o root -g root -m 644 \
  /tmp/db-ca.pem /etc/a5demo/db-ca.pem

先验证网络,再验证账号和 TLS:

nc -vz -w 3 10.20.0.12 3306

mysql --host=10.20.0.12 --user=shop_app --password \
  --ssl-mode=VERIFY_IDENTITY \
  --ssl-ca=/etc/a5demo/db-ca.pem \
  shop

登录后执行:

SELECT DATABASE(), CURRENT_USER();
SHOW SESSION STATUS LIKE 'Ssl_cipher';
SELECT 1;

预期数据库为 shop、授权身份为 shop_app@10.20.0.11,Ssl_cipher 非空。不要把密码直接写在命令参数中。

2. 配置 PHP 数据库连接

以下最小应用用于验证 Nginx、PHP-FPM 和远程数据库整条链路,不应覆盖已有业务文件。已有网站应修改自身连接配置,并清理框架配置缓存。

sudo install -d -o root -g www-data -m 750 /srv/a5demo/public
sudo install -o root -g www-data -m 640 \
  /dev/null /etc/a5demo/database.php
sudoedit /etc/a5demo/database.php

文件内容:

<?php
return [
    'dsn' => 'mysql:host=10.20.0.12;port=3306;dbname=shop;charset=utf8mb4',
    'user' => 'shop_app',
    'password' => 'REPLACE_WITH_A_UNIQUE_STRONG_PASSWORD',
    'options' => [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_TIMEOUT => 3,
        PDO::MYSQL_ATTR_SSL_CA => '/etc/a5demo/db-ca.pem',
        PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
    ],
];

数据库口令文件位于网站根目录之外,只有 root 和 PHP 所属组可读。不要为了处理权限问题将其改为全员可写。

创建测试入口:

sudo tee /srv/a5demo/public/index.php >/dev/null <<'EOF'
<?php
header('Content-Type: application/json; charset=utf-8');

try {
    $config = require '/etc/a5demo/database.php';
    $pdo = new PDO(
        $config['dsn'],
        $config['user'],
        $config['password'],
        $config['options']
    );
    $ok = (int) $pdo->query('SELECT 1')->fetchColumn() === 1;
    echo json_encode(['web' => 'ready', 'database' => $ok ? 'ready' : 'error']);
} catch (Throwable $e) {
    error_log('Database probe failed: ' . $e->getMessage());
    http_response_code(503);
    echo json_encode(['web' => 'ready', 'database' => 'unavailable']);
}
EOF

sudo chown root:www-data /srv/a5demo/public/index.php
sudo chmod 640 /srv/a5demo/public/index.php

错误详情只写服务器日志,不把数据库地址、账号或堆栈返回给访客。

3. 配置 Nginx 并验证

新建独立测试站点:

server {
    listen 80;
    server_name app.example.com;

    root /srv/a5demo/public;
    index index.php;

    access_log /var/log/nginx/a5demo.access.log;
    error_log /var/log/nginx/a5demo.error.log;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }

    location ~ /\. {
        deny all;
    }
}

将配置保存为 /etc/nginx/sites-available/a5demo,确认没有其他站点重复使用同一域名后启用:

sudo ln -s /etc/nginx/sites-available/a5demo \
  /etc/nginx/sites-enabled/a5demo

sudo nginx -t
sudo systemctl reload nginx

curl --fail --show-error \
  -H 'Host: app.example.com' http://127.0.0.1/

成功时可得到如下示例结果:

{"web":"ready","database":"ready"}

此结果只证明连接可用,不代表业务迁移完成。生产网站还应沿用或配置有效的 HTTPS 证书;已有应用的路由、上传限制和静态缓存配置也不能用上述测试配置替代。

PHP-FPM 并发应与数据库连接容量一起规划。每个 PHP 工作进程都可能占用连接,后台任务也会连接数据库,不能一边扩大 pm.max_children,一边忽略 MySQL 的连接上限。

四、迁移业务数据:停写、导出、导入、切换

1. 确定停写范围

新建业务可直接执行应用的初始化流程。已有业务使用下面的停写迁移方式。

停写必须覆盖网站、队列、定时任务、管理后台和外部写入接口。只给首页挂维护提示,不代表数据库已经停止写入。

建议操作顺序:

  1. 备份原应用数据库配置和框架缓存处理方法。
  2. 启用业务维护模式,停止生产消费者、定时写入任务。
  3. 等待在途请求和事务结束。
  4. 确认没有业务写入后再导出。
  5. 在目标库完成验证前,保持维护状态。

确有需要时可停止原节点 PHP-FPM:

sudo systemctl stop php8.1-fpm

该命令会中断 PHP 服务,未配置维护页时可能返回 502;回滚时用 systemctl start php8.1-fpm 恢复。其他语言服务、计划任务和独立消费者必须分别停止。

2. 导出并传输数据

以下命令在原数据库所在服务器执行,适用于主要使用 InnoDB 的 MySQL 8.0 数据库:

sudo -i
umask 077

mysqldump --single-transaction --quick \
  --routines --triggers --events \
  --set-gtid-purged=OFF --no-tablespaces \
  --default-character-set=utf8mb4 \
  shop > /root/shop-cutover.sql

sha256sum /root/shop-cutover.sql > /root/shop-cutover.sql.sha256
exit

必须检查命令退出状态和错误输出。非事务表不能依靠 --single-transaction 获得一致快照;导出期间也不要执行建表、改表等 DDL。

转储文件含业务数据,应通过受控 SSH/SCP 通道传输到数据库节点的 /root 目录,并在那里核对校验值:

sudo -i
cd /root
sha256sum -c shop-cutover.sql.sha256
mysql shop < shop-cutover.sql
exit

导入可能执行建表或删除同名表,因此目标 shop 应为空库;若已有数据,必须先备份并确认覆盖范围,不能直接执行。

包含视图、触发器、事件和存储过程的业务,还需检查 DEFINER 账号及其权限。事件调度器也应单独核对,避免迁移后遗漏任务或两端重复执行。

3. 对照业务数据后切换连接

在两端分别核对关键业务表:

SELECT COUNT(*) AS order_count, MAX(id) AS latest_order_id
FROM shop.orders;

orders 仅为示例,应替换为实际表名。大表全表计数会产生额外读取,可以改用指定日期区间、主键范围和业务汇总核对。

随后修改真实应用的数据库地址、账号、CA 路径与 TLS 校验选项,清理配置缓存,再启动 PHP-FPM及需要恢复的消费者。

上线前还要检查:

  • cron、队列和后台脚本是否仍连接 localhost。
  • 持久连接或常驻进程是否仍保持旧连接。
  • 数据库事件是否只在目标节点运行。
  • 原数据库是否已退出业务写入路径。

保留旧数据库用于回滚,但不要允许新旧两端同时接受独立业务写入。普通单库拆分不具备自动合并两端数据的能力。

五、结果检查与异常处理:确认 I/O 隔离确实生效

1. 从功能到性能逐层验收

先验证登录、列表查询和一次可控写入,再检查事务提交、重复请求和后台任务。写入测试应使用测试账号及可清理的数据,不要随意修改真实订单。

两台节点同时采集:

iostat -xz 1 10
pidstat -d 1 10

数据库节点补充查看:

sudo mysql -e "
SHOW GLOBAL STATUS
WHERE Variable_name IN (
  'Threads_connected',
  'Threads_running',
  'Slow_queries',
  'Innodb_buffer_pool_reads',
  'Innodb_buffer_pool_read_requests'
);"

这些状态值大多为累计值,应比较同一时间窗口的增量,不能拿不同运行时长的绝对值直接比较。

拆分后的合理变化是:Web 节点不再承载业务 MySQL 数据文件的持续读写,数据库节点承担主要数据读写。最终是否改善,需要在相近流量、相近业务请求组合下比较磁盘等待时间、接口延迟和错误率。

若数据库节点仍处于高 I/O 等待,而 Web 节点磁盘已明显空闲,说明服务间竞争得到隔离,但数据库自身瓶颈尚未解决。 下一步应检查慢查询、索引、Buffer Pool、临时表落盘和存储规格,而不是继续增加 Web 机器。

不要在生产库运行无边界压测或写满磁盘的测试。

2. 注意拆分后新增的网络成本

数据库从本机变成远程服务后,每次串行交互都会经过网络。以每次往返约 1 毫秒、一个页面存在 20 次依次执行的数据库交互为例,仅往返等待就可能增加约 20 毫秒,还不包含 SQL 执行和数据传输时间。这只是解释机制的估算,不是香港服务器的固定延迟。

因此应同时减少逐行查询、过大的结果集和重复建连。香港地域一致不代表两台机器一定处于同一机房或具有低延迟私网,必须验证实际链路。

3. 按低风险到高风险排查故障

现象优先检查结果含义与处理
TCP 3306 连接超时私网路由、安全组、UFW通常是路径或过滤问题,不要先改数据库账号
Connection refusedMySQL 状态、监听地址主机通常可达,但目标端口没有正常监听
Access denied来源地址、账号 Host、口令私网流量可能经过地址转换,应确认 MySQL 实际看到的来源
证书校验失败CA、SAN、系统时间、有效期修正证书或连接目标,不要关闭身份验证
命令行成功、PHP 失败扩展、文件权限、FPM 配置缓存CLI 与 Web 运行环境可能不同
网站返回 502FPM 服务、socket、Nginx 配置先检查 Web 链路,不一定是数据库问题
拆分后延迟增加私网往返、查询次数、慢 SQL可能从磁盘瓶颈转为网络或查询瓶颈

对应的日志检查命令:

sudo journalctl -u mysql -n 100 --no-pager
sudo journalctl -u php8.1-fpm -n 100 --no-pager
sudo tail -n 100 /var/log/nginx/a5demo.error.log

数据库节点和 Web 节点分别执行对应命令。日志可能包含账号、SQL 和业务信息,提供排障材料前应脱敏。

六、验收与回滚检查项

1. 上线验收清单

  • [ ] 数据库只监听指定私网地址,3306 不向任意公网来源开放。
  • [ ] 应用账号限制为实际 Web 来源,权限与业务需求匹配。
  • [ ] 命令行与 PHP 均通过 TLS 证书验证,而非仅建立加密连接。
  • [ ] 关键表、最新业务记录、汇总数据核对一致。
  • [ ] 网站读写、队列、cron、管理后台都已使用目标库。
  • [ ] 旧数据库没有继续接受业务写入,两端没有重复定时任务。
  • [ ] Web 节点与数据库节点的磁盘指标分别采集,业务延迟和错误率可对照。
  • [ ] 备份已安排到独立存储,并验证过恢复;备份任务不会长期挤占业务磁盘。
  • [ ] 数据盘空间、连接数、慢查询、证书到期和错误率已有监控。
  • [ ] 本机测试入口已移除或限制访问,生产网站使用 HTTPS。

2. 尚未产生新写入时的回滚

如果目标库还没有接受新的业务写入,可以恢复原应用连接配置、清理缓存、重启应用进程,并恢复原定时任务与消费者。

仅回滚本文新建的测试站点时:

sudo unlink /etc/nginx/sites-enabled/a5demo
sudo nginx -t
sudo systemctl reload nginx

此操作只移除测试站点的启用链接,不删除配置和业务数据。已有业务站点应恢复自己的配置备份,不要直接覆盖整套 Nginx 目录。

3. 已经产生新写入时的回滚

一旦目标数据库产生了新订单、用户修改或后台写入,就不能直接切回旧库,否则会让这些变化从业务视角消失。

此时应重新进入维护状态,停止所有写入,从目标库导出最新完整数据,备份旧库后再恢复。若两端版本一致、迁移期间没有结构变更,且已经演练过覆盖恢复流程,可在原节点执行:

sudo -i
umask 077
mysqldump --single-transaction --quick \
  --routines --triggers --events \
  --set-gtid-purged=OFF --no-tablespaces \
  shop > /root/shop-before-rollback.sql

mysql shop < /root/shop-back-from-db.sql
exit

其中 /root/shop-back-from-db.sql 必须是目标数据库停写后导出并校验通过的文件。导入可能删除或覆盖同名表,执行前需确认磁盘空间、数据版本、对象权限及恢复范围;有结构变更时不能把这段命令当成通用回滚脚本。

回滚结束后再次核对:

  • [ ] 最近新增和修改的数据已经恢复到原节点。
  • [ ] 应用连接地址、口令及本地连接方式已恢复。
  • [ ] 目标数据库不再接受应用写入。
  • [ ] 消费者和定时任务仅在一套环境运行。
  • [ ] 网站读写验证通过,日志未持续出现连接或权限错误。
  • [ ] 两端备份和迁移记录继续保留,未因回滚立即删除。

两台香港服务器分层部署的价值,在于把 Web 文件读写和数据库持久化读写分开管理。只有同时验证数据一致性、连接安全、网络开销和磁盘指标,才能确认这次拆分真正减少了 I/O 竞争,而不是把压力换了一个位置。