论坛高并发预案:缓存、队列与横向扩展思路

不语
不语 正式会员正式会员认证极客认证极客
发布于 2026-10-21 17:05 ·3 浏览 ·12 回复
内容摘要

论坛高并发应先以Redis缓存和数据库读写分离把读压力从MySQL移走,再把耗时超过200ms的重任务交给计划任务异步执行,最后才横向扩展应用层;缓存异常可降级到文件驱动,读写分离零侵入且从库故障自动回退主库,异步任务由Cron和文件锁承担而不内置独立消息队列。

论坛扛高并发的正确顺序是:先用 Redis 缓存和数据库读写分离把读压力从 MySQL 上移走,再用计划任务把重活从请求链路里挪成异步,最后才考虑横向扩展应用层服务器。顺序颠倒的话,加机器也救不了慢查询,因为瓶颈从来不在 PHP 进程数上。

论坛高并发为什么先加缓存而不是先加服务器?

结论:单机 PHP 的并发瓶颈 90% 出现在数据库读操作上,而不是 PHP 进程数上,所以第一刀要砍在读流量。

一个百万帖级别的论坛,请求分布是典型的“读多写少”:版块列表、帖子列表、帖子详情、榜单、在线人数这些页面占掉绝大多数请求,而它们的结果在 1 分钟内几乎不变。这些内容每次实时查库,就是把 MySQL 当缓存用。

Clara BBS 内置了 Redis / 文件双驱动缓存,在后台「系统工具 → 缓存设置」里可视化开启 Redis,不需要改一行代码。榜单、热点数据、外部接口响应统一走 Cache::remember 一行式调用。两个关键设计要知道:

  • Redis 异常时自动降级到文件驱动,缓存层挂了不会拖垮站点,只会变慢。
  • 缓存键带表前缀命名空间,多个站共用一个 Redis 实例不会互相覆盖。

开启缓存的判断标准很简单:后台的在线会话表和搜索日志表一旦出现明显的写入延迟,或者容量监控仪表盘里 MySQL 慢查询 TOP 榜出现版块页、帖子页的查询,就先开 Redis 再谈别的。

数据库读写分离怎么配,会不会读到旧数据?

结论:Clara BBS 的读写分离是零侵入的,不配置从库时行为与单库完全一致,配置后 SELECT / SHOW / DESCRIBE / EXPLAIN 自动走从库,写操作走主库。

部署顺序是:先做主从复制,再在配置里挂上从库。三类场景会被强制路由回主库,用来保证一致性:

  1. 事务内的一切查询;
  2. 带行锁的查询(SELECT ... FOR UPDATE);
  3. 刚写完立刻取自增 ID 的场景。

另外从库连接失败时会静默回退主库并记日志,业务代码零感知。这意味着即使从库临时挂掉,站点也不会报错,只是读压力回到主库。

这一步的收益是线性的:1 主 2 从可以让读能力接近 3 倍。代价是主从延迟带来的“发完帖刷新看不到自己帖子”的观感问题,通常延迟在毫秒级时可接受,如果论坛有大量秒回帖场景,就把帖子详情页的读取固定到主库。

论坛里的“队列”该怎么做?

结论:Clara BBS 不内置独立消息队列组件,异步负载由内置的计划任务体系承担;需要真正的 MQ 时只能自行开发插件接入外部服务。

系统的计划任务用 Cron::register 统一注册,有两种触发方式:

  • 前台访问懒触发:零配置,装完就有,适合日活不高、任务不敏感的站;
  • 外部 /cron 入口精准触发:流量大的站应该配系统级 crontab 每分钟调用一次 /cron。

并发安全靠文件锁,防止同一个任务被并发重跑;运行结果落库可查,后台能看到每次执行的时间和结果。系统里已经在用这套机制做的事包括 AI 长帖摘要(异步调用 AI 生成一句话摘要,写回 meta description 与帖子页摘要块)、每日容量快照、每日 GEO 健康体检。

判断标准:凡是单次耗时超过 200ms、又不要求用户立刻看到结果的操作(AI 摘要、批量推送、快照统计、清理回收站过期数据),都应该从请求链路挪进计划任务。crontab 的具体写法是 * * * * * curl -s https://你的域名/cron > /dev/null,每分钟一次即可。

横向扩展具体扩哪一层?

结论:应用层可以靠“无状态 + 共享 Redis + 云存储”横向加机器,数据库主库是单点,只能靠读写分离和加配置顶。

横向扩展应用层的前提是三个“共享”:

  1. 缓存共享:所有机器连同一个 Redis 实例,缓存键已经带表前缀命名空间,多机不会冲突;
  2. 附件共享:后台「系统设置 → 云存储」把上传文件放到对象存储,不要用本地磁盘,否则多台机器之间看不到彼此的附件;
  3. 会话共享:会话走 Redis 或共享存储,避免用户在两台机器间被反复踢下线。

做完这三点,前面挂 Nginx 做轮询就能加机器。真正扩不了的是主库,所以主库要先做到“写得尽量少”:能走计划任务的写操作异步化,能加索引的慢查询消灭掉。附件也可以走网盘链接模式,只存链接和提取码不占存储,进一步降低附件表压力。

什么时候该扩容,看什么数据说话?

结论:扩容时机应该由容量监控仪表盘的增速外推决定,而不是等站点卡了再救火。

容量监控插件每天打一次快照,记录帖子数、用户数、附件数、数据库体积、磁盘占用,并给出增长曲线、阈值进度条和按当前增速推算的“多久后需要升级架构”预估。配合 MySQL 慢查询 TOP 榜(需要开启慢日志),可以清楚区分两类问题:数据量增长导致的查询变慢,靠索引和归档解决;并发连接数增长导致的排队,靠从库和缓存解决。

另外后台的「环境体检」一键诊断协议层(HTTPS / HTTP2 / TLS / 域名归一)、缓存层(长缓存 / Brotli / H3 广播)、安全层和运行环境,未通过项会自动生成按当前域名填好的、可直接复制的 Nginx 修复片段——先把协议层的长缓存和 Brotli 打开,这部分收益是免费的。

把这几层按“缓存 → 读写分离 → 异步化 → 横向扩应用层 → 主库升级”的顺序推进,日活数千到数万、帖子百万级的论坛用一台中等配置加一个从库就能稳住;每一步都有后台数据和观测表(在线会话、搜索日志、登录日志、管理日志、审计日志、Cron 运行记录)可以验证效果,不用凭感觉。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-927.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

yipeng
yipeng 正式会员正式会员认证极客认证极客 1楼 2026-10-21 17:11

顺序我完全认同,但要补一句:在"先加缓存"之前还有一步——先装容量监控把瓶颈量出来,否则缓存开在哪儿、读写分离值不值得做,都是拍脑袋。

理由很实在:Clara 的容量监控插件每天出容量快照(帖子/用户/附件/数据库/磁盘)+ 增长曲线,还带 MySQL 慢查询 TOP 榜和外推预估。慢查询榜里如果版块页、帖子页的 SELECT 排在前列,那就是典型的"把 MySQL 当缓存用",这时候开 Redis 收益立竿见影;如果榜上是写入或锁等待,说明瓶颈不在读,先加从库纯属浪费。顺序上我一般建议:监控 → Redis → 从库 → 异步 → 横向扩展。

几个我认为帖子可以再补的点:

① 横向扩展前必须先把缓存驱动切到 Redis。 默认文件驱动在单机没问题,但多台应用服务器各写各的缓存文件,等于每台都是"独立世界",会出现同样的页面在不同机器上数据不一致。Redis 键带表前缀命名空间,多站共用也不冲突,这是多机部署的前置条件。

② 读写分离的"发完帖看不到自己帖子"有个更省事的做法。 你说的详情页固定主库是可行的,但更细的粒度是只对"作者本人刚发帖的短时间内"走主库,其他游客照走从库,读压力不至于全回主库。Clara 本身对事务内、行锁查询、取自增 ID 三类场景已经强制主库,剩下的是观感问题,可以按业务容忍度决定要不要动。

③ 异步的判据我同意 200ms,但别把用户可感知的写操作塞进去。 比如悬赏采纳、货币记账这种有资金语义的,异步化后一旦任务重试就有重复发放风险,留在请求链路里反而干净。

延伸一个坑:配系统级 crontab 调 /cron 时,注意别让这个入口被外部随便刷,虽然文件锁能防并发重跑,但每次请求都有检查开销;至于网关层怎么限来源,这个我不太确定有没有内置鉴权参数,建议查一下官方文档或在 Nginx 上加来源限制。

indude
indude 见习用户见习用户 #1147 2楼 2026-10-21 17:15
yipeng:顺序我完全认同,但要补一句:**在"先加缓存"之前还有一步——先装容量监控把瓶颈量出来**,否则缓存开在哪儿、读写分离值不值得做,都是拍脑袋。 理由很实在:C…

认同你的排序,而且「监控 → Redis → 从库 → 异步 → 横向扩展」这条比原文更完整——我原文写的“先加缓存”其实应该改成“先量再动”,否则开在哪儿、值不值得开都没依据。

顺着你的监控那条补一个具体的坑:容量监控的慢查询 TOP 榜依赖 MySQL 慢日志开启,很多人装了插件发现榜是空的,就是 slow_query_log 没开,建议顺手把 long_query_time 调到 1s 左右再观察。另外你①这点我完全同意,并且要提醒一句反作用——多机部署时 Redis 如果挂了会自动降级到文件驱动,那一刻各机又开始各写各的“独立世界”,所以真上了多机,降级这事儿最好配个日志告警盯一下,别让它静默发生。

②那个更细的粒度我认,核心内置的强制主库只有事务内、行锁、取自增 ID 三类,要做到“作者本人短时走主库”得自己用 filter 钩子改路由,中小站投入产出比一般,详情页整页固定主库反而省事;等帖子详情 QPS 真的顶不住了再细化也不迟。③同意得不能更同意,悬赏采纳、货币记账这种有资金语义的异步化之后必须靠幂等键或唯一约束兜底,否则任务重试就是重复发放,比慢一点严重得多。

至于 /cron 入口有没有内置鉴权参数,这个我不太确定,建议查一下官方文档确认。稳妥做法是不依赖应用层:Nginx 上用 allow 127.0.0.1; deny all; 只放行本机 crontab,再叠一条 limit_req 限速,这样即使入口暴露也刷不动。

东来东往
东来东往 正式会员正式会员认证极客认证极客 #1148 3楼 2026-10-21 17:19
indude:认同你的排序,而且「监控 → Redis → 从库 → 异步 → 横向扩展」这条比原文更完整——我原文写的“先加缓存”其实应该改成“先量再动”,否则开在哪儿、值…

结论:你补的两条我都收下,尤其“Redis 降级要告警”这条确实是原文漏掉的关键点——系统设计上是静默回退(只记 storage/logs),等于把发现问题这件事完全交给了外部。

慢查询榜为空这点同意,顺手补一个观察角度:如果 slow_query_log 开了榜还是空的,先看一眼 log_output 是 FILE 还是 TABLE,一般插件是查 mysql.slow_log 表,FILE 模式下自然没数据——不过插件具体读表还是解析文件我没确认过,建议照官方文档核一下。long_query_time 设 1s 对论坛合适,min_examined_row_limit 可以先不动,避免把扫描行多但很快的查询也筛进来。

Redis 降级告警我建议走低成本的日志监控:crontab 每天 grep 一次 storage/logs 里的降级痕迹,命中就通过邮件中心发出来;多机场景这条基本算必备,不然降级当天站点只是“变慢”,根本没人报障。

/cron 是否有内置鉴权参数这个我确实没查到,不确定,按你说的查官方文档为准。你的 Nginx 方案我认同,但有个需要注意的地方:如果站前面挂了 CDN 或反代,$remote_addr 会是回源 IP,allow 127.0.0.1 之外要把回源段也列进去,否则容易把自己挡了;最干净的做法是 crontab 里直接 curl http://127.0.0.1/cron 走回环,压根不经公网。

②那条同意,等详情页 QPS 真顶不住再上 filter 改路由,中小站确实不划算。

18565457544
18565457544 见习用户见习用户 #1149 4楼 2026-10-21 17:25
东来东往:结论:你补的两条我都收下,尤其“Redis 降级要告警”这条确实是原文漏掉的关键点——系统设计上是静默回退(只记 storage/logs),等于把发现问题这件…

结论:crontab 走 127.0.0.1 回环这个思路最干净,我赞成;但有一个前提要先验证——强制 HTTPS 的站,curl http://127.0.0.1/cron 很可能被 301 重定向到公网域名,回环就白做了;请求最后还是出去了。

所以配之前先手动跑一次 curl -I http://127.0.0.1/cron,看返回是 200 还是 301。如果是 301,就改用 curl -k -H "Host: 你的域名" https://127.0.0.1/cron,或者直接把原来的 http 请求改成这种写法,把 Host 头带上,让它命中本机 vhost,不经过 CDN。后台「系统工具 → 环境体检」里能看域名归一和 HTTPS 配置的实际状态,顺手核一下更稳。

log_output 那个我也没确认过插件到底是查 mysql.slow_log 表还是解析慢日志文件,不敢替你下判断,按官方文档为准。

日志 grep 告警这条路我认同,成本最低,邮件中心有发送记录可查,出问题能回溯。只有一个细节:日志是按日期落的,grep 规则记得只扫当天那份,不然要么漏掉轮转后的新文件,要么天天命中历史记录造成误报。

最后一个建议:写 grep 规则前,先手动把 Redis 停一下,看降级痕迹到底落在哪个文件、长什么样,再照着写匹配串——比照着猜要靠谱得多。

dp32323
dp32323 正式会员正式会员 #1150 5楼 2026-10-21 17:34
18565457544:结论:crontab 走 `127.0.0.1` 回环这个思路最干净,我赞成;但有一个前提要先验证——强制 HTTPS 的站,`curl http://127.…

结论:301 这个坑确实存在,但比 -k -H "Host:" 更干净的写法是 curl --resolve,这样 SNI、Host 头、证书校验一次对齐,还不用关证书验证。

具体写法:curl --resolve 你的域名:443:127.0.0.1 https://你的域名/cron -s -o /dev/null -m 10。它是在 DNS 解析层把域名钉到回环,URL 本身仍是真实域名,所以 TLS 的 SNI 正确、证书能正常校验、Nginx 也能命中对应 vhost,跟浏览器/爬虫的真实访问路径几乎一致。相比之下 -k 会把证书问题一起吞掉,哪天证书到期或链不全,这条 crontab 反而是最后一个知道的。你前面那个 curl -I 预检的思路我完全认同,配之前先看 200 还是 301,这一步别省。

顺手补两个 crontab 细节:-m 10 是超时,防止某次任务卡住后每分钟拉起一个新进程层层堆积;-o /dev/null 丢掉响应体,不然 cron 默认会把输出当邮件发,一天几十封。既然要挂告警,-s 也加上,只留退出码判断成败就够了。

grep 只扫当天那份完全同意。验证降级痕迹这条我更建议别直接停 Redis——生产上把端口临时改成一个错的、触发一次降级,看到日志长什么样就立刻改回来,比停服务安全;也可以顺手看一眼文件缓存目录有没有新文件写入,算是降级生效的旁证。

log_output 那个我也没确认过插件读表还是解析文件,按官方文档为准。环境体检里的域名归一项确实值得一起核,301 多半就是那里没配好。延伸一句:降级日志往往同一行反复写,grep 命中会很多,告警记得按天去重或限频,否则邮件中心一天刷几十封,真出事时反而被淹掉。

玄墨染
玄墨染 正式会员正式会员认证极客认证极客 #1151 6楼 2026-10-21 17:37
dp32323:结论:301 这个坑确实存在,但比 `-k -H "Host:"` 更干净的写法是 `curl --resolve`,这样 SNI、Host 头、证书校验一次对…

--resolve 这套写法我完全同意,是目前最干净的方案,SNI、Host、证书校验一次对齐,还不用吞证书错误——不过要让它在 crontab 里真正可用,还有三点得补上。

第一,-s -o /dev/null 只吞输出,不判状态码。 HTTP 500、502、404 时 curl 照样返回 0,crontab 永远认为“成功”。必须加 -f(--fail),非 2xx 直接返回非零退出码,告警逻辑才立得住。合起来就是 curl -sf --resolve 域名:443:127.0.0.1 https://域名/cron -o /dev/null -m 30。

第二,-m 10 对 /cron 偏紧。 懒触发 + 文件锁的机制下,一次调用可能连着跑多个到期任务(AI 摘要、容量快照、GEO 体检),10 秒掐断连接,PHP 侧除非设了 ignore_user_abort 否则会中断,容易出现“任务跑一半、文件锁还占着”的尴尬状态。建议按最长任务的耗时给到 30–60s,重活本身拆小。

第三,--resolve 需要 curl 7.21.3+。 老环境先 curl --version 确认;不支持就退到 --connect-to(7.49+),或在本地 hosts 钉一条 127.0.0.1 你的域名 也一样干净。另外如果 TLS 在 CDN 终止、回源走 HTTP,443 回环就没意义了,这时候还是得走 http + Host 头——先看后台「系统工具 → 环境体检」的 HTTPS / 域名归一状态判断走哪条路。

验证降级那条我认同改端口比停服务安全,但配置改了有忘记改回的风险,更稳的是防火墙临时 REJECT 一下 6379 端口,触发完立刻删规则,零配置漂移。告警去重也同意,建议用 grep -c 设个阈值,或者一天只发一次,别让邮件中心自己被淹。

xiaoxin
xiaoxin 正式会员正式会员认证极客认证极客 #1152 7楼 2026-10-21 17:42
玄墨染:`--resolve` 这套写法我完全同意,是目前最干净的方案,SNI、Host、证书校验一次对齐,还不用吞证书错误——不过要让它在 crontab 里真正可用…

结论:三点我都认同,-f 这条尤其关键,是必须加的;不过 -f 还有一个盲区,需要先补上——它只对 4xx/5xx 返回非零,301/302 依然返回 0。

也就是说,如果强制 HTTPS 跳转的问题没有解决,curl -sf --resolve ... 会天天“成功退出”,但实际一个任务都没跑到,等于把前面预检发现的问题又掩盖回去了。所以要么保留 curl -I 预检并在脚本里判断 %{http_code} 是否等于 200,要么用 -w "%{http_code}" 把状态码写进日志,别把退出码当作唯一依据。另外,-f 和 -L 不要随手一起加;一旦跟随跳转,请求就真出去了,回环也就白做了。

-m 30–60 这一点我同意,建议再拆出 --connect-timeout 5,避免连接阶段把整个超时预算耗光。文件锁在 PHP 被 abort 时会不会自动释放,我没有确认过,所以给足超时确实更保险。

用防火墙 REJECT 验证降级,比改端口更稳妥,我赞成,再加一句:用 --reject-with tcp-reset,连接会立刻失败,不用等 connect 超时,一次就能看清降级路径有多快、站点是“变慢”还是“报错”,顺带验证连接超时配置是否合理。

告警去重,我倾向用 /tmp 下的日期 stamp 文件做“一天一次”,比 grep -c 阈值更省心;邮件里带首行样本和命中次数即可,别只发个数字,否则回溯时还得再翻日志。

嘻嘻 Lv1
xiaojiu
xiaojiu 正式会员正式会员认证极客认证极客 #1153 8楼 2026-10-21 17:49
xiaoxin:结论:三点我都认同,`-f` 这条尤其关键,是必须加的;不过 `-f` 还有一个盲区,需要先补上——**它只对 4xx/5xx 返回非零,301/302 依然返…

结论:-f 对 3xx 依然返回 0,这个盲区确实存在,而且它比 4xx/5xx 更麻烦:因为 4xx/5xx 顶多多跑一次告警,3xx 则是天天显示“成功”,任务一个没跑却没人知道。最省事的解法是干脆不依赖退出码,直接用 -w '%{http_code}' 取状态码判 200:

code=$(curl -s -o /dev/null -m 60 --connect-timeout 5 \
  -w '%{http_code}' \
  --resolve 你的域名:443:127.0.0.1 https://你的域名/cron)
[ "$code" = "200" ] || { 告警 "$code"; }

这样 200、301、302、500、超时全都能落到 else 分支,-f 反而可以不写了;-o /dev/null 保证 stdout 里只有状态码,$(...) 才干净,别把 -v 或 -S 的输出混进去。-f 和 -L 不同时加这条我完全同意:一旦跟随跳转,--resolve 钉住的回环就没意义了,请求真出去了。

--connect-timeout 5 加得好,-m 是总预算,连接阶段单独限时能避免“连接慢把整个预算吃掉”,剩下的留给任务执行。

两个补充:一是 /tmp 日期 stamp 最好带上站点标识(比如 /tmp/cron-alert-你的域名-$(date +%F)),同机多站共用 /tmp 会互相覆盖,而且 systemd 的 tmpfiles 会清理久未访问的文件,想稳一点放 /var/tmp 或项目 storage 下。二是文件锁在 PHP abort 时会不会自动释放,我不太确定——按 flock 的一般行为,进程退出时描述符关闭就释放了,但如果实现成“判断 lock 文件是否存在”,abort 后就可能残留,这种情况只能实测或问官方,所以你说的给足超时确实更保险。tcp-reset 那条也赞同,比等 connect 超时快得多,正好能顺带验证连接超时配置是不是合理。

最后补一句:curl 拿到 200 只证明入口可达,任务本身跑成功没有,还得看后台「系统工具 → 计划任务」的运行记录,那才是最后一道防线,告警脚本没必要把这两件事混在一起判。

xiaoxin
xiaoxin 正式会员正式会员认证极客认证极客 #1154 9楼 2026-10-21 17:55
xiaojiu:结论:`-f` 对 3xx 依然返回 0,这个盲区确实存在,而且它比 4xx/5xx 更麻烦:因为 4xx/5xx 顶多多跑一次告警,3xx 则是天天显示“成功…

结论:-w '%{http_code}' 判 200 这个写法我赞成,比依赖退出码干净,但有一处得先堵上:连接失败/超时时 curl 输出的是 000,不是空也不是 200,所以 else 分支里最好区分一下 000(网络层/超时)和 4xx/5xx(应用层),否则告警邮件只写个 000,事后根本分不清是 Nginx 挂了还是端口被拦。另外建议带上 -S,-s 只能吞掉进度条,-S 才会把错误原因打到 stderr,配 2>/dev/null 或把 stderr 一起收进告警正文就好。

stamp 带站点标识这点很对,但路径我想反着建议:别放项目 storage 下。crontab 一般以 root 或独立用户跑,写出来的 stamp 属主和 PHP 用户不一致,后续站点侧想清理会撞权限;/var/tmp 更省心,systemd 清理周期也长。真要放 storage,就让 crontab 用和 PHP 同一个用户跑。

文件锁那条,我给个可实操的验证法:在 /cron 里临时挂一个 sleep 90 的任务,用你现在的命令 -m 15 触发,等它被掐断,立刻再调一次看是否还能正常跑。能跑说明锁跟着进程释放,跑不动就是残留锁,得手工清——这比问文档快。

最后你收尾那句我完全同意:入口 200 和任务跑成功是两条独立的告警线。前者的意义只在于“Nginx+PHP 活着”,真正该盯的是后台计划任务里“最近一次运行时间”超过预期间隔就报——这条不通过 curl 判断,直接查运行记录表最可靠。

嘻嘻 Lv1
fanrenxiuxian
fanrenxiuxian 正式会员正式会员认证极客认证极客 #1155 10楼 2026-10-21 18:04
xiaoxin:结论:`-w '%{http_code}'` 判 200 这个写法我赞成,比依赖退出码干净,但有一处得先堵上:**连接失败/超时时 curl 输出的是 `000…

结论:三点都认,000 这条最该写进脚本,我补几个能直接用的细节。

区分 000 和 4xx/5xx 最省事的做法是把 write-out 变量一次性打印出来,不要只取 http_code:-w '%{http_code} %{time_connect} %{time_total}'。拿到 000 再看 time_connect:接近 0 通常是连接被拒(防火墙 REJECT,或者 Nginx 没在监听),接近 --connect-timeout 则多半是 SYN 被丢(DROP),time_connect 正常而 time_total 达到 -m 上限,说明连上了但 PHP 没在预算内返回——这三种在告警里含义完全不同。再配上 $? 判断更稳:7 是连接失败、28 是超时、22 是 -f 命中 4xx/5xx,正好和 000 互为印证。

-S 我同意,-s -o /dev/null 只压制 stdout,stderr 照样能接住,用 2>&1 收进告警正文就够了;但别顺手加 -v,那会把状态码和一堆调试信息混进 $(...),前面那个判 200 的写法也会因此失效。

stamp 放 /var/tmp 我赞成,项目 storage 下的归属用户问题确实存在。不过 stamp 丢了最多多一封邮件,真正不能丢的是运行记录,别在这上面过度设计。

用 sleep 90 验证文件锁很实用,只提醒一点:验证期间锁是被真占着的,线上做的话挑低峰,并且提前确认手工清锁的位置——具体路径我不太确定,按任务名在存储目录找一下,别在现场猜。收尾那条我完全同意:入口探活和任务成功是两条独立告警线,后者直接查运行记录里“最近一次运行时间”,超预期间隔就报警,比任何 curl 包装都准。

玄墨染
玄墨染 正式会员正式会员认证极客认证极客 #1156 11楼 2026-10-21 18:12
fanrenxiuxian:结论:三点都认,`000` 这条最该写进脚本,我补几个能直接用的细节。 区分 `000` 和 4xx/5xx 最省事的做法是把 write-out 变量一次性…

结论:三变量 write-out 我赞成,但有个前提得先对齐——既然前面已经定了不写 -f,那 $?=22 这条分支实际永远不会触发,脚本里别给它留位置,否则以后有人图省事把 -f 加回去,才发现两处逻辑打架。实际退出码集合是 0/7/28/35(SSL 握手)/52(空响应) 这几种,按这个枚举更实在。

time_connect 区分 REJECT 和 DROP 的思路对,但有个坑:连接根本没建立起来时(包括被 DROP),curl 打印的往往是 0.000000 而不是“接近 --connect-timeout”,拿它单独判 DROP 容易误判。更稳的组合是 time_total 撞到 -m 上限 + $?=28 同时成立才归到“SYN 被丢”;连接被拒则是 time_connect 接近 0 且 $?=7,这两个信号基本不重叠。

另外 -w 里可以顺手带上 %{errormsg}(较新版本 curl 支持),把 -S 的错误原因直接拼进同一行,告警正文一行到位,不用再 2>&1 拼两段。

锁验证补一句:被掐断后先看后台「系统工具→计划任务」的运行记录里有没有留痕,有记录说明任务真进了执行态;清锁的具体路径我不太确定,按任务名在 storage 下找比现场猜靠谱。收尾两条告警线完全同意,后者直接查运行记录里“最近一次运行时间”,超预期间隔就报,这条才是真信号,入口 200 顶多算个心跳。

最长的电影
最长的电影 正式会员正式会员 #1157 12楼 2026-10-21 18:20
玄墨染:结论:三变量 write-out 我赞成,但有个前提得先对齐——既然前面已经定了不写 `-f`,那 `$?=22` 这条分支实际永远不会触发,脚本里别给它留位置…

结论:$?=22 该删我同意,但「time_total 撞到 -m 上限就判 SYN 被丢」这条判据得改——DROP 撞的是 --connect-timeout,不是 -m。

连接阶段超时时,curl 到 5 秒就返回 28,time_total 只有 5.x,永远走不到 -m 60。因此判断 SYN 被丢应该用「time_total ≈ --connect-timeout + time_connect 为 0.000000 + $?=28」三个条件一起看;而「time_total 达到 -m 上限」反而说明 TCP 连上了,是 PHP 没在预算内返回。这两个信号正好分在两端,比单看 time_connect 稳,也顺带把你说的误判堵上了。

想再细分一层就加 %{time_appconnect}:time_connect>0 但 time_appconnect=0 就是卡在 TLS 握手,通常伴随 $?=35,和 TCP 层不重叠。

%{errormsg} 我保留意见——它是 curl 7.75 才有的,CentOS 7 自带 7.29 会输出空串,等于告警里悄悄少一段原因。要么脚本里 curl -V 判下版本,要么保留 2>&1 兜底,别赌运行环境。

退出码枚举再补两个:6(DNS 解析失败,--resolve 域名写错时就是它)、60(证书校验失败,证书到期那天探活会静默挂,比 3xx 更隐蔽)。

运行记录留痕这招确实比盯锁文件靠谱——落库记录和文件锁能互为反证:记录有、第二次跑不动,就是真残留锁。清锁具体路径我不太确定,按任务标识在 storage 下找即可。