缓存一致性实战:Cache Aside、延迟双删与binlog补偿
缓存一致性这个词只要做过后端、碰过 Redis 或者任何一层缓存的开发者大概率都在深夜被它折腾过。白天压测一切正常凌晨两点告警响了用户说改了昵称刷新还是旧的运营后台改了活动配置前端展示的价格和数据库里对不上。十有八九缓存一致性问题在作祟。这篇内容我打算把它从为什么会不一致一路讲到工程上到底怎么解覆盖 Cache Aside、Read/Write Through、Write Behind、延迟双删、binlog 异步刷新、分布式锁这几套主流打法每一套都给适用场景、代码骨架和踩坑记录。不管你刚接触缓存还是写了几年业务想系统梳理一遍都能从这里直接抄到能用的东西。1. 缓存一致性问题到底出在哪1.1 从一次线上脏数据说起我印象最深的一次事故发生在一个用户中心服务上。用户改了头像前端上传成功、接口返回 200刷新页面还是旧头像。运营反馈了三天开发一直说接口没问题直到有人去 Redis 里查了一下发现缓存里存的还是两小时前的旧 URL。问题其实不复杂更新逻辑是更新数据库 删除缓存但那次改动里删除缓存的操作被包在一个 try-catch 里Redis 网络抖动抛了异常异常被吞掉数据库更新成功了缓存却留着旧值。更麻烦的是这个 key 设了 24 小时过期意味着最长要脏一整天。这类问题的共同点是数据库和缓存是两个独立的存储它们之间没有事务保证。你没法像操作单库那样让写 DB和写缓存要么都成功要么都失败。只要存在两个写操作中间就有时间窗口只要窗口里来了并发请求就可能读到旧数据。这就是缓存不一致的本质——它不是 bug而是分布式系统里两个数据副本之间的固有窗口。所以讨论缓存一致性不该问怎么彻底消灭不一致而该问窗口能压到多小、业务能不能容忍、出了不一致怎么快速恢复。想清楚这三个问题方案自然就选出来了。下面我按读多写少写多读少强一致要求这几类场景把关键点一条条拆开。1.2 不一致的根源并发时序拆开看很多人背过先更新数据库再删除缓存这句话但没想过为什么。要理解它得把并发时序画出来。假设有读请求 R 和写请求 W缓存操作只有删除和写回两种。我们用三段式来看先删缓存再更新数据库。这个顺序的问题在于删除缓存之后、数据库更新之前如果来了读请求 RR 发现缓存空了去数据库读到旧值然后把旧值写回缓存。等 W 更新完数据库缓存里已经是被 R 写回的旧值而且这个旧值不会再被删掉脏数据会一直留到过期。这就是为什么先删缓存再更新 DB在低并发下也容易出事。先更新数据库再删除缓存。这个顺序下也有窗口读请求 R 在数据库更新之前查到了旧值还没来得及写回缓存此时 W 更新数据库完成并删除了缓存然后 R 把旧值写回缓存。看起来同样会脏但关键在于这个窗口要求 R 的读 DB 写缓存整个过程跨越了 W 的整个更新动作实际耗时通常只有几毫秒到几十毫秒而写操作的完成时间往往更短。概率极低这也是为什么工程上普遍推荐这个顺序。注意缓存操作推荐删除而不是更新。因为更新缓存需要计算新值如果两个写请求并发更新缓存的顺序可能和更新数据库的顺序相反缓存里反而是先到的旧值。删除操作是幂等的谁先谁后结果一样简单可靠。还有一类不一致来自主从架构。数据库主从复制有延迟写完主库立刻读从库可能读到旧值再把这个旧值写进缓存。这个问题靠缓存策略解决不了得从关键读走主库等主从同步位点入手。把根源拆清楚后面选方案才有依据。2. 方案选型四套主流策略怎么挑2.1 Cache Aside最常用也最容易踩坑Cache Aside 就是大家平时写的读时先查缓存、没有则查库回填写时更新库再删缓存。它的优点是实现简单、对业务代码侵入小、缓存只保存被访问过的数据、不容易被冷数据撑爆。缺点是所有的一致性保障都得靠业务代码自己写写操作失败、并发竞争这些情况都要手动处理。我见过太多团队把它当成银弹写完就以为万事大吉。实际情况是读多写少的场景它足够用一旦写操作密集或者对一致性要求高光靠它就不够了。它的核心参数是缓存过期时间作为兜底机制必不可少。一般热点数据设几分钟到几小时配置类数据可以设短一点比如 30 秒让不一致最多持续半分钟。另外 Cache Aside 有个隐藏细节读缓存回填时遇到空值要防穿透。如果数据库查不到别每次都被打到库上可以缓存一个空值比如null或特殊标记加短过期时间。这个和一致性也有关系——如果某条数据刚被插入之前缓存的空值还在就会读到不存在的错误结果。所以空值缓存的过期时间要短或者在写操作时一并删掉空值 key。2.2 Read/Write Through 与 Write BehindRead/Write Through 的思路是让缓存层去代理数据库的读写。应用只和缓存打交道缓存组件负责把数据同步到数据库。Read Through 是读缓存未命中时由缓存层去加载Write Through 是写的时候同时写缓存和数据库由缓存层保证两者同步。它的好处是业务代码干净不用自己管理两套存储坏处是需要一个支持这种模式的缓存组件Redis 原生并不直接提供得自己封装一层而且写入性能受限于缓存层和数据库的同步调用。Write Behind也叫 Write Back是写的时候只写缓存攒一批再异步刷到数据库。它的写性能最好特别适合写多读少、能容忍短暂数据丢失的场景比如计数器、日志、点赞数。但它的一致性风险也最大缓存还没刷到数据库时缓存挂了数据就丢了。所以我一般只在可容忍丢失的计数类场景用它业务数据绝不碰。这三套加上 Cache Aside基本上覆盖了绝大多数缓存模式的选择。选哪套不取决于哪个高级取决你的读写比例、一致性要求和运维能力。2.3 一张表看清四种策略的取舍策略一致性强度写性能实现复杂度典型场景Cache Aside中靠过期兜底中低读多写少通用业务Read Through中中中需要统一缓存层的服务Write Through中高低中写少但要求同步Write Behind低可能丢数据高高计数、日志、可丢场景从表里能看出来一致性强度和写性能基本是反比关系。工程上没有免费的午餐能做的就是在业务能接受的窗口里选一个代价最小的方案。我个人的习惯是默认用 Cache Aside遇到超高并发写再考虑订阅 binlog 做异步补偿计数类走 Write Behind强一致场景干脆不用缓存或者上分布式锁。这里补一句很多人纠结一致性强度这个指标其实更该关注的是不一致的持续时间和不一致的数据量。一个只对单个用户可见、几秒后自动恢复的脏数据和一个对全站可见、持续一小时的脏数据完全不是一个量级的问题。选方案时按这个维度评估比死抠理论模型有用得多。3. 实操落地Cache Aside 的完整实现3.1 为什么是先更新数据库再删除缓存前面拆过时序这里把结论落成可执行的规则。写操作的顺序固定为先更新数据库提交事务再删除缓存。这三个动作的顺序不能乱。删缓存必须放在事务提交之后因为如果事务里删了缓存事务又回滚了缓存就白白被删了一次还得靠下次读回填反而增加一次数据库压力。删除失败怎么办这是 Cache Aside 最大的软肋。删缓存失败但数据库更新成功脏数据就会一直留着。所以删缓存一定要有补偿机制常用三种重试删缓存失败后同步重试 2 到 3 次间隔几十毫秒。适合抖动类失败。消息队列补偿删缓存失败就把 key 投到 MQ由消费者重试删除直到成功。适合对一致性要求较高的场景。binlog 兜底不依赖业务代码的删除直接订阅数据库变更日志异步刷新缓存。这个下一章细说。还有一个细节很多人忽略删除 key 要比更新缓存更安全但如果业务上确实需要更新缓存比如缓存里存的是聚合后的结果重新计算代价大那就得保证缓存更新和数据库更新的顺序一致通常做法是给写操作按业务主键加锁保证同一 key 的写串行。3.2 延迟双删的延迟时间怎么算延迟双删是解决先删缓存那种极端场景的补丁做法是更新数据库之前先删一次缓存更新完成后再删一次第二次删除延迟一段时间执行。它的逻辑是——中间那次可能被读请求回填的旧值靠第二次删除清掉。关键参数就是这个延迟时间设太短旧值还没被回填就删了等于白删设太长则不一致窗口拉大。经验公式是延迟时间 一次读请求的查数据库 写缓存总耗时 主从复制延迟实际项目里我一般取500 毫秒到 1 秒。如果是主从架构且读走从库要把从库延迟算进去通常再留一倍余量。这个值不是拍脑袋你可以通过监控 APM 上读接口的 P99 耗时来定P99 是 80 毫秒主从延迟 50 毫秒那延迟设 300 毫秒就够留点余量设 500 毫秒。延迟双删的延迟执行一般用定时任务或者延时队列实现别用Thread.sleep会占着线程还可能因为服务重启丢失任务。下面给一段可以直接复用的骨架。3.3 可直接复用的代码骨架先看读缓存的标准写法注意空值缓存防穿透public User getUser(Long id) { String key user: id; String cached redis.get(key); if (cached ! null) { return NULL.equals(cached) ? null : JSON.parse(cached, User.class); } User user userMapper.selectById(id); if (user null) { // 空值缓存过期时间短防止穿透 redis.setex(key, 30, NULL); return null; } // 过期时间加随机值防止同一时刻大量 key 集体失效 redis.setex(key, 300 random.nextInt(60), JSON.toJSONString(user)); return user; }写操作的标准写法先更新库再删缓存删除带重试Transactional public void updateUser(User user) { userMapper.updateById(user); // 事务提交后再删缓存这里用事务同步回调 TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { Override public void afterCommit() { deleteWithRetry(user: user.getId(), 3); } }); } private void deleteWithRetry(String key, int retries) { for (int i 0; i retries; i) { try { redis.del(key); return; } catch (Exception e) { // 最后一次失败投递到 MQ 补偿 if (i retries - 1) { mqProducer.send(cache_delete_topic, key); } } } }延迟双删的延迟任务投递public void updateWithDoubleDelete(User user) { String key user: user.getId(); redis.del(key); // 第一次删 userMapper.updateById(user); // 更新库 // 投递延迟消息500ms 后执行第二次删除 delayQueue.offer(key, 500, TimeUnit.MILLISECONDS); }这三段代码就是大多数业务系统能直接落地的核心。注意afterCommit这一步很关键很多人把删缓存写在方法体里事务还没提交就删了遇到并发读会把旧值回填反而制造了脏数据。4. 高并发进阶把不一致窗口压到最小4.1 订阅 binlog 做异步补偿业务代码里手动删缓存总有覆盖不到的地方定时任务批量改数据、DBA 直接改库、多个服务同时操作同一张表。这时候靠人工在每处加删除逻辑早晚会漏。更稳的做法是订阅数据库的 binlog数据库一变就自动刷新缓存。常见工具是 Canal、Debezium、Maxwell 这些原理都是伪装成数据库的从库拿到变更日志后解析出表名、主键、操作类型再投递到 MQ 或者直接删缓存。// binlog 消费者伪代码 KafkaListener(topics db_change_topic) public void onMessage(String message) { ChangeEvent event JSON.parse(message, ChangeEvent.class); if (!user.equals(event.getTable())) return; String key user: event.getPrimaryKey(); // 删除失败就重试binlog 场景可以无限重试 redis.del(key); }用 binlog 的收益很明确业务代码零侵入、覆盖所有写路径、天然带重试。代价是引入了额外组件链路变长消费延迟可能到几十毫秒到几百毫秒。所以它更适合能容忍百毫秒级延迟但要求最终一致的场景比如商品信息、用户资料的展示缓存。真正要求毫秒级强一致的核心数据还是别指望它。我踩过的一个坑是binlog 消费是乱序的。同一行数据短时间内被改了两次消费者可能先处理第二次再处理第一次导致缓存被旧的删缓存请求命中——虽然删除操作幂等但如果在删除后还有回填动作就有风险。解决办法是按主键 hash 到同一个 partition保证同一行变更串行消费。4.2 分布式锁与读写锁的取舍如果业务真的不能容忍任何窗口比如账户余额、库存扣减那就得让读写串行化用分布式锁把同一 key 的操作锁起来。Redisson 提供了RLock和RReadWriteLock后者允许读读并发、读写互斥比单纯互斥锁吞吐高一些。RLock lock redisson.getLock(lock:user: id); lock.lock(3, TimeUnit.SECONDS); try { // 读或写逻辑 } finally { lock.unlock(); }但锁的代价很大加锁、释放锁、锁等待都要时间QPS 会掉得很明显还引入死锁和锁超时的风险。我一般只在写操作极低频且一致性要求极高的场景用锁比如后台手动调价。日常的高频读场景用锁等于把缓存的性能优势全搭进去得不偿失。更常见的折中是读不加锁写加锁 延迟双删既保证了写之间的串行又让读保持高并发残留的窗口靠过期时间兜底。这套组合拳在实际项目里最实用。4.3 过期时间与降级兜底不管用哪套方案缓存过期时间都是最后一道防线。它保证即使前面所有手段都失效不一致也只会持续一个 TTL 周期。所以设计缓存时TTL 不是可有可无的配置而是必须明确写进方案里的参数。TTL 怎么定三个原则一致性要求高TTL 短30 秒到 5 分钟让脏数据快速自愈。访问频繁、改动少TTL 长几小时甚至一天减少回源压力。加随机抖动在基础 TTL 上加 0 到 60 秒的随机值避免大量 key 在同一秒过期引发缓存雪崩。另外还要准备降级路径。缓存整体不可用时读请求直接落到数据库要有熔断和限流保护别让数据库被打挂。删缓存服务不可用时要有补偿队列兜底。这些不是缓存一致性的解法但决定了你的一致性方案在故障时是降级可用还是彻底崩盘。5. 常见问题与排查技巧实录5.1 高频问题速查表现象可能原因排查手段处理方式改了数据缓存还是旧值删缓存失败查删除日志、监控失败率加 MQ 补偿binlog 兜底短时间读到旧值之后恢复并发回填旧值看时间窗口是否在毫秒级延迟双删容忍即可主从切换后读到旧值主从延迟查复制位点延迟关键读走主库大量 key 同时失效TTL 无抖动看 key 过期时间分布加随机 TTL缓存与库长期不一致无过期或过期过长比对样本数据缩短 TTL加数据校验任务删除操作被吞异常try-catch 未处理代码审查必须重试或投递 MQ这张表基本覆盖了我遇到过的八成一致性问题。排查的第一步永远是确认是短期不一致还是长期不一致。短期的通常是并发窗口属于设计预期内长期的才有问题多半是删除失败或漏了删除路径去查删除日志一般能定位。5.2 几个文档里不会写的避坑经验第一别在事务里删缓存。前面提过但这是最容易被忽略的错误。哪怕你写的是afterCommit也要确认框架版本对事务同步回调的支持有些老版本或者手动管理事务的场景不会触发回调。稳妥做法是把删缓存抽成一个独立方法事务方法里通过事件机制在提交后触发。第二读缓存回填要加过期时间。有人图省事用set不带过期一旦出现脏数据就是永久性的只能手动清理。所有回填操作都必须带 TTL这是纪律。我见过一个系统因为回填用了set出了脏数据之后花了两小时全量扫描清理代价很大。第三监控比方案更重要。再好的方案跑在线上都会出意外关键是有没有监控能发现不一致。我会加两类监控一是缓存命中率命中率突然下降可能是大量 key 被删或失效二是定期抽样比对从缓存和数据库各取一批 key 做对比发现不一致就告警。抽样比对对业务侵入小能提前发现问题。第四批量操作要特别小心。批量更新 100 条数据如果逐条删缓存中途异常会导致部分删了部分没删。更稳的方式是先批量更新库再一次性批量删缓存或者交给 binlog 统一处理。批量任务最好设计成可重入、可续跑失败后重跑不会造成新的不一致。第五热点 key 不要设太短的 TTL。短 TTL 意味着频繁回源热点 key 回源会瞬间打爆数据库。热点数据用长 TTL 加逻辑过期值里存一个过期时间戳异步刷新既避免集中失效又保证能被刷新。这套逻辑过期方案配合 binlog 刷新是我目前用下来最稳的组合。最后说个个人习惯每次设计缓存我都会在文档里写清楚三件事——读路径、写路径、不一致的兜底恢复手段。写路径里把每个删除操作的下游补偿标出来兜底手段里把 TTL 和降级策略写死。这三块写清楚后面的人接手就不会在深夜里瞎猜为什么缓存里是旧值了。缓存一致性没有一劳永逸的方案只有把窗口算清楚、把补偿做扎实才能在性能和正确性之间拿到那个能接受的平衡点。

相关新闻

嵌入式求职真相:用STM32、FreeRTOS与Linux构建能力证据链

嵌入式求职真相:用STM32、FreeRTOS与Linux构建能力证据链

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:01:45 阅读更多 →
FOC电流环采样更新策略:SSSU/DSDU/SSDU/DSSU实战解析

FOC电流环采样更新策略:SSSU/DSDU/SSDU/DSSU实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:01:45 阅读更多 →
IMU加速度计重力消除完全指南:原理、算法与工程避坑实战

IMU加速度计重力消除完全指南:原理、算法与工程避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:01:45 阅读更多 →

最新新闻

nas服务域名高速访问-获取公网IP和端口

nas服务域名高速访问-获取公网IP和端口

家庭网络框架 标准架构拆解 标准连接链路: 运营商光纤 → 光猫(光调制解调器) → 路由器(无线 AP 模式) → 手机 / 电脑 / 电视各设备角色: 光猫:负责光电转换(把光信号变电信号…

2026/10/7 9:40:28 阅读更多 →
Hazelcast 统一实时数据平台:流处理与分布式数据存储的架构与实践指南

Hazelcast 统一实时数据平台:流处理与分布式数据存储的架构与实践指南

缓存KV存储消息队列流处理后端 【免费下载链接】hazelcast Hazelcast is a unified real-time data platform combining stream processing with a fast data store, allowing customers to act instantly on data-in-motion for real-time insights. 项目地址: htt…

2026/10/7 9:40:28 阅读更多 →
CodeQL C 库对 C 9 unary `not` 模式的完整支持:UnaryPatternExpr 与 NotPatternExpr 解析

CodeQL C 库对 C 9 unary `not` 模式的完整支持:UnaryPatternExpr 与 NotPatternExpr 解析

静态分析SAST应用安全漏洞扫描代码质量 【免费下载链接】codeql CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security 项目地址: https://gitcode.com/gh_mirrors/co/code…

2026/10/7 9:40:28 阅读更多 →
GPT-SoVITS hps未定义报错:3步快速修复指南

GPT-SoVITS hps未定义报错:3步快速修复指南

GPT-SoVITS hps未定义报错:3步快速修复指南 【免费下载链接】GPT-SoVITS 1 min voice data can also be used to train a good TTS model! (few shot voice cloning) 项目地址: https://gitcode.com/GitHub_Trending/gp/GPT-SoVITS 装好环境、打开网页、点下…

2026/10/7 9:40:28 阅读更多 →
blackbox 的 GnuPG 排错指南:从常见报错到密钥轮换的完整实战手册

blackbox 的 GnuPG 排错指南:从常见报错到密钥轮换的完整实战手册

密码学开发工具应用安全DevOps 【免费下载链接】blackbox Safely store secrets in Git/Mercurial/Subversion 项目地址: https://gitcode.com/gh_mirrors/bl/blackbox 点击查看 免费下载 blackbox 本质上只是 GnuPG(GPG)的一层前端封装&…

2026/10/7 9:40:28 阅读更多 →
C++基本组件之内存池详解

C++基本组件之内存池详解

前言内存池(memory pool)要解决的问题很具体:通用分配器(general-purpose allocator,也就是 malloc / free 或 operator new / operator delete)为了应付任意大小、任意生命周期的请求, 内部必须…

2026/10/7 9:39:28 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 4:21:51 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →