8.6 KiB
MySQL 与首页请求保护修复记录
17:42 更新:此前 Nginx 每 IP 和首页总量频率限制已撤回;目前只针对已确认的异常请求直接关闭连接,另有 84 个异常来源临时拦截 15 分钟。数据库索引与 PHP 缓存保留。当前访问与到期边界详见 网络拥塞处理记录。下文 15:32 的限速测试为历史记录,不代表现配置。
服务器:47.106.181.28。完成时间:2026-08-31 15:32,北京时间。
已直接部署到生产环境。修复范围为 www.txiaw.com 的首页请求保护、首页文章列表缓存,以及共享数据库 xiaxia.gxl_news 的查询索引。
已完成的修改
1. 数据库索引
已执行:
SET SESSION lock_wait_timeout = 5;
ALTER TABLE xiaxia.gxl_news
ADD INDEX idx_news_status_addtime (news_status, news_addtime),
ALGORITHM=INPLACE,
LOCK=NONE;
- 执行前检查了现有索引,没有发现前两列为
(news_status, news_addtime)的适用索引。 - 保留原有分类列表等索引,没有删除索引或修改业务记录。
- 先用一致性快照备份目标表,校验 gzip 完整性后才执行 ALTER。
- 在线创建耗时约 0.49 秒;明确指定并发读写及原地算法,避免自动回退到需要复制表的操作。
- 新执行计划使用
idx_news_status_addtime,不再出现Using filesort。 - 关闭 MySQL 查询缓存后连续测试三次,均返回 100 条,包含客户端启动的耗时分别约 4.7、4.3、4.0 毫秒。
- 单次查询会话计数:
Handler_read_key=1、Handler_read_prev=99、Sort_rows=0、Sort_scan=0。
原故障负载下同一 SQL 的慢日志样本平均为 6.479 秒,每次检查 72,352 行;前后测试处于不同流量条件,不能把全部耗时改善单独归因于索引。
2. PHP 入口保护与 Nginx 首页限速
新增 PHP 文件:
/www/wwwroot/www.txiaw.com/Lib/HomeRequestProtection.php
由网站 index.php 在加载 ThinkPHP 之前引入。对 / 或 /index.php 的 GET/HEAD 请求,若同时带 r 参数且 User-Agent 为空,则返回 429;PHP 响应附带 Retry-After: 60。不依靠客户端提交的 IP 地址,也不读取数据库。
此前 Nginx 同步在请求进入 PHP 之前拦截上述异常特征,并启用了以下首页频率限制(17:18 已撤回):
- 每个来源 IP:平均 5 次/秒,允许 20 次突发。
- 该站首页总量:平均 50 次/秒,允许 100 次突发。
- 超出限制返回 429。
- 限速键使用原始请求路径;普通
/down/...等文章路径、静态资源和 POST 请求不在这些首页限速键的范围内。 - 限速配置只被
www.txiaw.com/txiaw.com这个虚拟主机引用,不对其他站点启用。
变更文件:
/www/wwwroot/www.txiaw.com/index.php
/www/server/panel/vhost/nginx/www.txiaw.com.conf
/www/server/panel/vhost/nginx/txiaw-home-protection-http.inc
/www/server/panel/vhost/nginx/txiaw-home-protection-server.inc
首次部署时 Nginx 修改前后均通过 nginx -t,只做了一次平滑重新加载。17:18 撤回频率限制、17:36 启用精确连接关闭规则时也分别检查配置并平滑重载。未重启 MySQL、PHP-FPM 或服务器,未终止业务查询。
3. PHP 首页文章列表缓存
新增:
/www/wwwroot/www.txiaw.com/Lib/HomeNewsCache.php
修改首页模板:
/www/wwwroot/www.txiaw.com/Tpl/icp/gxl_index.html
该列表仍调用原来的文章查询函数、返回原来的 100 条数据,但增加:
- 固定缓存键,URL 的随机参数不参与缓存键。
- 60 秒有效期。
- 文件锁避免正常并发刷新时重复查询。
- 正在刷新时可使用最近 300 秒内的旧值,降低刷新期间的请求排队。
- 冷缓存等待最多约 200 毫秒;缓存设施故障时回退原查询,避免因缓存文件故障直接让页面不可用。新索引和异常特征拦截继续保留,目前不依赖首页频率限制。
- 同目录临时文件加重命名写入,避免读到半份 JSON。
实际缓存位于:
/www/wwwroot/www.txiaw.com/Runtime/TxiawHomeNews/news-list-v1.json
只备份并清理了命中这条旧首页查询的 1 个编译模板缓存文件,没有清空全站 Runtime、会话或其他业务数据。
验证结果
以下为首次修复的历史验证,当前网络验证见上方更新记录。
已通过以下验证:
- PHP 5.6 语法检查。
- 11 个请求保护边界用例,包括正常首页、浏览器请求、文章路径、POST 和证书验证路径。
- 缓存命中、过期刷新、持锁时读取旧值、模拟数据库异常回退、损坏缓存恢复。
- 8 个同时到达的冷缓存请求取得相同数据,数据加载函数只执行 1 次。
- 首页加入随机参数仍复用同一个新鲜缓存,实际缓存包含 100 条数据。
- 30 次、最多 4 路并发的短突发 HTTP 检查:24 次 200、6 次 429;等待 5 秒后首页恢复 200。
- 首页限速期间,同站文章页和
www.xxiaw.com首页仍为 200。
实际页面检查:
| 请求 | 结果 |
|---|---|
无 User-Agent 的 www.txiaw.com/?r=... |
429 |
正常 www.txiaw.com/ |
200 |
www.txiaw.com/down/184778.html |
200 |
www.xxiaw.com/ |
200 |
m.bchongw.com/ |
301,保留站点原跳转行为 |
15:30 的数据库采样:
| 指标 | 故障期间 | 修复后采样 |
|---|---|---|
| Threads_running | 79 | 1 |
| Threads_connected | 80 | 7 |
| 慢查询增长 | 约 12.4 次/秒 | 5 秒采样内 0 次 |
| MySQL CPU | 约 136%(3 秒样本) | 约 18.33%(3 秒样本) |
| 系统 1 分钟负载 | 修改前约 86.31 | 15:32 约 1.51 |
这些是短时采样,不是长期服务承诺。5/15 分钟负载仍包含此前故障窗口,会滞后回落。
剩余压力与边界
异常请求尚未停止:验证时最近 3,000 条、以及后续最近 1,000 条该站访问记录均返回 429,说明仍在持续拦截。15:32 的两秒整机 CPU 样本空闲约 15%、I/O 等待约 1%;入口处理仍消耗资源。此次已经消除已定位的 MySQL 排序热点并限制首页流量,但没有部署云端 WAF/CDN,也未宣称能够抵御任意规模或任意路径的攻击。
后续可在云侧增加 CC 防护,减轻源站的连接与 TLS 处理压力。若站点以后接入代理/CDN,需要同步核对可信真实 IP 配置和合法流量,再调整当前每 IP 阈值。
备份与回滚
服务器备份目录(权限 0700):
/www/backup/txiaw-mysql-fix-20260831/
主要文件:
originals/original-index.php:原 PHP 入口。originals/original-www.txiaw.com.conf:原 Nginx 站点配置。originals/original-gxl_index.html:原首页模板。compiled-home-before/:本次清理的首页编译模板备份。gxl_news-before.sql.gz:完整目标表一致性快照,压缩后 15,245,809 字节,已做 gzip 完整性校验。gxl_news-schema-before.txt:修改前表结构和索引。index-result.json、cache-result.json、verification-result.json、final-summary.json:实际执行与验证记录。stage/:此次部署的源码与测试脚本。
回滚时先核对文件是否又被别人修改,避免覆盖后续变更。建议按所需范围单独回滚:
- 仅撤回 PHP 缓存:恢复原首页模板,保留其当前属主和权限;只重新生成该首页的编译模板缓存。缓存辅助 PHP 文件可先保留,避免在途请求的已编译模板调用失效。无需导入数据库备份。
- 撤回请求保护:恢复原 PHP 入口和原 Nginx 站点配置,先执行
nginx -t,成功后再nginx -s reload。两个.inc文件可保留为未引用文件,不必急于删除。撤回后当前刷请求会再次进入 PHP,应先有替代保护。 - 确需撤回新索引:确认没有其他新依赖后,使用短元数据锁等待时间,单独删除
idx_news_status_addtime;不需要删除旧索引或恢复整张表。
不要为了撤销索引而直接导入整表备份,这会有覆盖修复后新增业务数据的风险。
参考资料
- Nginx 请求速率限制:请求键、突发限制及拒绝状态码。
- MySQL InnoDB 在线 DDL:
ALGORITHM/LOCK约束的用途;本次同时以服务器实际 MySQL 5.6.50 的执行结果验证支持情况。 - PHP flock:非阻塞文件锁。
本记录、部署脚本和本地副本均未保存登录密码或数据库密码。