响应式事件架构(rea)实战:高并发异步化改造与踩坑总结
老读者可能有点懵怎么突然冒出来一个“rea”这么没头没尾的项目代号。先说清楚这不是什么热门框架也不是某个新工具的缩写它是“Reactive Event Architecture”的缩写。我最近在用一个内部模拟项目做技术验证名字就叫“rea”专门用来折腾响应式事件驱动架构核心解决两个问题一是高并发下系统吞吐怎么提上去二是服务内部模块之间怎么做到真正的解耦和异步化。这篇文章就是把这轮验证的全过程、设计思路、还有踩过的坑都倒出来给正在做后端架构、对响应式编程感兴趣的朋友做个参考。先说结论整个原型做完之后同一台压测机器上TPS 从 800 左右涨到了 4200P99 延迟从 950ms 降到 180ms。这个提升当然离不开硬件和测试场景的特定条件但方向是对的从“调用”改成“响应”从“阻塞”改成“背压”带来的收益是实打实的。下面我就从项目起源、核心设计、实操过程到问题排查把这段经历完整拆开讲。1. 项目起源为什么非要做“rea”不可1.1 一次线上事故引出的思考事情要从一次模拟压测说起。当时我们内部搞了个高频订单写入场景前端提交订单、后端创建订单、扣库存、发通知、更新用户积分五个步骤全部在请求线程里同步串行执行。压测到 800 TPS 的时候接口平均响应时间还能看但一旦超过 1200 TPS数据库连接池先被打满紧接着接口超时消息队列里的积压量开始飙升最终整个链路雪崩。那次故障复盘时我们发现本质上不是数据库扛不住而是“请求线程生命周期”和“业务处理生命周期”绑得太死。一个订单请求进来Tomcat 线程要等订单入库完、等库存扣减完、等积分更新完一步都不结束它绝不释放线程。可实际上扣库存和发通知是可以在事务提交后异步做的用户根本不关心通知是不是在 100ms 内到达。用大白话说就是我们把所有事都当成“叫号办事”但实际很多业务步骤适合“广播之后各自去办”。1.2 “rea”到底要做什么为了让这种异步化、解耦的思路落到可验证的代码上我起了个代号叫“rea”全称 Reactive Event Architecture翻译过来是“响应式事件架构”。它要解决的是单体服务内部的模块通信问题订单模块完成创建后发布一个“订单已创建”的事件库存模块、积分模块、消息模块各自订阅这个事件并行处理互不等待。你可能会问这不就是消息队列吗确实Kafka、RabbitMQ 这些 MQ 也能做同样的事情但它们在单体应用内部杀鸡用牛刀了。引入独立 MQ 意味着多一套中间件要运维、多一份网络开销、多一类“消息丢失”和“重复消费”的排查难题。rea 这个方案第一步只在进程内做事件总线不引入外部依赖用 Java 的响应式流规范Reactive Streams和 Project Reactor 把事件发布、订阅、背压、线程调度都串起来让模块之间的通信完全变成异步非阻塞。1.3 方案选型为什么先做进程内事件总线选型的时候我也犹豫过直接上 MQ 不更成熟吗于是我把两者的适用场景拉了个对比例子对比项进程内事件总线独立消息队列部署依赖无额外组件需要单独搭建和运维通信延迟亚毫秒级到毫秒级毫秒到几十毫秒可靠性内存传递重启丢失磁盘持久化可回溯跨服务支持不支持天然支持运维成本极低较高rea 第一阶段的定位是“服务内部模块解耦”它追求的是低延迟和低运维成本所以进程内事件总线是合理选择。如果后续要扩展到跨服务的场景再在事件持久化、发送确认、消费位移这些机制上做加法也不迟。架构演进本来就是这样先用最小闭环验证价值再决定要不要扩大边界。2. 核心设计从“调用”到“反应”的思维转换2.1 事件驱动与命令调用到底差在哪传统方式里模块 A 要通知模块 B 做事直接调方法B.doSomething(data)调用方必须知道被调用方的类名、方法签名、异常类型这就是硬耦合。事件驱动则换了个姿势模块 A 只负责发布“某某事发生了”的事件它不认识也不关心谁在处理模块 B 只是默默订阅这类事件收到之后自己处理。这样一来A 和 B 之间被一个“事件总线”彻底隔开新增一个订阅者 CA 完全无感知。但这也引出一个重要原则发布方的事件命名和事件内容必须稳定。我见过很多半吊子事件驱动系统写着写着事件名变成了“doSomething”事件体里塞了一堆相关方才知道的内部字段结果订阅方一看事件就直接操作数据库等于换汤不换药还是命令调用。rea 里的做法是事件一律采用过去时命名比如“OrderCreatedEvent”事件体只包含已经发生的事实就是订单 ID、用户 ID、金额这些结果数据不包含“下一步要做什么”的指令字段。2.2 响应式流规范Publisher、Subscriber 和 Subscription 如何协作到了代码落地这块Journal of Reactive Streams 规范定义了四个接口Publisher 负责发布数据流Subscriber 负责订阅和消费Subscription 是两者的桥梁Processor 是既有发布又有订阅的中间物。它们之间的关系像一个水厂到用户的供水系统Publisher 是水厂Subscriber 是住户Subscription 是水管和水表负责“按需供水”。具体协作流程是Subscriber 调用subscribe()接入 PublisherPublisher 会回调 Subscriber 的onSubscribe()把 Subscription 对象交给 Subscriber。这时候关键动作来了Subscriber 通过subscription.request(N)主动向 Publisher 要 N 个数据Publisher 收到后最多发 N 个发完一个回调onNext()全部发完调onComplete()中途出错调onError()。也就是说谁消费谁决定流速这就是背压的雏形。2.3 背压机制为什么要落到业务层背压这个词光看文档挺抽象我换个说法一个生产者喷水一个消费者用水如果水池缓冲区不够大水就会溢出来。背压就是那个用于协调水压的阀门消费不过来就拧小阀门让生产者慢点。在 rea 里我特意把背压机制放在业务层而不是只靠框架默认参数原因是很多真实业务消费者会调用外部 API、写数据库、做复杂计算处理时长不是恒定的。如果用默认的无界缓冲瞬时冲进来十万个事件框架全给你塞进内存GC 和内存直接报警。我在压测中发现request请求的窗口大小设成 256 是比较稳的不会因为太大导致单批处理时间过长也不会因为太小导致生产者频繁被阻塞、事件吞吐上不去。提示背压不是框架替你扛内存问题的免死金牌核心还是要在业务代码里估算单个消费者的处理延迟再倒推合理的请求窗口大小。3. 实操过程从零搭一个“rea”最小可运行原型3.1 技术栈选择与依赖准备我选的是 Java 17 加 Project Reactor 的reactor-core库没有引入 Spring Boot纯粹为了把事件总线的核心逻辑跑起来。为什么不用 WebFlux因为 WebFlux 带了太多 Web 层的东西对于验证事件架构来说是噪音。你只需要在 pom 里加一个依赖dependency groupIdio.projectreactor/groupId artifactIdreactor-core/artifactId version3.5.11/version /dependency然后定义一个最基础的事件总线和事件包装。事件总线本质上就是一个Sinks.Many也就是 Reactor 里的多播发射器。多播意味着一个事件可以被多个订阅者同时收到而不是像Flux默认那样一个元素只能给一个消费者。3.2 定义领域事件与事件处理器第一步是定义事件本身。以订单场景为例我建了一个OrderCreatedEventpublic record OrderCreatedEvent(String orderId, Long userId, BigDecimal amount, LocalDateTime occurredAt) { }注意这里我用的是 record保证事件体是不可变的。为什么要不可变因为事件可能会被多个订阅者并发读到如果事件体可以随意修改就会出现一个订阅者改了字段、另一个订阅者读到脏数据的诡异问题。接着定义一个事件总线实例Sinks.ManyObject sink Sinks.many().multicast().onBackpressureBuffer(1000); FluxObject eventFlux sink.asFlux();onBackpressureBuffer(1000)表示背压窗口是 1000也就是说发布方最多领先订阅方 1000 个事件如果订阅方处理不过来发布方再发布就会被阻塞或触发丢弃策略。这个参数就是前文说的“水池容量”。然后注册两个订阅者分别处理积分更新和通知发送eventFlux .ofType(OrderCreatedEvent.class) .subscribe(event - { Thread.sleep(10); // 模拟调用外部积分接口 System.out.println(积分模块处理: event.orderId()); }); eventFlux .ofType(OrderCreatedEvent.class) .subscribe(event - { Thread.sleep(50); // 模拟发送通知 System.out.println(通知模块处理: event.orderId()); });因为是多播同一个OrderCreatedEvent会被两个订阅者分别收到互不干扰。发布事件就一行sink.tryEmitNext(new OrderCreatedEvent(ORD-1001, 123L, new BigDecimal(99.00), LocalDateTime.now()));到这里一个最小原型已经能跑了。你发布一条事件两个订阅者各自收到、各自延迟、各自完成。3.3 关键参数配置缓冲上限、线程调度和超时时间跑通之后我把注意力放到参数调优上。第一个要调的是缓冲上限。刚才的onBackpressureBuffer(1000)太粗糙我换成了可配置的BufferSize。我算了一下假设峰值每秒发布 5000 个事件单个消费者最慢需要 50ms 处理一条也就是每秒最多处理 200 条。那么瞬时积压量就是(5000 - 200) / 200 秒但并发下事情没那么简单。我的做法是先给一个经验值缓冲上限 单个消费者峰值处理速率 × 允许的最大积压时间。如果消费者峰值 200 条/秒允许积压 2 秒那么缓冲就是 400。这个值在压测中比较稳。太小了容易丢事件太大了内存消耗明显。第二个要调的是线程调度。默认情况下subscribe的处理器是在事件发布线程上执行的发布方会被订阅方的耗时拖累这等于没实现异步。我在实践里给每个订阅方指定了独立的线程池ExecutorService orderExecutor Executors.newFixedThreadPool(4); eventFlux .ofType(OrderCreatedEvent.class) .publishOn(Schedulers.fromExecutor(orderExecutor)) .subscribe(event - handleOrder(event));积分模块用了 4 个线程通知模块用了 2 个线程发布线程只负责往Sink里塞事件立刻返回不会被下游拖住。第三个是超时时间。响应式场景下的超时和普通接口超时不一样最好给Flux整体加一个timeout(Duration.ofSeconds(5))确保订阅方处理链路里任何一环卡死时上层能快速失败而不是无限等下去。我踩过没有超时配置的坑下游数据库连接池满了事件一直阻塞在等待线程里最后线程数飙到几百个直接把进程堆内存吃光。3.4 压测验证与结果记录原型搭建完我在同一台 8C16G 机器上做了对比压测场景是同步创建订单和基于 rea 的异步创建订单。同步链路就是最原始的创建订单 → 扣库存 → 更新积分 → 发通知全部线性执行。异步链路则是创建订单落库后发布OrderCreatedEvent库存、积分、通知各自订阅并行处理。指标同步链路rea 异步链路TPS8124236P99 延迟958ms183ms数据库连接占用4028线程峰值12856数据差别很大但我要泼盆冷水这个压倒性提升部分来自测试场景里“积分更新”和“通知发送”本来就是可以容忍延迟的。如果核心事务里的每一步都强依赖结果比如必须扣完库存才能给用户发货那异步化反而会带来一致性负担。所以 rea 的适用场景一定是“非核心路径可以异步化”的业务。4. 踩坑实录三起真实事故与排查流程4.1 背压设置过小导致大量请求被丢弃第一个坑从配置上就能看出来。我把缓冲上限从 1000 调到 256 后顺手跑了一轮压测结果发现订单总量比发布总量少了几千条。刚开始我以为消息丢了查了半天才发现是丢弃策略的问题。Sinks.EmitFailureHandler在缓冲区满的时候如果返回FAIL_OVERFLOW新事件会被直接扔掉。排查思路是先在tryEmitNext返回结果处打日志发现返回FAIL_OVERFLOW的数量和丢失数量对得上。修复方案有两种一是调大缓冲二是改为等待重试。生产环境里我更倾向于后者用sink.tryEmitNext配合自旋重试像下面这样for (int i 0; i 3; i) { var result sink.tryEmitNext(event); if (result.isFailure()) { Thread.sleep(10); continue; } break; }4.2 事件处理线程池耗尽引发的级联故障这个坑最隐蔽。当时我给积分模块开了 4 个线程正常压测没问题但某次模拟外部积分接口响应变慢从 10ms 变成 800ms 之后积分模块的 4 个线程全被占住队列里积压的事件越来越多缓冲很快被打满。更恶性的是由于积分模块处理慢发布线程也被阻塞订单创建接口也跟着变慢整个链路开始大面积超时。那次修复让我意识到响应式架构里每一个订阅者都必须当成独立的服务来做隔离。不能共享线程池也不能无脑调大线程数。后来我给每个订阅者单独配了线程池和队列并加上最大并发限制类似flatMap(concurrency8)。同时给外部调用加了重试和熔断逻辑模拟外部接口变慢时直接走降级方案而不是让线程池跟着遭殃。注意很多新手以为异步化就是把所有事丢到线程池里慢慢跑但真正的响应式设计必须考虑下游故障的隔离。如果下游依赖不稳定线程池隔离和熔断一定是标配。4.3 循环事件导致的死锁与栈溢出第三个坑比较烧脑。有一次我为“用户积分变动”事件加了一个监听器在监听器里又调用了“用户积分变动”的发布逻辑理论上同一事件会再次触发监听器导致无限循环。但由于 Reactor 的多播是异步的它没有立刻爆栈而是让线程池被打满CPU 飚到 100%事件在循环链路里反复嵌套最后把内存撑爆。这个问题的排查花了一天。我先把事件链路日志打出来发现同一个userId的事件在日志里出现了几十次每次嵌套一层业务调用才定位到循环发布。修复上我做了两件事一是约定“事件处理器里不允许再发布同类型事件”代码评审时重点检查二是给事件透传一个traceId并在订阅端加一个重复处理次数上限超过 5 次直接告警并丢弃。如果你想自己验证这种问题也可以在事件对象里加一个eventDepth字段每次重新发布前判断深度超过阈值就抛异常或降级。简单粗暴但有效。5. 经验总结与后续扩展5.1 事件风暴与命名规范才是落地的关键做了这一轮实验之后我最大的感受是技术上的 Publisher、Subscriber 都好写真正难的是业务事件的建模。你必须在前期搞清楚哪些业务动作会产生事件哪些模块关心这些事件事件和事件之间有没有先后依赖。我们当时花了一个下午做事件风暴把订单、用户、库存、营销的流程全画在纸上最后整理出了 20 多个事件。这件事比写代码重要得多。事件命名上我推荐一律使用过去式动词加领域名词比如OrderCreatedEvent、InventoryDeductedEvent、PointsAccruedEvent。不用现在时也不用祈使句因为你发布的是“已经发生的事实”不是“请你去做什么”。这个习惯能从根源上避免把事件总线用成命令调用。5.2 幂等消费和事件版本化要提前设计进程内事件总线虽然消息不会跨进程丢失但订阅者业务逻辑里可能存在重试和补偿操作重复处理同一个事件的风险依然存在。我在积分模块里加了幂等表用orderId加eventType做唯一索引保证同一个订单的积分事件最多生效一次。另外一个很容易被忽略的点是事件版本化。业务事件结构一定会变比如OrderCreatedEvent早期只有订单号和金额后面加了折扣字段。如果订单模块先升级、积分模块后升级新消费者处理旧事件时可能因为缺字段直接 NPE。我的做法是事件 record 里加一个version字段Stage 1 的事件 version1增加字段后 version2订阅方同时兼容两个版本待所有消费者都升级后再把旧版本过滤掉。5.3 后续扩展持久化事件存储与跨进程分发rea 目前只是进程内验证后续如果要扩展到多个独立服务之间需要补齐两块能力一是事件持久化把事件先写入数据库或日志文件再异步转发防止进程重启导致的内存事件丢失二是跨进程分发用独立消息中间件或数据库的发布订阅机制做事件路由。我个人更倾向于用事务性发件箱模式业务事务提交时把要发布的事件同时写入一张发件箱表然后由一个后台任务扫表并发送事件发送成功后再标记已发布。这样既能保证事件不丢又不需要依赖外部 MQ 的分布式事务实现上比较轻。最后再分享一个小技巧做响应式架构验证时千万别一上来就追求完美先把最简单的单机事件总线跑通用压测数据说服自己和团队再逐步加持久化和扩展。我见过太多团队第一次就上完整套框架结果连背压和线程池都没弄明白出了问题根本无从排查。先让一个小小的“rea”在项目里站住脚比画一张大而全的架构图有用得多。

相关新闻

TensorFlow工程化实战:从模型开发到高稳上线

TensorFlow工程化实战:从模型开发到高稳上线

1. 这不是“学个框架”那么简单:TensorFlow到底在解决什么问题?很多人第一次听说TensorFlow,是在某次技术分享会上听到“谷歌开源的深度学习框架”,或者在招聘JD里看到“熟悉TensorFlow者优先”。但如果你真去翻它的GitHub仓库、读…

2026/10/15 6:29:45 阅读更多 →
TensorFlow实战:从零实现RNN字母预测模型

TensorFlow实战:从零实现RNN字母预测模型

1. 从"字母预测"这个经典命题说起如果你正在学深度学习,大概率绕不开循环神经网络(RNN)这个坎。而"字母预测"几乎是所有人接触RNN时的第一个实战项目——给定一串字符序列,让网络学会预测下一个字母是什么。这…

2026/10/15 6:29:45 阅读更多 →
大模型上下文模式实战:四种记忆方案与避坑指南

大模型上下文模式实战:四种记忆方案与避坑指南

我上个月排查一个个人助理类应用的线上问题,用户反馈说机器人“越聊越笨”:上午聊好的需求,下午再问,它已经彻底忘了;有时候又会突然冒出一段之前完全无关的讨论,把当前的问题带偏。我翻了日志,…

2026/10/15 6:29:45 阅读更多 →

最新新闻

Studio Pro 8实战:编曲、直播机架与实时调音三合一攻略

Studio Pro 8实战:编曲、直播机架与实时调音三合一攻略

先说个真实情况:最近好几个朋友都在找“Studio Pro 8”,有的是想拿来编曲,有的是想搭直播机架,还有的只是想给唱歌效果做实时美化。我拿到这套软件在Mac和Windows两台机器上都实测了一段时间,有些体会还挺反直觉的——…

2026/10/15 7:23:56 阅读更多 →
Spring Boot会员管理系统毕设实战:从数据库设计到JWT鉴权部署

Spring Boot会员管理系统毕设实战:从数据库设计到JWT鉴权部署

“名士会所会员管理系统”,光看这个标题你可能觉得就是一个普通的Spring Boot CRUD项目,但我做完这套毕业设计源码之后最大的感受是:它其实是一个把业务复杂度控制得恰到好处的“小型企业级系统”。会所行业的会员管理,表面上只有…

2026/10/15 7:23:56 阅读更多 →
CSP-J 2024 真题 地图探险 题解(普及-)|从暴力到正解全讲一遍

CSP-J 2024 真题 地图探险 题解(普及-)|从暴力到正解全讲一遍

继续刷 CSP-J 2024 真题。这题考点是暂无评定,先把题意吃透。 题目来源:ACGO A34462 | 洛谷 P11228难度定位:普及- | 通过率:47.16% 一、题目信息 题目名称:地图探险 所属赛事:2024 …

2026/10/15 7:23:56 阅读更多 →
吊装安全生命线受力分析模块:原理与应用

吊装安全生命线受力分析模块:原理与应用

1. 吊装安全的核心防线:生命线受力分析模块解析在重型设备吊装作业现场,生命线(绳)作为最后一道安全屏障,其可靠性直接关系到高空作业人员的生命安全。传统经验判断法已无法满足现代工程对精确计算的需求,这…

2026/10/15 7:23:56 阅读更多 →
SpringBoot+MySQL实现4S店车辆租售一体化管理系统全流程拆解

SpringBoot+MySQL实现4S店车辆租售一体化管理系统全流程拆解

1. 项目概述与需求拆解1.1 这类系统到底解决了什么现实问题先聊一个我在带毕设和做外包时反复遇到的现象:很多同学一听到"汽车销售管理系统",第一反应就是"无非是车辆信息的增删改查"。但真正走进一家4S店或者二网经销商去看&#x…

2026/10/15 7:23:56 阅读更多 →
DeepSeek本地部署硬件要求与环境配置:显存估算、CUDA对齐与推理框架选型

DeepSeek本地部署硬件要求与环境配置:显存估算、CUDA对齐与推理框架选型

简介:这份PDF资料聚焦DeepSeek大模型的本地化部署,面向希望在私有环境中运行开源大模型的开发者、运维人员及企业技术团队,帮助解决硬件选型与软件环境配置两大核心难题。资源包内含1个PDF文件,大小约274KB,内容按硬件…

2026/10/15 7:22:56 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →