0%

Discuz 大版本迁移中的准备、切换与流量故障

旧论坛在旧系统上运行多年( 2013 年左右开始建站、17年之后就很少维护了 )。
最近 PHP 爆了几个漏洞,还有梯子频繁被封 IP,就调整了一下架构。
然后一起把漏洞处理一下。

新版 Discuz 对 PHP 的要求已经超过旧环境,原地升级会让操作系统、运行时、数据库和论坛程序一起变化。
结合 aws LightSail CPU 突发型的实例,尽量降低 CPU 占用,避免影响线上服务。
所以就先把 Discuz 程序代码 + 数据库文件拉到本地,用 docker 的方式进行迁移验证。

近期 AI 爬虫是真多,迁移快完事的时候,把我站点都打挂了。
一个没什么人访问的老论坛,在线人数直接创新高。

总计 1058 人在线 - 最高记录是 30437 于 2026-11-3

1. 迁移目标

迁移要同时完成四件事:

  • 把 Discuz X3.4 按官方路线升级到 X5.0.4.Open:X3.4 → X3.5 → X5.0.4。
  • 把运行环境换到新系统和新版 PHP、MariaDB、Redis。
  • 保留 Cloudflare、HAProxy、Xray、Reality 和 Clash 的使用方式。
  • 先并行验证,再切正式流量,避免新系统和旧站互相影响。

DNS 只处理论坛域名和 WebSocket 入口。其他子域、邮件记录和无关服务不改。

最终链路仍然是 CDN 到源站:

flowchart LR
    U[访客] --> CF[Cloudflare]
    CF --> HA[HAProxy]
    HA --> NG[Nginx]
    HA --> XR[Xray]
    NG --> PHP[PHP-FPM]
    PHP --> DB[(MariaDB)]
    PHP --> RD[(Redis)]

2. 为什么先在线下升级

旧站运行多年,系统和运行时都落后于新版 Discuz 的要求。
直接替换线上目录,升级程序会在真实流量下修改数据库、模板、插件和缓存。
一次失败可能同时影响网站、后台和代理链路。

本地准备了两套环境:

  • 一套按旧环境复刻,专门执行 Discuz 官方数据库升级。
  • 一套换成新实例准备使用的运行环境,提前验证兼容性。

生产数据先导入本地。升级脚本按官方路线逐级执行,不跳版本。
升级完成后,再逐项检查首页、论坛页、帖子页、登录、后台、插件、头像、附件、私信和模板。

本地验证发现的问题,先在测试库和测试目录里修。只有验证通过的代码、数据库和补丁才进入生产候选包。

3. Discuz 和 PHP 兼容

大版本升级不是把压缩包解压到目录就结束。
旧论坛积累的插件、模板和 UCenter 数据,会和新版核心同时参与请求。

问题表现原因处理
官方包依赖目录为空新版无法正常启动发布包没有带全依赖单独取回依赖并合并
封禁缓存方法缺失重建缓存时报缺少方法发布包没有实现该方法按现有表结构补回
UCenter 模式不兼容登录和头像异常旧服务模式不再适用切换到独立模式
新运行时弃用旧写法页面和插件报错旧代码使用新运行时不再支持的行为应用兼容补丁并全量检查
luckypost 不初始化插件功能失效使用早期风格的构造函数改为新构造方式
私信没有开启提示私信功能尚未开启新版本新增开关,升级时没有补值补写设置并重建缓存
早期账号无法登录正确密码仍失败密码格式没有盐值,失败计数又按名称和数字账号分别记录兼容旧哈希,清理两类计数,重置后实测
登录验证码不显示提示验证码但页面没有输入框跳转令牌漏标记,生成和读取密钥也不一致统一密钥并补齐标记

这些问题都要在页面样式之前处理。核心请求、登录和缓存不稳定时,继续修 CSS 只会制造更多噪音。

4. 新实例怎么接数据

新实例原生安装 Nginx、PHP、MariaDB、Redis、HAProxy 和 Xray,不使用 Docker。
旧站依赖容器的地方,在新环境里全部改成系统服务和明确的本地端口。

迁移数据分三部分:

  • 代码和补丁进入站点目录。
  • 离线升级后的数据库导入新实例。
  • 附件和头像单独同步,不塞进代码包。

附件和头像使用可续传的文件同步,完成后检查文件数量和目录权限。
Web 进程必须能读取这些文件,否则页面能打开,图片和下载仍然会坏。

生产配置从旧实例单独迁移。测试环境的数据库主机、Redis 主机、域名和路径不能带过去。

5. 切换前怎么验证

DNS 没有立即改。先把正式域名临时解析到新实例,完整访问一遍。

验证不只看首页状态码,还包括:

  • 版块、帖子、个人空间和附件。
  • 前台登录、后台登录和登录后跳转。
  • 发帖、回帖、私信和头像。
  • 编辑器、认证图标、favicon 和静态资源。
  • WebSocket 入口和代理节点。
  • TLS 证书链。

页面里还不能残留测试端口和测试域名。数据库里的站点 URL、附件 URL 和 UCenter 地址必须改成生产值。

问题现象根因处理
生产地址被写成空值页面能开,但跳转、头像和附件异常迁移脚本在错误的命令行环境下执行手工修回数据库并重建全部缓存
静态资源指向临时目录编辑器有按钮但没有图标生成缓存时缺少脚本路径和站点根目录补齐运行环境后重建样式和导航缓存
旧模板仍在复用修改后浏览器继续显示旧样式模板缓存没有清理,资源地址没有版本变化清理编译模板并重新生成页面

迁移脚本因此被标记为不能盲跑。重复执行不能写坏生产值,站点 URL 改完后也不能只刷新应用缓存。

6. 切换时的顺序问题

正式切换改了论坛域名、WWW 域名和 WebSocket 入口的 A、AAAA 记录。
Cloudflare 记录都是代理模式,客户端连接的是 CDN,源站变更不需要等待本地 DNS 缓存。

第一次切换使用新实例的动态公网 IP。随后又把旧实例的静态 IP 迁到新实例,A 记录再做了一次更新。

这个顺序不理想。静态 IP 应该在第一次 DNS 切换前整理好,避免多一次源站地址变化。
对代理模式来说,第二次切换不一定造成长时间中断,但会增加验证项,也容易让配置和文档出现两个版本的 IP。

静态 IP 迁走后,Clash 的 IPv4、IPv6 节点和控制面板配置都要同步。只改 DNS 不够。代理节点如果还记着旧地址,网站恢复了,代理仍然不可用。

旧实例最后被删除,保留一份快照作为回滚点。快照恢复出来的是新实例,不能直接覆盖当前机器,所以回滚还要配合静态 IP 和 DNS。

7. 页面和登录问题

页面问题看起来零散,原因大多相同:旧数据和新缓存没有对齐。

问题现象根因处理
头像路径不一致头像入口返回后再报文件不存在文件在 UCenter 目录,旧路径指向空目录建立兼容链接,删除失效的旧域名重写
认证图标路径重复图片地址里出现两段附件前缀数据库保存完整路径,缓存又拼一次统一为相对路径并重建设置缓存
签到插件显示占位符页面出现未解析的语言标记插件缺少语言文件补充语言文件并刷新语言缓存
favicon 仍是默认图标浏览器标签显示框架默认图根目录图标没有迁移替换为旧站图标并加版本参数
编辑器图标全部消失工具栏只有文字或空白样式缓存把静态前缀写进临时目录修正缓存环境后重建样式

页面和登录问题处理完后,又专门用真实表单验证了登录和需要验证码的失败场景。
只看登录接口返回值不够,跳转、Cookie 和验证码状态必须一起检查。

8. 上线后的流量故障

DNS 切换完成后,源站很快被打满。

访问日志里大量请求集中在论坛帖子、个人中心、搜索和插件接口。
请求来自很多不同地址,单个 IP 的请求并不多。单 IP 限流能挡住一部分异常客户端,但挡不住这种分布式抓取。

8.1 流量特征

三万多条日志里,论坛动态页占了大头,个人中心、搜索和插件接口也持续被访问。最高频的单个地址只有十几次请求,绝大多数地址只出现一两次。

这不是一个脚本在固定频率抓取,而是大量地址分散访问。只按单 IP 限速,命中率很低。必须同时限制异常路径和全体匿名流量。

8.2 Nginx、HAProxy 和 Cloudflare

问题表现原因处理
AJAX 登录 502登录请求短暂失败FastCGI 响应头缓冲不足增大缓冲
Nginx fd 太小报打开文件过多进程上限远低于 worker 连接数提到 65535
conntrack 打满SSH 重置,Cloudflare 504连接跟踪上限只有 7680提到 262144 并缩短超时
PHP worker 占满页面 502 和 50412 个 worker 无法承受突发流量临时提到 32,并加 10 秒硬超时
机器人持续抓取大量 502、499 和 444分布式地址访问论坛、私信和搜索接口Nginx 路径拦截、限流和 Cloudflare 验证

CPU 使用率看起来不高,并不代表机器有余量。大量时间花在等待调度和超时上,Cloudflare 侧持续出现网关超时,SSH 也会间歇性断开。

第一轮处理提高了文件描述符、连接跟踪和 PHP worker 上限,同时增加 PHP 请求硬超时。
源站能短暂恢复,但机器人流量继续进入后,实例的 CPU credits 仍然会被耗尽。

8.3 Nginx 管路径,HAProxy 管连接

第二轮处理把拦截放到 Nginx 的 HTTP 层。
未登录访问回复页、私信页、用户中心动作、热搜入口和 RSS 时,直接断开连接。
静态资源不限流,PHP 请求增加单 IP 和全局限流。

层能看到什么适合做什么局限
HAProxyIP、端口和 TLS SNI连接数、TCP 转发和 SNI 路由TCP 模式看不到 URI 和 HTTP 头
NginxURI、参数、真实 IP 和 Cookie 字符串路径过滤、单 IP 限流和请求超时不能验证 Discuz 的登录 Cookie
Cloudflare边缘请求和通行状态人机验证、WAF 和全局拦截需要单独配置规则

这里也验证了一个判断:Nginx 能看完整 URI、参数和 Cookie,适合做业务路径过滤。
HAProxy 在这套链路里是 TCP 透传,只能看 IP 和 TLS SNI,看不到 URI。
路径规则放在 Nginx 更准确,也不会破坏 Reality 的透传。

Cloudflare 会把真实访客地址放进请求头。Nginx 据此恢复真实 IP,访问日志和单 IP 限流都能拿到原始地址。
HAProxy 只转发字节流,不解析 HTTP,所以它看不到这个头,也不需要为了路径过滤去拆 TLS。

登录 Cookie 不能只判断是否存在。Nginx 能看到 Cookie 字符串,却不能验证签名、过期时间和会话状态。
机器人带一个伪造值也能绕过简单的字符串判断。紧急阶段因此暂停了 Cookie 绕行,所有动态请求先共用低速率。

8.4 Cloudflare 才是最后的刹车

但仅靠源站限流仍然不够。机器人来自大量不同 IP,源站必须立刻把请求挡在 CDN 边缘。

Cloudflare 里原本已经有一条人机验证规则,只是一直处于关闭状态。
启用 Managed Challenge 后,浏览器先进入安全验证,源站请求量迅速下降,PHP worker 回到空闲,CPU steal 也降了下来。

启用前,源站请求量仍在持续增长。
启用后,正常浏览器先完成一次安全验证,后续访问使用 Cloudflare 的通行状态。
机器人没有通行状态,请求在边缘被拦下。

为了避免 CPU credits 继续被消耗,PHP 请求还临时保持了极低的全局速率。
这个阶段部分访客会看到 429。它不是最终状态,只是给实例恢复 CPU 配额留时间。

9. 这次迁移得到什么

新实例已经承载正式流量,静态 IP、DNS、Clash 和控制面板配置全部对齐。
旧实例已经删除,快照暂时保留。Cloudflare 人机验证仍在运行,源站处于保护模式。

迁移过程里最有效的几条做法是:

  • 生产数据先离线升级,不直接在线上做不可逆修改。
  • 新旧实例并行,切换前用正式域名验证新站。
  • 静态 IP、DNS、代理配置和站点配置一起验收。
  • 迁移脚本必须可重复执行,不能把空值或测试域名写回生产。
  • 站点地址变化后,代码、数据库和各类缓存要一起刷新。
  • 面向公网的论坛要在切流前准备机器人防护,不能等源站被打挂再补。
  • 小型实例要观察 CPU credits、连接跟踪和文件描述符,不只看 CPU 百分比。

迁移还没有完全收尾。Cloudflare 的人机验证需要继续观察,Nginx 的临时低速率要在 CPU 配额稳定后恢复。
源站的 80 和 443 长期只应允许 Cloudflare 官方地址访问,避免机器人绕过 CDN 直连。

旧实例快照也不会长期保留。稳定观察一段时间后,再决定是否删除。