> For AI agents: the site index is available at https://www.juniortree.com/llms.txt; this page is available as Markdown at https://www.juniortree.com/blog/typecho-dual-machine-disaster-recovery-and-failover.md.
> Typecho双机灾备方案：文件同步+数据库主从+自动DNS切换

## Page metadata

- **Published:** 2025-06-07
- **Updated:** 2025-06-07
- **Tags:** 云原生
- **Source URL:** https://www.juniortree.com/blog/typecho-dual-machine-disaster-recovery-and-failover

# Typecho 双机灾备与故障转移

Typecho 是传统的基于 php 的博客系统，该系统主要由两部分组成，一部分是站点文件，另外一部分是数据库

要实现 Typecho 的双机灾备与故障转移，我们需要确保站点文件和数据库的同步与高可用切换能力

## 架构目标

- 两台服务器：主机（A）+ 备机（B）
- 主机故障时，备机能够自动/手动接管服务

要求具备以下能力：

- Typecho 站点文件的一致性
- 数据库数据的同步
- 域名或负载均衡的快速切换

## 站点文件的同步策略

使用 `inotifywait` 搭配 `rsync`，首先在主机（A）上执行：

```bash
apt update
apt install inotify-tools -y
```

之后编写一个同步脚本：

```sh
#!/bin/bash

WATCH_DIR="/var/www/typecho"
REMOTE="<备机（B）>:/var/www/typecho"

inotifywait -mrq -e modify,create,delete,move "$WATCH_DIR" | while read path action file; do
    echo "Detected change: $action $file"
    rsync -az --delete "$WATCH_DIR"/ $REMOTE
done
```

其中的 `<备机（B）>` 要替换成对于的 IP，并且在主机（A）上配置好密钥验证或者证书验证，确保其能正常通过 `rsync` 进行连接

之后文件会存入到备机（B）的对应 `/var/www/typecho` 目录中

## 数据库同步策略

可以参考我写的这篇文章：

[post cid="115" /]

需要注意的是，表同步过去了之后，在备站（B）上的数据库上，也要为 Typecho 配置对应的权限

什么意思呢，你可以打开 Typecho 的配置文件，一般来说在路径 `/var/www/typecho` 下，文件名叫做 `config.inc.php`

里面有关于数据库连接的一些配置

![](https://s3.juniortree.com/pic/2025/06/087d5e8fbe7cb8b07dc43d9383649e2f.webp)

比如我这里的 Typecho 博客，就是以 `typecho` 这个用户去连接数据库的，我们在备站（B）的数据库中也要配置一个 `typecho` 用户，并且密码也要和配置文件里面一样，同时赋予其读 `typecho*` 表的权限

首先以 `root` 身份连接到数据库：

```bash
sudo mysql -u root
```

创建 `typecho` 用户，这里假设你上面查到的 Typecho 表为 `typecho`，密码设置为上面配置文件的密码：

```sql
CREATE USER 'typecho'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON typecho.* TO 'typecho'@'localhost';
FLUSH PRIVILEGES;
```

之后重新使用 `typecho` 用户登录，看看能否看到表：

![](https://s3.juniortree.com/pic/2025/06/efe8cc6fa728971925719f56caa9449c.webp)

## 配置 Web 服务

不出意外的话，现在你现在在备站（B）配置好 Web 服务，然后在云服务商将 DNS 改到备站（B）的 IP，就可以正常访问了

这个站点除了不能评论和写新文章，和主站（A）完全一样

> 蛤？你问我为什么只能读不能写？因为数据库是主从设计，备站（B）的数据库只能从主站（A）单向同步，本身不能推送数据到主站（A）的数据库

还有一个就是证书的问题，一般情况下，站点域名都是解析到主站的 IP 上，备用站没办法正常获取到证书（HTTP-01），只能通过 DNS-01 的方法获取证书

我比较喜欢用 Caddy，这里需要自己编译一个新的 Caddy，可以看我写的这篇文章：

[post cid="117" /]

因为我的域名是托管在阿里云的，可以使用他们的 DNS 进行证书验证签发，你需要在 RAM 中去创建 AccessKey，得到一对：`AccessKey ID` 和 `AccessKey Secret`，并且为其最少配置对 DNS 的完全管理权

最好不要把 `AccessKey ID` 和 `AccessKey Secret` 这种比较敏感的东西直接写到 `Caddyfile` 中，比较推荐的方法是把变量写进 systemd 的 EnvironmentFile

在 `/etc/caddy/` 目录下新建一个专门存放环境变量的文件，比如 `/etc/caddy/aliyun. Env`：

```bash
nano /etc/caddy/aliyun.env
```

写入：

```
ALIYUN_ACCESS_KEY_ID=你的AccessKeyID
ALIYUN_ACCESS_KEY_SECRET=你的AccessKeySecret
```

保存后设置权限：

```bash
sudo chmod 600 /etc/caddy/aliyun.env
sudo chown root:root /etc/caddy/aliyun.env
```

修改 Caddy 的 systemd 单元文件 `/etc/systemd/system/caddy.service`（或 `/lib/systemd/system/caddy.service`，取决你之前放在哪儿）。在 `[Service]` 段里，加上一行：

```
EnvironmentFile=/etc/caddy/aliyun.env
```

最终的（核心片段）大致长这样：

```
[Service]
User=caddy
Group=caddy
EnvironmentFile=/etc/caddy/aliyun.env
ExecStart=/usr/local/bin/caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
Restart=on-failure
```

重载，并且重启一下：

```bash
sudo systemctl daemon-reload
sudo systemctl restart caddy
sudo journalctl -u caddy -f
```

这时 Caddy 启动日志不再弹出 “AccessKeyID is empty” 的错误，应能看到 `solving challenges`、`certificate obtained successfully` 等正常输出

在两台服务器上都可以进行一样的配置

## 切流量

切的前提是健康检查失败，Typecho 这个博客系统本身不带有健康检查，得自己重新写一个 php 页面，可以从这个 gist 里面取： [Typecho健康检查](https://gist.juniortree.com/liueic/f21967c23f864c2f9aae5bc773d1c1e5)

之后放到网站根目录（`/var/www/typecho`）中，即可：

![](https://s3.juniortree.com/pic/2025/06/61c4fe9bfb2c1c114114618c7aa15057.webp)

之后就可以开始设计切流量的脚本了，阿里云给的 Python SDK 还是比较丰富和好用的，为此，我写了一个很好用的脚本，并且可以使用 Docker 进行部署：[GitHub - liueic/typecho\_dnstypecho](https://github.com/liueic/typecho_dnstypecho)

![](https://s3.juniortree.com/pic/2025/06/0bfc6c6e2fe8b574772303af7a6c4ee2.webp)

![](https://s3.juniortree.com/pic/2025/06/f2dbe6fa42031d0deb40ca6fa54b09c8.webp)

![](https://s3.juniortree.com/pic/2025/06/f4cd3ab7651c9618e0c3ecaf0d334784.webp)

## 检验效果

可以在主站直接停掉 MariaDB，这样话 Health Check 就通不过，就可以伪造出站点垮了的效果

```bash
systemctl stop mariadb
```

测试完了不要忘记重新启动数据库：

```bash
systemctl start mariadb
```
