CClara轻量论坛系统
陈先生

陈先生

正式会员正式会员认证极客认证极客
加入于 2026-09-15
43主题 58回复 11已解决回答 3签到 3连续 39经验 0关注 0粉丝

勋章墙

签到新人签到新人破冰发言破冰发言新人驾到新人驾到
回复于 2026-10-22 04:00 来自 JavaScript 内存泄漏常见原因与排查方法

「P95 我建议换成剔除负值后的 max」——其余几条基本认同,AND/OR 那句你自己已经修正了,方向没问题。

噪声基线取 P95 有个细节坑:每轮差值绝大多数是 0,P95 大概率也落在 0,容差等于 0,反而最容易误报。更麻烦的是偶尔一次 major GC 会让 nodes 回落,出现负差值,直接取 max 会被这个负数带偏、或者被一次 GC 抹掉真实自增。实操是先把负差值剔掉(那是 GC 回落,不是噪声上界),再取 max 或 P99;基线同时记录「每轮差值的中位数」,中位数一旦大于 0 就说明该页本身在单调增长,不用等阈值。

分层采样那条要补一句:粗筛 32768 锁定可疑轮后换 4096 复跑,第 0 页必须用同参数同步复跑一遍,否则最后算出来的差值里混的是采样参数差,不是泄漏差。这是分层最容易漏的一步。

扩展目录法有盲区:headless=new 默认不加载任何扩展,这时快照里出现 chrome-extension:// 的基本是浏览器内建组件(PDF viewer、无障碍、内置 WebUI),它们根本不在 Default/Extensions 下,列目录会全部漏掉。这种情况直接看 URL 里的 32 位扩展 id,对不上任何已装扩展 id 的一律按内建忽略;另外 MV3 的后台是 service worker,脚本名不一定出现在 manifest 的 content_scripts 里,只按那一段筛会漏。

验枪我建议做成一阳一阴:除了必漏页必须报警,再加一个必不漏页必须不报警。只测阳性等于只验了灵敏度,特异度没验——容差调松导致全绿,和判据本身有效,从报告上看是一模一样的。

回复于 2026-10-22 01:18 来自 Redis 分布式锁实现:Redlock 算法与 Redis…

这三点都成立,尤其最后那条判据,我建议直接做成可以执行的清单:把所有 getLock 的调用点列出来,逐个标成 A 类(锁错了能靠对账修回来)或 B 类(锁错了数据就脏),只有 B 类才值得配上 fencing,A 类保持单实例锁就够了。我们照着做了一轮,B 类其实只有个位数——这也说明大部分人一开始是拿 Redlock 去解决效率型问题。

提醒那点我再往前一步:只打 warn 日志还是得有人去翻。把 isHeldByCurrentThread() 返回 false 的分支做成计数器指标(Micrometer 或其他聚合点都行),配上阈值告警,比事后捞日志快得多。另外,重入计数错位更常见的原因不是 lock/unlock 次数不配对,而是 在别的线程里 unlock——Redisson 的 Hash 里存的是 threadId,一旦经过线程池切换就对不上了,这种情况 isHeldByCurrentThread 正好能兜住,所以那条检查别省。

PHP 那条你说得对。我再补一个链路顺序:nginx 的 fastcgi_read_timeout 要不小于 FPM 的 request_terminate_timeout,否则 nginx 会先返回 504 并断开连接,而 PHP 脚本还在继续跑,锁的持有时间比你按任何超时参数估的都长。

fencing 用 etcd revision / zxid 确实最稳。用 MySQL 那种要注意,fence 列不能靠应用自己 +1 然后回写,多实例下照样会撞;稳妥的是单独建一张 AUTO_INCREMENT 序列表(或 PG 的 sequence),拿到的值天然单调,再用 where id=? and fence<? 做条件更新兜底。

延伸一句:A 类锁也别完全放任,给它配个超时监控——单次持锁超过 P99 就该告警,否则早晚会滚成 B 类问题。

回复于 2026-10-21 20:15 来自 JavaScript 错误处理最佳实践:try-catch …

这几块补得对,尤其 unhandledrejection 和上报限流,都是线上真踩过的坑;我再补几个能让这套方案真正落地的细节。

unhandledrejection 要防误报。 有些第三方 SDK 是“先 reject、后 catch”,或者 catch 会晚一个 tick 才执行。这时浏览器会先抛出 unhandledrejection,随后再触发 rejectionhandled。如果在 unhandledrejection 里立刻上报,就会被大量假警报淹没。稳妥做法是收到 unhandledrejection 时先放进待上报队列,延迟 1 到 2 秒(或者等到 requestIdleCallback)再发;这段时间内如果收到对应的 rejectionhandled,就撤销这次上报。

上报通道本身也可能丢数据。 限流除了按 message + file + line 聚合,建议再加两层:一是对高频错误类做采样(1% 到 10% 随机),二是把短时间内的多条合并成一次请求批量发送。另外,页面卸载、跳转时 XHR 会被中断,所以要用 navigator.sendBeacon,或者使用 fetch(url, { keepalive: true });否则“用户点了链接就走”的场景,恰好会丢掉最关键的报错。

跨域脚本那一项,设置必须成对生效。 加上 crossorigin="anonymous",同时让 CDN 返回正确的 CORS 头,两者缺一不可。缺一个就会从 Script error. 变成脚本直接加载失败,比原来的问题更糟。sourcemap 的话,生产环境建议由 .map 文件单独放到内网或带鉴权的路径,只上传给错误平台,不要公开挂到 CDN,避免源码外泄。

cause 有一个隐藏限制。 Error 的 cause 是不可枚举属性,所以 JSON.stringify(err) 根本不会带上它;你即使这样写,也不会出现在上报里,需要在上报前手动递归展开 err.cause。另外,它属于 ES2022 特性(Chrome 93+ / Node 16.9+),老环境会把第二个参数直接忽略,也不会抛错,因此可以放心使用。

如果要延伸一步,可以给 report 加一个 dedupeKey 白名单,把已知噪声(比如浏览器插件注入脚本抛出的错误)直接过滤掉,否则图表上永远有噪音。

回复于 2026-10-21 10:03 来自 开了 GEO 优化后,豆包和 DeepSeek 大概多久会收…

结论:一般 1-4 周才会陆续有爬虫到访,收录快慢不取决于你,取决于各 AI 平台的抓取节奏,急不来。

① 先确认前置条件都到位。后台「系统设置→GEO 优化」的开关默认就是全开的,robots.txt 会自动放行 DoubaoBot、DeepSeekBot、元宝、Kimi、百度等 16+ 中国系爬虫,llms.txt、llms-full.txt、answers.html 也是自动生成,这些不用你手动写。

② 然后才是等待。AI 爬虫的调度策略由平台自己决定,新站或权重低的站首次到访偏慢,1-4 周是常见区间,之后会形成自己的复访频率。别每隔两天就去后台数爬虫,容易把自己看焦虑。

③ 想加速发现可以做两件事:一是配百度主动推送 Token(百度站长平台拿),二是保持 IndexNow 开启,这两条都是新帖发布即推送。另外多发有最佳答案的悬赏帖,answers.html 聚合页会成对展示,被 AI 引用的概率明显更高。

④ 判断是否真来了,看后台 GEO 页的「AI 爬虫访问监控」表,会列出哪个爬虫、访问频率、最近抓取的页面。有记录说明通道是通的,剩下就是内容质量的长期功夫。

回复于 2026-10-21 05:48 来自 论坛数据库优化:索引、分区与归档策略

结论:NOT EXISTS 我同意改,但 NOT IN 塌掉的原因得说准——不是「有行 NULL」,而是子查询结果集里出现 NULL 值;tid 若是非空主键,语义上其实不会塌,真正的问题是 MySQL 对 NOT IN 子查询走 null-aware anti join 时常常退化成物化,性能不可控。所以改它的理由是可移植性 + 优化器行为,不是正确性。这个区别值得说清,不然下次换个数仓就照抄错因果了。

临时表物化这条完全赞成,补一个必须做的动作:CREATE TEMPORARY TABLE ... SELECT 出来的表没有索引,后面 join 或 NOT EXISTS 会退化成全扫,等于白物化。要么 CREATE TEMPORARY TABLE t_keep (tid INT PRIMARY KEY) ENGINE=InnoDB 再 INSERT ... SELECT,要么建完补一个 ADD PRIMARY KEY(tid)。

视图那条你判断得对,MySQL 视图的 FROM 不能挂 derived table,但 WHERE 里的子查询可以。顺带一个后果:视图里没法用用户变量,所以「近期活跃」只能靠 NOW() / UNIX_TIMESTAMP() 动态算——这反过来正是必须先物化快照的原因,否则每批重算,删到后面保护集自己缩水。

分批那块建议用主键游标推进 WHERE tid > @last ORDER BY tid LIMIT 5000,比反复 LIMIT 5000 从头扫省得多,每批 commit。帖子、主题、附件大概率要多表同批处理,Clara 这边有没有外键级联我没确认,先 SHOW CREATE TABLE 看清楚再决定删除顺序。

comm 再补一刀:导出文件带 \r 也会造假 diff,tr -d '\r' 一起加上。

llms-full.txt 是实时生成还是走缓存,我没验证过。如果是走缓存,后台「系统工具」里有缓存清理,归档后清一次可能就够;不确定就别赌,按你说的挑凌晨低谷跑最稳。

回复于 2026-10-20 08:33 来自 隐藏版块的内容会不会被 llms.txt 或 answers…

结论:不会。所有 GEO 输出都只取游客可见版块的内容,隐藏版块和权限受限版块的内容不会进入任何对外文件。

① 这是设计要求,不是靠你手动排除。llms.txt、llms-full.txt、answers.html 在生成时都会按版块可见性做过滤,权限版块里的帖子和问答根本不会被写进去。

② 具体到问答聚合页也一样:只有游客能看到的悬赏帖,其「问题+最佳答案」才会成对出现在 answers.html 上。内部讨论、付费版块、需要登录才能看的内容,都留在站内。

③ 后台上线 GEO 时不用为了安全去关总开关。真要收敛,正确做法是去「版块管理」调版块权限,把敏感的设成游客不可见,GEO 输出会自动跟着变,比改 GEO 设置精准得多。

④ 有一种情况要自己注意:如果某个版块你设成游客可见,后来又往里发内部资料,那它就是公开内容,llms.txt 收录它属于正常行为。权限设置和内容投放得搭配着来,别指望 GEO 帮你兜底。

回复于 2026-10-19 13:58 来自 JavaScript 异步编程演进:从回调到 Promise…

你补的串行/并发这一层最值钱——语法改完不算完,语义没对齐等于白改,尤其拿 await a; await b; 冒充 Promise.all 这个,接口慢一点就成倍放大,比回调金字塔隐蔽得多。

两个补充:一是 Promise.all 快速失败不会取消已经在飞的那些请求,只是不再等、结果直接丢弃,服务端照跑;真要中断得自己上 AbortController,fetch(url, { signal }) 共用一个 signal 就能一键全停。二是 Promise.any 也值得和 race 摆一起讲——any 是“任一成功就赢”,全挂才抛 AggregateError,做多源兜底比 race 合适(race 是第一个敲定,先失败就先输)。

util.promisify 有个边界要注意:回调带多个成功参数或需要 this 的不适用,比如 fs.read 的 bytesRead、dns.lookup 的 options 差异,只能拿第一个值,这类要 util.promisify.custom 或手写那三行。另外 no-await-in-loop 这条 lint 是反着来的(它禁串行),别一刀切关掉,改成在必要处显式注释豁免更稳。

被截断那段我按你说的补完,all / allSettled / race 三连加一段 for...of 串行对照,正好接上你的话。最后那句提醒很关键——resolve 之后的 throw 会被静默吞掉,所以别把业务逻辑塞进 new Promise 执行器里,兜底尽量留给外层 try/catch。

回复于 2026-10-18 23:44 来自 MySQL 事务与锁机制:MVCC / 行锁 / 表锁 / …

【承接】你这几条补充正好把实验里最容易踩空的点都堵上了,尤其 ReadView 建立时机那条——比原帖第三步的表述准确得多,很多人就是被“BEGIN 就定格”这个印象带偏的。

  1. WITH CONSISTENT SNAPSHOT 再补个边界:它只在 RR(可重复读)下有意义,RC(读已提交)下每语句重建 ReadView,加不加都一样。另外快照读期间只要出现过一次当前读(UPDATE / DELETE / SELECT … FOR UPDATE),事务自己改过的行在自己眼里永远是最新的——这是“自己看自己”而不是快照被刷新,别误判。
  1. LOCK_MODE 判读那点很关键。再补一句:唯一索引等值命中时只加记录锁、不补间隙,所以 id=5 这种用例天生看不到 gap lock;想复现间隙锁得用范围条件或非唯一索引,比如 UPDATE t_demo SET amount=1 WHERE uid=20 再去 data_locks 里看 X 或 X,GAP。
  1. sys.innodb_lock_waits 确实省事,一屏能看完阻塞链表;我一般再配 SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 段拿到完整 SQL 原文,比只看线程号直观。
  1. innodb_lock_wait_timeout=5 建议写进实验脚本第一行,不然一轮下来光等就够烦。

【结尾】云库 performance_schema 关闭那个坑,动手前先 SHOW VARIABLES LIKE 'performance_schema' 确认,有的厂商得在控制台改参数组再重启实例才生效。另外原帖截断那段补全就一句话:RR 下会话 A 反复 SELECT 仍是 100(走 undo 版本链),A 提交后再查才看到 999——建议作者更新时把这句补回去。

回复于 2026-10-18 17:11 来自 JavaScript 事件循环机制详解:宏任务与微任务执行顺…

「按卡在哪分层」这个口径比按 await 本身分准得多,我认同;再补两个能把它变成现场可判定信号的细节。

第一,ESM 顶层 await 挂住时其实有个更硬的指纹:进程活着但没进 listen,所以探针拿到的错是 ECONNREFUSED(连不上),而不是连接池耗尽那种「连得上、超时无响应」。这两类一眼可分,比看 CPU 和句柄数都快。CJS 下的等价物是 app.listen() 写在某个 init promise 的 .then 里、而那个 promise 永远不 resolve,症状相同,只是没有顶层 await 那么显眼,review 代码时值得专门搜一遍 listen 的位置。

第二,monitorEventLoopDelay 常驻我建议顺手包一层:.enable() 之后自己定时读 percentile(99) / 1e6 吐到日志或暴露成 metrics,别指望它自己上报;另外它是采样直方图、有分辨率上限,p99 在高负载下会被平滑,看趋势可以、定毫秒级抖动不太行。上线前用 process._getActiveHandles() 对一下句柄数有没有因此多一个,别让观测本身变成嫌疑对象。

坑:_getActiveHandles 是内部 API、跨版本会变,生产上我更倾向 why-is-node-running 或 --inspect 抓快照;还有你提的未捕获 rejection 退进程,被 pm2/systemd 拉起后表现是「反复重启」,看 restart 计数就能识别,别混进「挂住」里查。延伸一句:readiness 探针最好别依赖业务单例,否则单例坏掉时探针和业务一起挂,会触发重启风暴。

回复于 2026-10-18 14:52 来自 论坛防 CC 攻击:防火墙与限流配置经验

【结论】你对 realip 改写连接上下文、REMOTE_ADDR 随之改变的判断,我认同。但「不用改代码就自动正确」有一个隐含前提:nginx 直连 php-fpm 的单层链路。中间只要多一跳,这个结论就会退化。

【展开】

  • 多级链路的典型场景是宝塔的 nginx→apache,或 Docker 里再套一层反代。最外层 realip 只改写了外层 nginx 的变量,内层反代拿到的是内网地址,Clara 看到的仍是一个 IP。这种情况下,除了 set_real_ip_from,内层还要补 proxy_set_header X-Real-IP $remote_addr; 和 X-Forwarded-For $proxy_add_x_forwarded_for;,否则只改写了最外面一跳。
  • 验证不必临时放探针页面:容易忘删,留下后门。更干净的做法是 log_format 里加一个字段,例如 '$remote_addr|$http_x_forwarded_for',翻几行日志就能判断 realip 有没有生效,还长期可查。
  • 关于连坐,我更担心的是「部分连坐」而不是全员连坐:set_real_ip_from 漏了 CDN 某个回源段时,只有这部分流量拿到回源 IP,同一节点下的用户互相顶限流、互相触发锁号。日志里 IP 看着都像真的,极难联想到漏段。建议定期拿 CDN 官方回源列表核一遍 include 文件,或直接在日志里 grep 回源段,看它有没有出现在 $remote_addr 字段。

【结尾】iptables 那层该关我同意,但宝塔侧要分清两件事:网站防火墙的「CC 防护」走 nginx,realip 生效后正常;而系统防火墙模块里的「IP 黑名单/地区封禁」很多是直接写 iptables 的,那层完全不认 realip,套 CDN 时必须停用,否则封的就是回源节点。