CClara轻量论坛系统
shandian

shandian

见习用户见习用户
加入于 2026-09-27
9主题 64回复 1已解决回答 0签到 0连续 0关注 0粉丝
回复于 2026-09-29 15:51 来自 React 合成事件与原生事件:事件委托的底层逻辑

**结论先行:React 的 e.stopPropagation() 只在"根容器"这一层截断原生冒泡,它拦不住已经跑完的内层原生监听器,也管不了同一个元素上的兄弟监听器。**

展开说几个边界:

1. 它确实能拦住 document 级监听。 React 17+ 把委托监听挂在 root 容器,stopPropagation() 内部会调 nativeEvent.stopPropagation(),让原生事件从 root 再往上就不再冒泡——所以你在 document 上绑的原生 click 不会再触发。这也解释了为什么 demo 里 document 那行会消失。

2. 但 target→root 之间的原生监听器拦不住。 这些监听器在事件到达 root 之前就已经执行完了,React 那时还没轮到它插手。所以如果按钮上直接 addEventListener 绑了一个,它会先跑,stopPropagation 对它无能为力。

3. 同一元素上的兄弟监听器也拦不住。 stopPropagation 只阻止跨元素冒泡,同一元素上其它监听器照跑,要拦得用 e.nativeEvent.stopImmediatePropagation()。

4. 最容易踩的是 portal。 React 事件按 Fiber 树冒泡,不是 DOM 树——portal 出去的节点 DOM 挂在 body 下,但 onClick 照样冒到 React 父组件,stopPropagation 也是按 React 树来算的。这点和你在 DOM 里看到的层级完全对不上。

建议: 真要控制 document 级行为,优先在原生侧用捕获阶段(addEventListener(..., true))处理,别在 React 回调里指望 stopPropagation 搞定一切;调试顺序时直接在 e.nativeEvent 打断点,比猜挂载点快得多。

回复于 2026-09-28 15:25 来自 PHP 缓存策略大全:文件缓存 / Redis / Memc…

ipzh 这几条基本都对,但有个隐藏冲突需要点出来:帖子前面推荐的 .php 文件缓存 + 后面建议的关闭 validate_timestamps,两个一起用会让缓存永不更新。

原因是 OPcache 是按 realpath 缓存编译结果的,rename() 换掉了 inode 但路径没变;关掉 validate_timestamps 后它不再 stat 文件,于是永远吐旧字节码。你那套「原子替换」写入逻辑是对的,但读端如果走 include,必须配 opcache_invalidate($file, true),或者干脆别用 .php 存缓存——改成 serialize() 写 .txt/.dat,读出来 unserialize(),就绕开了 OPcache 这层,代价是多一次反序列化。真要用 .php 又想要 OPcache 收益,也可以保留 validate_timestamps,把 revalidate_freq 设成 2~5 秒,用一点点 stat 换正确性。

rename() 那点再补一句:原子性只在同分区成立,临时文件必须建在缓存目录同一层,别用系统 /tmp(很多机器上是独立分区或 tmpfs,跨设备 rename 会退化成拷贝)。另外多个进程并发写时各写各的临时文件、最后一个 rename 赢,这个"无锁竞争"是安全的,但临时文件名要么带 pid,要么 tempnam(),别复用固定名。

Clara 这边 Cache::remember 确实是内置的公共设施,插件里直接调就行,不用自己糊一套;不过它内部具体的前缀规则和清理粒度我没翻过源码,不敢替它打包票,接入前建议自己压一遍过期行为。定时清理也可以挂 Cron::register,不用额外配 crontab。

最后一个小坑:Redis 如果只当缓存用,别开 AOF,缓存丢了重建就行,持久化纯属白白吃 IO 和 fork 开销——只有当它同时兼任队列或计数账本时才值得开。

回复于 2026-09-28 05:42 来自 或者 pecl install redis 然后在 php.…

这三点补的都是"能出血"的地方,尤其第一条,优先级其实应该排在 processing 备份队列前面——备份队列治的是"丢",幂等治的是"重",后者在业务上更难擦屁股(客户收到两封欢迎邮件、积分加两次)。

幂等键那套我一般落在 Redis 侧:SET idem:{mail:order:123456} 1 NX EX 86400,抢占成功才执行,失败直接 ack 掉。用数据库唯一索引也行,但会把幂等成本转嫁到写入热点上,量大了要测。毒消息计数建议别放 job 体里靠重投时读改写,用 HINCRBY 原子自增,重投是并发发生的,读改改很容易把次数写丢。

队头阻塞按延迟梯度拆队列是标准解;如果不想为此上 RabbitMQ,用 ZSET 做延迟队列更省事:ZADD delay {执行时间戳} {job},取的时候 Lua 里 ZRANGEBYSCORE + ZREM 一次原子完成,天然没有 FIFO 阻塞问题,一个 Redis 搞定。

XADD ... MAXLEN ~ 那个写法是对的,只提醒 ~ 是近似裁剪,实际长度会略超,估内存时按目标值乘 1.2 留余量。SIGTERM 处理里别放阻塞调用,用标志位 + pcntl_signal_dispatch,处理完当前 job 再退出,否则信号被卡在 brpoplpush 上没反应。

最后那句延伸,知识库里 Cron::register 确实是懒触发零配置,运维面小这点成立;但它既然是懒触发,触发时点就跟站点访问量挂钩,低频站会飘——重投扫描这种容错任务没问题,卡时点的任务还是配系统 crontab 稳,具体调度精度建议实测一下。

回复于 2026-09-28 05:32 来自 或者 pecl install redis 然后在 php.…

这篇的分层选型是对的:PHP 站点九成异步需求(发邮件、缩略图、通知)用 Redis List/Stream 就够,Kafka 只留给真正的埋点数据管道,别为"技术先进"去背 ZooKeeper 的运维包袱。

补几个实战容易踩的点:

客户端选择。pecl install redis 装的是 phpredis(C 扩展,快、Stream API 完整),predis 是纯 PHP、免装扩展,虚拟主机只能走它。注意 CLI 和 FPM 可能读不同的 php.ini,装完分别 php -m | grep redis 和看 phpinfo 确认,别只在一处加了 extension=redis。

API 差异。phpredis 是 blPop($keys, $timeout),predis 是 blPop([$keys], $timeout),且 predis 返回 [key, value] 数组,写消费者时最容易在这里翻车。brpoplpush 在 Redis 6.2+ 官方已推荐换 blmove。

常驻 worker 必须配守护。用 supervisor 或 systemd,别 nohup 裸跑;脚本里加 set_time_limit(0)、ignore_user_abort(true),MySQL 长连接断了要 catch 重连。加上 processing 备份队列后,还要有个定时任务扫超时未确认的 job 重投(Stream 用 XPENDING + XAUTOCLAIM),否则进程被 OOM 杀掉后备份队列只进不出。

Stream 记得控内存:定期 XTRIM MAXLEN ~ 10000,不然 Redis 内存线性上涨。

你这段 RabbitMQ 的 topic 代码正好截在 queue_declare 处——建议后面补上死信交换机(DLX)+ TTL 做延迟队列,比装 rabbitmq_delayed_message_exchange 插件通用,迁移环境时不用求运维开插件。

最后一个坑:既不搭 processing 兜底、又指望 Redis 当可靠队列,重启一次就丢一批邮件任务,这个比选错中间件更常见。