从55秒到1秒:MES Agent性能优化实战与Trace驱动排查方法
1. 从 55 秒到 1 秒这个优化到底动了谁的蛋糕先说结论一个原本跑一次要 55 秒的 MES Agent 任务最后压到了 1 秒出头。这不是靠换机器、加内存、堆并发这种“大力出奇迹”的路子而是靠一次完整的 Trace 驱动排查把真正的时间黑洞找出来然后精准下刀。MES 这个词做制造业数字化的朋友都不陌生制造执行系统车间里管工单、管物料、管设备、管质量的那套东西。而 MES Agent 通常是挂在产线边缘侧的一个采集与执行代理负责跟设备打交道、跟上层系统同步数据、执行调度指令。它跑得慢影响的不只是“等一会儿”而是整条产线的节拍、数据回传的实时性甚至直接影响看板刷新和异常响应。我接手这个优化任务的时候拿到的是一个很典型的场景Agent 每次执行一轮完整任务从触发到返回稳定在 55 秒左右。业务侧反馈“能用但太慢”运维侧反馈“CPU 和内存都不高看不出瓶颈”。这就是最难受的情况——资源指标一切正常但就是慢。很多人到这一步就开始猜是不是网络慢是不是数据库慢是不是对方接口慢猜来猜去改了一堆配置最后发现根本没打到点上。这篇文章我想完整复盘这次排查的全过程。核心不是告诉你“把某个参数从 A 改成 B”而是让你学会一套可复用的思路当资源指标正常但耗时异常时怎么用 Trace 把时间拆开怎么区分“真耗时”和“假耗时”怎么找到那个吃掉 90% 时间的元凶。适合所有做后端服务、边缘计算、系统集成、性能优化的朋友看哪怕你没接触过 MES这套方法照样能用在你自己的项目上。我先把这次优化的关键数据摆出来让你有个直观感受阶段单轮耗时主要瓶颈优化前约 55 秒串行等待 重复初始化定位后约 8 秒去掉无效等待最终约 1.1 秒缓存 并行 懒加载从 55 到 1中间不是一步到位的而是分了三刀。每一刀砍在哪里为什么这么砍后面会一节一节拆开讲。这里先埋一个伏笔真正的大头不是你以为的“计算慢”而是“等待慢”而且是一种非常隐蔽的等待。2. 为什么资源指标正常任务却慢得离谱2.1 先排除“硬件不够”这个伪命题遇到慢很多人的第一反应是加资源。我见过太多团队一慢就扩容CPU 从 4 核加到 16 核内存从 8G 加到 32G结果耗时纹丝不动。为什么因为如果瓶颈是 IO 等待、锁竞争、串行阻塞你加再多 CPU 也没用那些核心大部分时间都在闲着。这次我做的第一件事就是看资源曲线。观察窗口内Agent 进程的 CPU 占用峰值不到 15%内存平稳没有明显 GC 抖动磁盘 IO 也很低。这就基本可以判定这不是计算密集型问题而是等待型问题。等待型问题的特点是线程大部分时间处于阻塞状态可能在等网络、等锁、等定时器、等一个根本没必要等的条件。提示判断等待型还是计算型最简单的办法就是看 CPU 利用率。CPU 高但慢是算得太多CPU 低但慢是等得太久。两者的优化方向完全相反。2.2 等待型慢的三种常见伪装等待型问题之所以难查是因为它会伪装。我总结了三类最常见的伪装第一类是网络等待伪装成业务处理。比如调用一个下游接口日志里只记了“开始调用”和“调用结束”中间那 3 秒到底是网络传输慢还是对方处理慢还是本地序列化慢根本分不清。第二类是锁等待伪装成正常执行。多个线程抢一把锁抢不到的就阻塞但日志上看不出谁在等锁只看到整体耗时变长。第三类是初始化等待伪装成首次执行。很多组件第一次用的时候要加载配置、建连接、预热缓存这些开销被算进了业务耗时里但其实是“一次性成本”。这次的问题恰好是第二类和第三类的混合体而且藏得很深。如果只靠看日志和猜基本不可能定位到。2.3 为什么必须上 Trace而不是靠日志日志是“点”Trace 是“线”。日志告诉你某个时刻发生了什么Trace 告诉你一段时间内各个操作的先后顺序和耗时占比。当你要回答“这 55 秒到底花在哪了”这种问题时日志给不了答案只有 Trace 能给。打个比方日志像是路口的监控截图你能看到某辆车经过了Trace 像是行车记录仪你能看到这辆车从哪来、到哪去、在哪个路口堵了多久。排查性能问题你要的是行车记录仪不是截图。我这次用的是一套标准的分布式追踪方案核心就是给每个关键操作打上 Span记录开始时间、结束时间、父子关系。跑一轮任务导出一条完整的 Trace然后按耗时排序谁是大头一目了然。下面这张表是我第一轮 Trace 跑出来的结果注意看耗时分布Span 名称耗时占比类型任务总执行55.2s100%根 Span设备数据采集48.6s88%子 Span数据上报3.1s5.6%子 Span本地计算1.8s3.3%子 Span其他1.7s3.1%子 Span看到没设备数据采集一个环节就吃掉了 88% 的时间。这时候方向就非常明确了不用再纠结上报和计算先把采集这块啃下来。3. 用 Trace 把 55 秒拆开时间到底花在哪了3.1 给关键路径埋点的正确姿势埋点这件事讲究“够用就好”不是越多越好。埋太多Trace 本身的开销就上来了而且数据噪音大反而看不清。我的原则是只埋关键路径上的操作以及所有可能阻塞的操作。具体到这次我在几个地方埋了点任务入口和出口作为根 Span每个设备连接的建立和释放每次数据读取的开始和结束每次下游调用的开始和结束每次本地缓存读写的开始和结束埋点的时候有个细节要注意Span 的命名要能区分实例。比如“读取设备数据”这个 Span如果有 20 个设备你不能都叫同一个名字否则 Trace 里全是重名的根本分不清是哪个设备慢。我的做法是带上设备标识比如read_device_data.dev_07这样一眼就能看出是哪个设备拖了后腿。3.2 第一轮 Trace 暴露的真相串行采集是元凶第一轮 Trace 跑完我把采集环节的 Span 按耗时排了个序结果很有意思设备编号单次采集耗时备注dev_012.4s正常dev_022.6s正常dev_032.5s正常.........dev_182.7s正常dev_192.5s正常dev_202.6s正常20 个设备每个采集耗时都在 2.5 秒左右加起来正好 50 秒上下。问题来了这 20 个设备是串行采集的。一个采完再采下一个20 乘 2.5就是 50 秒。这就是那 88% 的来源。但这里有个更深的问题单个设备采集 2.5 秒合理吗如果这 2.5 秒里大部分是有效工作那并行化之后总耗时也就 2.5 秒左右如果这 2.5 秒里大部分是无效等待那还有进一步压缩的空间。所以我做了第二轮更细的 Trace把单个设备采集的内部再拆开。3.3 第二轮 Trace单个设备内部还有猫腻把单个设备采集的 Span 再往下拆一层结果让我有点意外子操作耗时占比建立连接1.8s72%发送请求0.1s4%等待响应0.4s16%解析数据0.2s8%单个设备采集 2.5 秒其中 1.8 秒花在“建立连接”上占了 72%。也就是说真正干活的时间只有 0.7 秒剩下 1.8 秒全在建立连接。这就是典型的“初始化等待伪装成业务处理”。为什么每次采集都要重新建立连接因为代码里是这么写的每次采集任务开始时为每个设备新建一个连接采完就关掉。这种写法在设备少、频率低的时候没问题但在 20 个设备、高频采集的场景下连接建立的开销就被放大了 20 倍。到这里优化的方向就非常清晰了分两步走把串行改并行20 个设备的采集同时进行总耗时从 50 秒降到接近单个设备的耗时。把连接复用起来不要每次重建把 1.8 秒的连接开销省掉。这两步做完理论上采集环节能从 50 秒降到 1 秒以内。但实际做的时候还有不少坑下面细说。4. 三刀下去从 55 秒砍到 1 秒的完整实操4.1 第一刀连接池化砍掉 1.8 秒的重复建连连接池这个东西做后端的人都不陌生数据库连接池、HTTP 连接池原理都一样连接建立一次反复使用避免重复的握手开销。但连接池不是随便配配就行的有几个参数必须想清楚参数含义我的取值取值理由最大连接数池里最多放多少连接2520 个设备 5 个余量最小空闲连接保持多少个常驻连接20覆盖全部设备避免冷启动获取连接超时拿不到连接等多久500ms超过就报错不无限等空闲回收时间空闲多久回收300s采集频率高不宜太短这里有个经验最小空闲连接数要覆盖你的常规并发量。如果设得太小高峰期还是要临时建连等于没池化。我一开始设成 5结果 Trace 显示高峰期还是有大量建连操作后来改成 20 才稳定下来。还有一个坑连接池里的连接是会失效的。设备侧可能因为各种原因断开连接如果池子里拿出来的是一条死连接请求就会失败。所以必须配一个连接有效性检测拿连接的时候先 ping 一下不通就丢弃重建。这个检测本身也有开销所以检测频率要控制好不能每次拿都全量检测。改完连接池单设备采集耗时从 2.5 秒降到了 0.7 秒左右。20 个设备串行的话是 14 秒比原来的 50 秒好多了但还不够。4.2 第二刀并行采集把 20 个 0.7 秒叠成 1 秒串行改并行听起来简单做起来有几个必须考虑的点。第一并发度设多少。不是越大越好。设备侧可能有连接数限制Agent 侧线程池也有开销。我的做法是先设成 10跑一轮看 Trace发现设备侧响应正常没有超时再逐步加到 20。最终用 20 个并发正好一个设备一个线程总耗时接近单个设备的 0.7 秒。第二异常怎么处理。并行之后某个设备失败不能影响其他设备。我用的是“收集所有结果失败的单独记录”而不是“一个失败全部回滚”。因为采集场景下20 个设备里坏一个不应该让另外 19 个的数据也丢了。第三超时怎么设。并行场景下超时设置尤其重要。如果某个设备卡住不返回不能让它拖死整个任务。我给每个采集任务设了 2 秒超时超过就标记失败继续等其他设备。这样最坏情况下总耗时也不会超过 2 秒。改完并行采集环节从 14 秒降到了 0.8 秒左右。加上上报和计算总耗时降到了 8 秒上下。这时候业务侧已经觉得“快多了”但我知道还有空间。4.3 第三刀懒加载与缓存把 8 秒压到 1 秒剩下的 8 秒里上报占 3 秒计算占 1.8 秒还有一些零散开销。继续用 Trace 拆发现两个问题问题一配置每次任务都重新加载。Agent 每次执行任务都会重新读一遍配置文件、重新解析一遍设备清单。这些数据在运行期基本不变完全没必要每次读。改成启动时加载一次之后走内存缓存这块省了大约 2 秒。问题二上报数据做了全量序列化。每次上报都把全部字段序列化一遍包括很多不变的元数据。改成增量上报只传变化的部分序列化开销降了一大半上报从 3 秒降到 0.5 秒。问题三部分计算可以懒执行。有些统计指标不是每次任务都必须算的改成按需计算用到才算又省了 1 秒左右。这三项加起来总耗时从 8 秒压到了 1.1 秒。到这一步我认为已经达到了一个比较理想的平衡点再往下压边际收益递减而且会增加代码复杂度不划算。下面是三刀之后的完整对比优化阶段采集上报计算总计优化前48.6s3.1s1.8s55.2s连接池后14.0s3.1s1.8s20.6s并行后0.8s3.1s1.8s8.4s缓存懒加载后0.8s0.5s0.3s1.1s5. 优化过程中踩过的坑和反直觉的发现5.1 并行度不是越高越好我在这栽过刚改并行的时候我想当然地把并发度设成了 50觉得越多越快。结果 Trace 显示总耗时反而从 0.8 秒涨到了 3 秒多。为什么因为线程池本身有调度开销50 个线程抢 20 个设备的任务上下文切换频繁而且设备侧面对 50 个并发连接响应也变慢了。后来我把并发度降到 20正好一个设备一个线程没有争抢耗时反而最低。这个教训是并发度的甜点值通常等于你的任务数而不是越大越好。超过任务数的并发纯属浪费。5.2 连接池的空闲回收差点让我背锅连接池配了空闲回收之后我观察了几天发现一个诡异现象每天早上第一轮任务总是特别慢要 5 秒多之后才恢复正常。Trace 一看早上第一轮有大量建连操作。原因是夜间任务少连接池把空闲连接都回收了早上第一轮要重新建连。解决办法很简单把最小空闲连接数设成覆盖常规并发量并且把空闲回收时间调长。这样夜间也保持足够连接早上不用重建。这个坑很隐蔽如果不看 Trace根本想不到是回收策略的问题。5.3 缓存带来的数据一致性问题加缓存之后我遇到一个新问题改了配置但 Agent 还是用旧配置跑。因为配置缓存在内存里不重启不刷新。这在调试阶段特别坑改了半天没生效以为代码写错了。后来我加了一个配置版本号机制配置中心每次变更版本号加一Agent 定期拉版本号发现变了就刷新缓存。这样既保留了缓存的高效又保证了配置能及时生效。这个设计后来成了标配所有缓存数据都走这套机制。5.4 一个反直觉的发现日志本身也是性能杀手排查后期我发现一个之前完全没注意的点日志打印本身消耗了不少时间。原来代码里在循环里打日志20 个设备每个打十几条一轮下来几百条日志同步写磁盘开销不小。改成异步日志 降低日志级别之后又省了大约 0.3 秒。这个发现让我意识到性能优化到最后往往拼的是这些不起眼的细节。日志、序列化、字符串拼接单看都不起眼累积起来很可观。6. 这套 Trace 排查方法怎么复用到你自己的项目6.1 排查顺序先粗后细逐层下钻这次排查最大的收获是验证了一套“先粗后细”的下钻方法。具体来说第一步跑一轮完整 Trace看根 Span 下面哪个子 Span 耗时占比最大。这一步不用细只要找到大头就行。第二步把大头那个 Span 再往下拆一层看它内部又是哪个操作占大头。第三步重复第二步直到找到那个“不可再分”的耗时操作。这次就是总任务 55 秒 → 采集 48 秒 → 单设备建连 1.8 秒 → 定位到重复建连。每一层都只问一个问题“这里面谁最慢”问三次答案就出来了。6.2 判断“真耗时”和“假耗时”的标准不是所有耗时都值得优化。我判断的标准是真耗时这个操作在干实事比如传输数据、计算指标。优化它需要改算法或换方案成本高。假耗时这个操作在等待或重复比如等锁、等超时、重复初始化。优化它通常只是改配置或改调用方式成本低收益高。优先干掉假耗时。这次的三刀砍的全是假耗时重复建连、串行等待、重复加载。真耗时实际的数据传输和计算其实只有 1 秒左右那才是这个任务的真实成本。6.3 优化到什么程度就该停手优化不是越极致越好。我的停手标准有三个第一达到业务预期。业务侧要求 3 秒内我做到 1 秒已经超额没必要为了 0.5 秒再折腾。第二边际收益递减。从 55 秒到 8 秒改动很小从 8 秒到 1 秒改动开始变多再往下可能要重构架构收益却只有零点几秒不划算。第三复杂度可控。优化引入的缓存、并行、连接池都是成熟方案维护成本低。如果为了性能引入一套复杂的自定义框架那就要慎重了。6.4 一份可复用的排查清单最后把我这次用的排查清单整理出来你可以直接拿去用检查项判断方法常见问题CPU 利用率看监控曲线低 CPU 高耗时 等待型耗时分布Trace 按耗时排序找占比最大的 Span串行操作看是否有循环内调用循环内 IO 是重灾区重复初始化看是否有重复建连/加载连接池、缓存可解锁竞争看阻塞时间缩小锁粒度或改无锁日志开销看日志量和级别异步日志 降级序列化开销看数据量增量序列化这套方法不限于 MES Agent任何后端服务、边缘程序、数据处理任务都能用。核心就一句话别猜用 Trace 把时间拆开看。你猜十次不如 Trace 跑一次。我在实际项目里反复验证过凡是“资源正常但就是慢”的问题九成以上都能用这套方法定位到。剩下的那一成通常是更底层的系统问题那就需要另一套工具了。但至少先把这九成解决掉你的项目就已经快了一大截。

相关新闻

SpringBoot 集成 Activiti 7 实战:工作流引擎落地审批全流程

SpringBoot 集成 Activiti 7 实战:工作流引擎落地审批全流程

团队里新来的同事看我在跑一个审批流程,问了一句:“这不就是请假状态机吗?加一摞 if else 不也能做?”我笑了笑,说实话很久以前我也这么想。可当你碰到的业务从“提交-审批”变成五级审批、会签、驳回、加签、流程版本…

2026/10/16 15:15:34 阅读更多 →
单链表底层原理与工程实践:从内存布局到LRU缓存

单链表底层原理与工程实践:从内存布局到LRU缓存

1. 为什么单链表是程序员绕不开的第一道“硬坎” 刚接触数据结构时,我见过太多人把单链表当成“老古董”——不就是几个指针连来连去?背个插入删除口诀,应付完考试就扔进回收站。直到某次在某公司做性能优化项目,一个本该毫秒级响…

2026/10/16 15:15:34 阅读更多 →
算法笔试经典题:为什么全相等的最优解是中位数?

算法笔试经典题:为什么全相等的最优解是中位数?

先交代一下背景:2026年春招笔试里,算法岗这套卷子的第一题叫“全相等”,题目本身不难,但它出现在卷首,承担的任务很特殊——把心态稳住、把基础盘接住。我陪人复盘时发现,不少同学在五分钟内能写出能过的代…

2026/10/16 15:14:33 阅读更多 →

最新新闻

EN 300 132-1接口A1标准解读:电压暂降、冲击电流与保护器件选型实战

EN 300 132-1接口A1标准解读:电压暂降、冲击电流与保护器件选型实战

简介:《ETSI EN 300 132-1 V2.1.1(2019-03)》是欧洲电信标准协会发布的环境工程与信息通信技术(ICT)设备电源接口标准,其第1部分规定了交流电输入接口的电压范围、电流等级、插头插座类型、过载及接地保护等…

2026/10/16 16:23:15 阅读更多 →
NASA CEA程序全解析:从化学平衡到火箭比冲计算实战

NASA CEA程序全解析:从化学平衡到火箭比冲计算实战

简介:CEA(Chemical Equilibrium with Applications)是NASA开发的热化学平衡计算程序,能模拟火箭发动机燃烧室内的高温高压化学反应,求解平衡组分、温度分布及焓熵等热力学参数,常被用于不同燃料/氧化剂组合…

2026/10/16 16:23:15 阅读更多 →
云原生入门:用Minikube跑通自愈型Hello World

云原生入门:用Minikube跑通自愈型Hello World

简介:本资源是一份系统化、分层级的云原生技术学习路线图,面向开发者、运维工程师及希望转型云原生领域的IT从业者,解决技术栈庞杂、学习路径不清晰、各组件关系难把握等核心痛点。路线图PDF文件共1个,大小1.29MB,内容…

2026/10/16 16:23:15 阅读更多 →
职业心理测评系统源码怎么选?二次开发避坑指南

职业心理测评系统源码怎么选?二次开发避坑指南

简介:这是一套面向职业心理测评场景的完整管理系统源码,适合需要搭建或二次开发测评平台的开发者、企业HR与就业指导机构。系统围绕职业倾向、性格特征、工作适应力等维度,覆盖用户认证、测评数据采集、结果生成、反馈展示,以及用…

2026/10/16 16:23:15 阅读更多 →
免停电免布线的开关柜局放在线监测:工程落地与选型要点

免停电免布线的开关柜局放在线监测:工程落地与选型要点

前阵子到一个工厂配电房看现场,配电柜的母线室局部放电量已经连续三次巡检超过告警线,运维负责人跟我说得很直白:柜子不敢停,负荷没法切;柜内二次小室塞满了,电缆沟和桥架也走不了线,厂里一年只…

2026/10/16 16:23:15 阅读更多 →
排序算法全解析:复杂度、稳定性与工程实战选型指南

排序算法全解析:复杂度、稳定性与工程实战选型指南

直接进入正题。这一章“排序”,可以说是整个数据结构课程里最实用、也最容易被轻视的一章。说它实用,是因为不管你以后写业务代码还是做底层框架,排序都逃不掉;说它被轻视,是因为很多人背了快排的代码就以为掌握了排序…

2026/10/16 16:22:13 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

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

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →