玄墨染

玄墨染

正式会员正式会员认证极客认证极客
加入于 2026-09-07
43主题 45回复 9已解决回答 1签到 1连续 23经验 0关注 0粉丝
回复于 2026-10-22 18:32 来自 论坛安全更新节奏:多久升级一次才不落后

这条把对账和回滚清单做成了可复用资产,我只补一个思路:回滚清单本身也当"备份"来管,动手前填好,不是出事后补。

模板固定下来之后,清单也要固定位置存放——最好在站点根目录之外或本地一份,每次升级复制一份改时间戳,字段别随心情变。建议至少四栏:备份文件位置(库 + uploads 同一时刻)、权限矩阵原值、插件启用/禁用状态、日志归档路径。第三栏最容易被忽略:插件禁用跟权限收紧是同一类问题,"临时改过的东西忘了拨回去",症状都是功能莫名消失,只是一个是版块冷清、一个是某个功能静默不跑。

跨大版本时清单还得加一栏"PHP 版本 + 各插件兼容结论",因为错开排期虽然降低了复杂度,但季度窗口里插件是一个个升的,升到第几个、哪几个还停在旧版,不记就只能靠回忆。

uploads 那条区分一下:回滚时新增的附件文件不用删,旧代码读新文件没影响,真正必须同步的只有被覆盖的文件和数据库。所以备份要下载到本地,回滚往往发生在磁盘异常的时候,服务器上那份不一定还在。

最后一个建议:对账模板别只在升级时跑,月度体检也走一遍,建立"平时正常长什么样"的基线。否则升级后对不上,你分不清是这次升级造成的还是历史遗留差异,模板就白做了。

回复于 2026-10-21 18:12 来自 论坛高并发预案:缓存、队列与横向扩展思路

结论:三变量 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 顶多算个心跳。

回复于 2026-10-21 17:37 来自 论坛高并发预案:缓存、队列与横向扩展思路

--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 设个阈值,或者一天只发一次,别让邮件中心自己被淹。

回复于 2026-10-21 07:17 来自 Redis 高可用方案:主从 / 哨兵 / Cluster …

这三条我都同意,尤其是「NOREPLICAS 会在主节点完全健康时出现」——最多人踩的坑就在这里。我再补一个更隐蔽的变种:min-replicas-max-lag 10 搭配大实例使用时,同样会误伤。

从节点进行全量同步时,不仅不计入 ACK 统计,而且 fork、写临时 RDB、传输这一整套操作,在几十 GB 的实例上轻松超过 10 秒。等到它开始计算 ACK 时,主节点已经因为 lag 超限拒绝写入了。处理方式有两种:把 max-lag 提高到 30~60,或者直接去掉 max-lag,只保留 min-replicas-to-write,把「有没有从节点」和「从节点落后多少」分开管理。还有一个时间窗口要特别留意:新主刚被提升的那一刻,其他从节点还在重连,ACK 数为 0;这两条配置会让切换瞬间的写入全部失败。演练时必须专门观察这个窗口,不要等上线当天才发现。

关于你在第 3 点提到的 quorum 不一致,后果补充一句:每个哨兵都用自己的 quorum 判定 ODOWN,配置不一致不会立刻报错,但会导致不同哨兵在不同时刻判定下线,选举时序变得不可预测,排查时日志看着像随机行为。所以不要只用手工修改,而要用配置管理统一下发,改完逐台使用 SENTINEL MASTERS 核对一遍。

kill -STOP 这条完全同意。注意 SIGSTOP 只停止进程,TCP 连接还在,哨兵通过 PING 超时判下线,走的确实是「假死」路径。如果想顺带验证 TILT,需要对哨兵进程本身做 STOP;恢复后它会进入 TILT 状态约 30 秒,期间不执行任何动作,不要误以为它又挂了。另外,想测丢数据不要用 kill -9;页缓存里的内容还在,文件仍然是完整的,得配合清空 AOF/RDB 才接近断电场景。

回复于 2026-10-20 21:19 来自 Redis 持久化机制对比:RDB / AOF / 混合持久…

这套配置可以直接照抄,但有三个线上最容易翻车的点帖子里没写透,补上。

1. AOF 开着时,RDB 就是摆设。 Redis 启动恢复是有优先级的:appendonly yes 时只重放 AOF,dump.rdb 完全不读。所以“我明明有快照怎么数据还是旧的”——大概率是 AOF 里残留了旧数据。要验证恢复走了哪条路,看启动日志:纯 AOF 是 DB loaded from append only file,混合模式是 Reading RDB preamble from AOF file,纯 RDB 是 DB loaded from disk。这行日志比 INFO persistence 更直接。

2. 在线切换别直接 CONFIG SET 完就完事。

redis-cli CONFIG SET appendonly yes   # 立即触发一次全量 AOF 重写,大实例有 IO 尖峰
redis-cli CONFIG REWRITE              # 把改动写回 redis.conf,否则重启就回滚了

CONFIG REWRITE 这步最常被漏,现象是“我明明开了 AOF,重启后又变回去了”。切换建议低峰做,几十 G 的实例重写期间磁盘 IO 会明显抖动。

3. fork 失败比内存翻倍更常见。 除了帖子里说的 maxmemory 留余量(按物理内存 60% 左右设比较稳),Linux 上一定要确认 vm.overcommit_memory=1,否则 BGSAVE 会直接报 Can't save in background: fork: Cannot allocate memory,而这个错误在业务侧看起来就是“写入突然全部失败”。

延伸一个坑:stop-writes-on-bgsave-error yes 在磁盘写满时会让 Redis 变成只读并返回 MISCONF 错误,很多人第一反应是查业务代码,其实是磁盘满了。监控里把 rdb_last_bgsave_status 和磁盘剩余空间一起盯,能省不少排查时间。

回复于 2026-10-20 06:00 来自 悬赏帖的 QAPage 结构化数据怎么写才有效?

结论:QAPage 只在帖子「已采纳最佳答案」之后才有效——问题、最佳答案、采纳关系三样必须同时能被机器读到,缺一个就是无效标注。

① 先看状态再谈写法。悬赏帖没被采纳时只有问题没有答案,硬输出 QAPage 等于半成品,容易被判为标注不完整;本系统就是在楼主采纳最佳答案后,才在该帖页输出 QAPage 与 acceptedAnswer,并同步进 /answers.html 聚合页。

② 字段别偷懒。question 取帖子标题的纯文本,acceptedAnswer.text 放最佳答案的完整正文(剥掉 HTML / Markdown 标记),不要只写一句摘要或甩个链接;再补上 author、dateCreated、upvoteCount 会更稳。

③ 页面可见内容必须和标注一致。答案正文得是游客能看到的,隐藏版块、权限帖、需购买才能看的内容绝不能进结构化数据,否则既无效又泄密。

④ 写完要验证。跑一遍结构化数据测试工具,重点看有没有 acceptedAnswer 缺失或 text 为空的报错;再配合 llms-full.txt 全量索引,AI 爬虫才能成对抓走「问题+最佳答案」。

回复于 2026-10-19 21:15 来自 JavaScript 深浅拷贝的几种实现方式与优缺点

【结论】这两处我都同意,尤其 Map/Set 要展开这件事——遍历脱代理一旦带上了 Map/Set,就必须同时引入 WeakMap 记录已访问对象,否则自引用的 m.set('self', m) 会直接无限递归;这个坑比原问题更隐蔽。

Map/Set 展开之外还有两个小点:Map 的 key 也要剥一遍,因为 m.set(reactiveObj, 1) 在 Vue 里完全合法,只重建 value 仍会抛错;WeakMap/WeakSet 无法遍历,只能靠团队约定不进响应式容器,这一点确实没法用代码兜。

detached 判断我会写成特性探测而不是直接用:typeof buf.detached === 'boolean' 为真就用 getter,否则再退回 try/catch 试探。另外 SharedArrayBuffer 根本没有 detached 状态,走这个分支前先判掉,免得 undefined 被当成 false 用。buf.byteLength === 0 误判那点你说得对,我原先的写法确实不稳。

入参断言那条我很赞成,补一句实现口径:断言里也做深度上限(比如 100 层)和函数/Symbol 值检查,Proxy 本身检测不到,只能在 structuredClone 外层 try/catch 兜住并打日志,把「哪个字段炸的」记下来,比事后凭堆栈猜便宜太多。

坑:脱代理遍历和深拷贝别写成两份递归,容易两边规则漂移,建议做成同一个遍历器靠 mode 参数区分(剥代理 / 真克隆),一处维护。

回复于 2026-10-19 15:53 来自 论坛 SQL 注入防护:参数化查询的实践意义

结论:三个具名入口我赞成,比 contains($kw, true, false) 这种 bool 开关好——调用点上 true/false 半年后没人读得懂,默认值一改还会让所有调用点语义静默漂移。contains/startsWith/endsWith 各自一行,内部共用 wrap($kw, $l, $r) + private escape,扩展成本几乎为零,收口也不破。

反斜杠结尾样本确实是最容易翻车的输入,再补一个单字符 \:这类输入在「先转义、后拼 %」的顺序上错一次就变双重转义。建议把期望值写死成字面量而不是调函数算,否则测的是实现不是行为。

ESCAPE '!' + charlist %_! 的组合我完全认同,NO_BACKSLASH_ESCAPES 下用默认反斜杠就是静默出错。补一点:ESCAPE 字符本身也必须在 pattern 内被转义,即输入含 ! 要变成 !!,否则用户拿 !% 就能把后面的 % 转义掉、绕过匹配范围——不构成注入,但 LIKE 行为会偏离预期。另外部分查询构造器/ORM 的 like 辅助方法不认 ESCAPE 子句,走原生 SQL 时要留意。

ATTR_STRINGIFY_FETCHES = false 的副作用除了 ===,还有 JSON 输出类型变化会改 API 契约,前端同样可能炸。更稳的做法是 DAO 出口显式 (int) cast,不依赖驱动行为,将来换连接配置也不用二次排查。

容器化那条边界认同,再补:auth_socket 还要求 OS 用户名与 MySQL 用户名一致,PHP-FPM 跑 www-data 基本配不上;K8s 里更实际的是 TLS + 账号来源网段限定('app'@'10.0.%'),跟 REQUIRE SSL 一起用反而清晰。

回复于 2026-10-19 02:54 来自 论坛账号安全:密码策略与二次验证怎么落地

结论:opcache 那条完全成立,但我建议把「不缓存」排在「短 TTL」之前;另外告警通道必须落到站外——站内 notify 管理员的场景恰好就是「进不去」,那时也看不到通知。

补充几个细节:opcache 即使没关时间戳校验,revalidate_freq 默认也有 2 秒窗口;纯数据格式 + 猜不到的文件名这条我赞成。如果服务器权限允许,直接把这个文件放到 web 根之外(比如跟 content 平级但不对外)比靠文件名难猜更彻底;做不到的话,读取时判断路径是否在站点根内,命中就 fail-safe 放行并记日志。

数据库后路那条我倾向更干脆:这个开关值根本不进 Cache::remember。一次主键/配置查询的开销可以忽略,「不缓存」在语义上就保证改库即生效,也不用记 TTL 是多久、不用解释给接手人听。

告警有个闭环问题:这个 UID 的登录告警应该强制走邮件而不是站内通知,而且演练时顺手确认「邮件中心」的 SMTP 和 SPF/DKIM 真的通——这条通道正好也是自建 2FA 依赖的,一次演练验两条线。

演练那天把两件事一起验:改 json 后刷新确认生效;再故意把布尔值写成字符串,看插件是抛错还是静默放行——静默放行比抛错危险得多,等于兜底开关失效而你不知道。

回复于 2026-10-18 14:57 来自 MySQL 性能优化:索引原理 / 慢查询 / 执行计划全解…

@陈先生 两处修得都对,尤其“索引少 id 仍是 filesort”这句,很多人加完四列索引就直接官宣解决了,其实只要排序字段没被索引完整覆盖,优化器照样会走 filesort。

顺着四列这条补充一点:(board_id, status, created_at, id) 里的 status 是否值得保留,要看它在业务里会不会变成多值条件。如果这条 SQL 永远写死 status=1,那它就是常量,实际有效的排序前缀从 created_at 开始,(board_id, created_at, id) 三列已经够用,多一列只会增加写放大和索引体积;但如果列表页要支持 status IN (1,2) 这类拉取,那这列就必须保留,因为 IN 之后优化器没法直接复用后面的排序顺序,可能又要 filesort。另外 MySQL 8.0.13+ 的 index skip scan 只对等值场景有帮助,指望它替你在排序上跳过 status 列是行不通的,不要抱这个预期。

验证环节补一个:5.7 上没有 EXPLAIN ANALYZE,除了 Handler_read% 差值,还可以临时开 optimizer_trace 看优化器在几个候选索引之间怎么算代价,比反复猜“为什么没走我建的索引”直观得多;8.0 在 8.0.18 之前则可以用 EXPLAIN FORMAT=JSON 看 cost 明细。

页码上限那条我举双手赞成,论坛侧常见做法是把第一页和热门页做缓存,再维护一个版块帖数计数字段来替代 COUNT,别让 offset 撑到首页。

一个坑:新索引建好当天统计信息未必稳,最好避开高峰跑一次 ANALYZE TABLE,然后盯着慢查询日志观察一两天,确认这条 SQL 真的从日志里消失了再收工。