news 2026/9/4 18:33:20

RocketMQ LiteTopic技术解析:海量Topic场景下的资源优化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RocketMQ LiteTopic技术解析:海量Topic场景下的资源优化实践

这次我们来看一个关于 RocketMQ 核心特性“LiteTopic”的技术解析。对于正在使用或计划使用 RocketMQ 的开发者来说,理解 LiteTopic 的设计理念和优势,是优化消息队列架构、提升系统性能的关键一步。它并非一个全新的独立产品,而是 RocketMQ 5.0 版本引入的一项旨在解决特定场景下 Topic 资源消耗问题的核心优化特性。

简单来说,LiteTopic 的核心目标就是“轻量化”。在传统的消息队列模型中,每个 Topic 都会消耗相对固定的存储和内存资源,当系统需要创建成千上万个 Topic 时(例如在 SaaS 多租户、物联网设备通信等场景),资源开销会变得非常巨大。LiteTopic 通过共享底层物理存储、优化元数据管理等手段,大幅降低了单个 Topic 的资源占用,使得在同等硬件条件下支持更多 Topic 成为可能。

本文将带你深入理解 LiteTopic 的三大核心优势,并通过一套可落地的操作流程,从环境准备、配置验证到性能观察,让你不仅能“听懂”,更能“动手验证”其效果。无论你是正在为海量 Topic 的管理成本发愁,还是希望为未来的系统扩展性提前布局,这篇文章都值得你仔细阅读并动手实践。

1. 核心能力速览

在深入细节之前,我们先通过一个表格快速把握 LiteTopic 的关键信息,这能帮助你快速判断它是否是你当前需要的解决方案。

能力项说明
所属项目Apache RocketMQ 5.0 及以上版本
核心特性轻量级 Topic,用于优化海量 Topic 场景下的资源消耗
主要优势1.存储资源节省:多个 LiteTopic 共享底层 CommitLog 存储。
2.内存占用降低:大幅减少每个 Topic 的元数据内存开销。
3.运维复杂度下降:简化海量 Topic 的创建、管理和监控。
适用场景SaaS 多租户隔离、物联网设备通信、微服务按业务细分消息通道等需要创建大量 Topic 的场景。
启动/启用方式在 Broker 配置文件中开启特性,在创建 Topic 时指定 Topic 类型为LITE
资源占用对比相比普通 Topic,LiteTopic 的元数据内存占用可降低一个数量级,存储为共享模式。
兼容性与普通 Topic 的 API 接口完全兼容,对生产者/消费者透明。
是否支持批量操作支持通过管理命令或 API 批量创建、查询和删除 LiteTopic。

2. 适用场景与使用边界

理解一个技术特性的最佳方式,是明确它解决什么问题,以及在什么情况下可能不适用。

LiteTopic 最适合谁?

  1. SaaS 平台开发者:需要为成千上万个租户提供独立的、逻辑隔离的消息通道,每个租户对应一个 Topic。使用普通 Topic 会导致元数据爆炸,而 LiteTopic 能完美控制资源成本。
  2. 物联网平台架构师:需要为每台设备或每组设备设立独立的命令下发或数据上报通道。设备量动辄百万千万,LiteTopic 是实现这一架构的可行基础。
  3. 复杂微服务系统维护者:系统内部分工极细,不同业务、不同链路希望使用独立的 Topic 进行解耦,导致 Topic 数量快速增长,运维压力大。

LiteTopic 能解决什么问题?

  • 资源成本问题:显著降低海量 Topic 对 Broker 节点内存(尤其是堆外内存)的占用。
  • 存储效率问题:通过共享存储,避免为每个低活跃度的 Topic 预分配和独占物理存储空间,提升磁盘利用率。
  • 运维管理问题:简化海量 Topic 的日常管理、监控和问题排查流程。

LiteTopic 不适合什么场景?

  • 超高吞吐的单一 Topic:如果一个 Topic 承担了系统绝大部分的流量,那么使用普通 Topic 即可,无需引入 LiteTopic 的共享机制。
  • 需要独立存储策略的 Topic:如果某些 Topic 因数据安全、生命周期或性能隔离等原因,必须使用独立的存储设备或策略,则不适合配置为 LiteTopic。
  • RocketMQ 5.0 以下版本:该特性在 5.0 版本引入,旧版本无法使用。

重要边界与提醒

  • 功能完整性:LiteTopic 在消息收发、顺序消息、事务消息等核心功能上与普通 Topic 保持一致,但某些极端高级特性(如与特定存储插件的深度定制)可能需要额外验证。
  • 监控差异:由于存储共享,监控视角可能需要从“单个Topic”部分转移到“Broker节点整体”和“LiteTopic资源组”层面。

3. 环境准备与前置条件

要验证 LiteTopic,你需要一个 RocketMQ 5.0+ 的环境。以下是两种最常用的准备方式。

方案一:本地快速启动(使用 Docker)这是最快的方式,适合功能验证和开发测试。

  1. 安装 Docker:确保你的桌面或服务器已安装 Docker 及 Docker Compose。
  2. 获取镜像:拉取 RocketMQ 5.x 的官方镜像或包含控制台的集成镜像。
    docker pull apacherocketmq/rocketmq:5.1.4
  3. 准备磁盘目录:在宿主机上创建用于持久化存储和日志的目录,例如~/rocketmq-data

方案二:Linux 服务器部署更贴近生产环境的部署方式。

  1. 操作系统:CentOS 7+ 或 Ubuntu 18.04+。
  2. Java 环境:安装 JDK 8 或 JDK 11(推荐 JDK 11),并配置JAVA_HOME
    # 以 Ubuntu 安装 OpenJDK 11 为例 sudo apt update sudo apt install openjdk-11-jdk java -version # 验证安装
  3. 下载 RocketMQ:从 Apache 官网或镜像站下载 RocketMQ 5.x 二进制发行版。
    wget https://archive.apache.org/dist/rocketmq/5.1.4/rocketmq-all-5.1.4-bin-release.zip unzip rocketmq-all-5.1.4-bin-release.zip cd rocketmq-all-5.1.4-bin-release
  4. 系统参数调整(重要):RocketMQ Broker 需要较多的内存和文件描述符。
    # 编辑 /etc/security/limits.conf,添加 * soft nofile 655350 * hard nofile 655350 * soft nproc 4096 * hard nproc 4096 # 编辑 /etc/sysctl.conf,调整 vm 参数 vm.max_map_count=262144 vm.swappiness=0 # 使配置生效 sysctl -p # 需要重新登录会话以使 limits 生效

4. 安装部署与启动方式

我们以 Linux 服务器部署为例,演示如何启动一个支持 LiteTopic 的 RocketMQ 集群(单节点模拟)。

步骤1:修改 Broker 配置进入 RocketMQ 解压目录的conf文件夹,复制一个 Broker 配置文件。

cd conf cp broker.conf broker-lite.conf

编辑broker-lite.conf,确保包含以下关键配置以启用 LiteTopic 支持:

# Broker 集群名称 brokerClusterName = DefaultCluster # Broker 名称 brokerName = broker-lite # Broker ID,0 表示 Master brokerId = 0 # 删除文件时间点,默认凌晨4点 deleteWhen = 04 # 文件保留时间,默认48小时 fileReservedTime = 48 # Broker 角色,ASYNC_MASTER 表示异步复制Master brokerRole = ASYNC_MASTER # 刷盘方式,ASYNC_FLUSH 表示异步刷盘,性能更好 flushDiskType = ASYNC_FLUSH # 存储路径 storePathRootDir = /home/rocketmq/data/store storePathCommitLog = /home/rocketmq/data/commitlog # 监听端口 listenPort = 10911 # NameServer 地址 namesrvAddr = 127.0.0.1:9876 # 是否允许 Broker 自动创建Topic,生产环境建议关闭,此处测试开启 autoCreateTopicEnable = true # 启用 LiteTopic 特性(关键配置) enableLiteTopic = true # LiteTopic 相关的存储池配置(可选,高级调优) # liteTopicPoolSize = 16

注意storePathRootDirstorePathCommitLog的路径请根据你的实际磁盘情况修改。

步骤2:启动 NameServerNameServer 是路由发现中心,需要先启动。

# 进入 RocketMQ 根目录 cd /path/to/rocketmq-all-5.1.4-bin-release # 启动 NameServer,调整内存参数 nohup sh bin/mqnamesrv -n 127.0.0.1:9876 > ~/logs/namesrv.log 2>&1 & # 查看启动日志,确认成功 tail -f ~/logs/namesrv.log # 看到 “The Name Server boot success...” 即表示成功

步骤3:启动 Broker使用我们刚才修改的配置文件启动 Broker。

# 调整 Broker 启动内存,根据机器配置调整 -Xms 和 -Xmx nohup sh bin/mqbroker -n 127.0.0.1:9876 -c ./conf/broker-lite.conf > ~/logs/broker.log 2>&1 & # 查看 Broker 启动日志 tail -f ~/logs/broker.log # 看到 “The broker[broker-lite, 192.168.x.x:10911] boot success...” 即表示成功 # 特别注意日志中是否有 “enableLiteTopic=true” 相关的输出,确认特性已加载

步骤4:验证服务状态使用 RocketMQ 自带的管理工具快速验证。

# 查看集群信息 sh bin/mqadmin clusterList -n 127.0.0.1:9876 # 预期能看到你的 broker-lite 节点信息

5. 功能测试与效果验证

服务启动后,我们通过实际操作来感受 LiteTopic 与普通 Topic 的差异。

5.1 创建 LiteTopic 与普通 Topic

我们将使用管理命令创建两个 Topic 进行对比。

# 创建普通 Topic,名为 `NormalTopicTest` sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -b 127.0.0.1:10911 -t NormalTopicTest # 创建 LiteTopic,名为 `LiteTopicTest`,关键是指定 `-a +topicType=LITE` sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -b 127.0.0.1:10911 -t LiteTopicTest -a +topicType=LITE

执行成功判断:命令执行后无报错,并返回update topic success等类似信息。

5.2 查看 Topic 配置与状态

查看创建后的 Topic 详情,确认 LiteTopic 类型已生效。

# 查看 Topic 路由信息 sh bin/mqadmin topicRoute -n 127.0.0.1:9876 -t LiteTopicTest # 查看 Topic 统计信息(需要发送消息后才会有完整数据) sh bin/mqadmin topicStats -n 127.0.0.1:9876 -t LiteTopicTest

在 Broker 的日志文件或未来的监控面板中,可以区分出 Topic 的类型。

5.3 生产者与消费者测试

编写一个简单的 Java 测试程序,验证 LiteTopic 的消息收发功能是否正常。你需要创建一个 Maven 项目并引入 RocketMQ 客户端依赖。

<dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>5.1.4</version> </dependency>

生产者示例代码

import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; public class LiteTopicProducer { public static void main(String[] args) throws Exception { DefaultMQProducer producer = new DefaultMQProducer("producer_group_lite"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); for (int i = 0; i < 10; i++) { Message msg = new Message("LiteTopicTest", // 主题名称 "TagA", ("Hello LiteTopic, this is message " + i).getBytes()); producer.send(msg); System.out.println("Sent: " + new String(msg.getBody())); } producer.shutdown(); } }

消费者示例代码

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; public class LiteTopicConsumer { public static void main(String[] args) throws Exception { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group_lite"); consumer.setNamesrvAddr("127.0.0.1:9876"); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("LiteTopicTest", "*"); // 订阅主题 consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { System.out.println("Received: " + new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); System.out.println("Consumer Started. Waiting for messages..."); Thread.sleep(60000); // 等待一分钟接收消息 consumer.shutdown(); } }

测试步骤

  1. 先启动消费者程序,使其处于等待状态。
  2. 再启动生产者程序,发送消息。
  3. 观察消费者控制台,是否成功接收到所有10条消息。
  4. 用同样的代码,将 Topic 名称改为NormalTopicTest重复测试,验证功能一致性。

预期结果:LiteTopic 和 NormalTopic 在消息收发的基本功能上表现完全一致,对业务代码无感知。

6. 资源占用与性能观察

这是体现 LiteTopic “核心优势”的关键环节。我们主要通过观察 Broker 进程的内存占用来对比。

测试方法

  1. 基准观察:启动 Broker 后,不创建任何 Topic,使用jcmdtop命令观察其内存占用(RSS 和堆内存)。
    # 找到 Broker 的 PID jps -l | grep BrokerStartup # 假设 PID 是 12345,查看内存概要 jcmd 12345 GC.heap_info # 或者使用 top 动态观察 top -p 12345
  2. 创建大量普通 Topic:编写脚本,通过mqadmin命令批量创建 1000 个普通 Topic。
    for i in {1..1000}; do sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -b 127.0.0.1:10911 -t NormalTopicBatch_$i done
    创建完成后,再次观察 Broker 进程的内存占用(尤其是堆外内存Native Memory),记录增长值。
  3. 重启并创建大量 LiteTopic:停止 Broker,清理存储数据(或更换存储路径),修改配置确保enableLiteTopic=true,重启 Broker。 同样编写脚本,批量创建 1000 个 LiteTopic。
    for i in {1..1000}; do sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -b 127.0.0.1:10911 -t LiteTopicBatch_$i -a +topicType=LITE done
    创建完成后,观察 Broker 进程的内存占用。
  4. 对比分析:对比步骤2和步骤4的内存占用情况。理论上,创建 1000 个 LiteTopic 导致的内存增长,应远低于创建 1000 个普通 Topic。

观察要点

  • 堆内存(Heap):主要存储消息轨迹、过滤器等数据。LiteTopic 在此处优化可能不明显。
  • 堆外内存/原生内存(Native Memory):这是重点。普通 Topic 的元数据(如消费队列 ConsumeQueue)会占用大量堆外内存。LiteTopic 通过共享和优化,能大幅降低这部分开销。
  • 磁盘空间:在storePathCommitLog目录下,观察创建大量 Topic 前后磁盘空间的变化。LiteTopic 共享 CommitLog,因此创建大量 LiteTopic 不会导致存储空间线性增长。

性能影响:由于 LiteTopic 共享底层存储,在极端高并发写入场景下,可能会对单个存储文件产生竞争。但在其设计目标的海量 Topic、中低吞吐场景下,这种影响微乎其微,其带来的资源节省收益远大于此。

7. 接口 API 与批量任务

LiteTopic 的管理可以通过 RocketMQ 的 HTTP API 或 Admin API 完成,便于集成到运维平台或自动化脚本中。

通过 HTTP API 创建 LiteTopic: RocketMQ Broker 内置了 HTTP 服务(端口 8080 或自定义)。

# 使用 curl 命令创建 LiteTopic curl -X POST http://127.0.0.1:8080/topic/createOrUpdate \ -H "Content-Type: application/json" \ -d '{ "topic": "LiteTopicViaAPI", "brokerAddrList": "127.0.0.1:10911", "attributes": { "topicType": "LITE" } }'

通过 Java Admin API 批量管理: 以下示例展示如何使用 RocketMQ 5.x 的 Admin API 批量创建 LiteTopic。

import org.apache.rocketmq.client.apis.*; import org.apache.rocketmq.client.apis.message.Topic; import org.apache.rocketmq.client.apis.producer.Producer; import org.apache.rocketmq.client.apis.admin.Admin; import org.apache.rocketmq.client.apis.admin.TopicProperties; public class LiteTopicBatchAdmin { public static void main(String[] args) throws Exception { // 1. 创建 Admin 实例 String endpoint = "127.0.0.1:8081"; // Admin 端点 ClientServiceProvider provider = ClientServiceProvider.loadService(); ClientConfiguration config = ClientConfiguration.newBuilder() .setEndpoints(endpoint) .build(); Admin admin = provider.newAdminBuilder() .setClientConfiguration(config) .build(); // 2. 批量创建 LiteTopic for (int i = 0; i < 100; i++) { String topicName = "BatchLiteTopic-" + i; TopicProperties topicProperties = TopicProperties.builder() .setTopic(topicName) // 关键:设置 Topic 类型为 LITE .putAttributes("topicType", "LITE") .build(); try { admin.createTopic(topicProperties); System.out.println("Created LiteTopic: " + topicName); } catch (Exception e) { System.err.println("Failed to create topic: " + topicName + ", error: " + e.getMessage()); } } admin.close(); } }

批量任务设计建议

  1. 速率限制:在脚本或程序中加入简单的延迟(如Thread.sleep(10)),避免瞬间创建请求压垮 Broker。
  2. 错误重试:对创建失败的 Topic 进行记录和有限次数的重试。
  3. 状态校验:批量创建后,通过mqadmin topicRoute或 API 抽样检查 Topic 的创建状态和类型是否正确。

8. 常见问题与排查方法

在部署和使用 LiteTopic 过程中,你可能会遇到以下问题。

问题现象可能原因排查方式解决方案
创建 LiteTopic 失败,报错topic type is not supportedBroker 版本低于 5.0,或配置未启用 LiteTopic。1. 检查 Broker 版本:sh bin/mqbroker -v
2. 检查 Broker 配置文件broker.conf中是否有enableLiteTopic=true
升级 RocketMQ 到 5.0+ 版本,并确保配置正确、重启 Broker。
生产者无法向 LiteTopic 发送消息Topic 路由信息未正确同步到客户端;或 Topic 未创建成功。1. 使用mqadmin topicRoute检查 Topic 是否存在。
2. 查看生产者日志,是否有 “topic route not found” 错误。
1. 确认 Topic 已成功创建。
2. 生产者客户端尝试重启,重新拉取路由信息。
监控显示 LiteTopic 内存占用未明显降低对比的基准不对;或创建 LiteTopic 时未指定类型,默认为普通 Topic。1. 确认创建命令中包含了-a +topicType=LITE参数。
2. 使用mqadmin topicStats或查看 Broker 日志确认 Topic 类型。
重新创建 Topic 并确保类型正确。检查是否在启用 LiteTopic 前已存在大量普通 Topic 数据。
Broker 启动失败,报错Failed to initialize LiteTopic service配置冲突或存储目录权限问题。查看 Broker 日志文件末尾的详细错误堆栈。检查存储路径storePathRootDir是否存在且 Broker 进程有读写权限。检查配置文件中是否有重复或冲突的参数。
通过 API 创建的 Topic 不是 LiteTopic 类型HTTP API 或 Admin API 调用时,未正确设置attributes检查 API 调用时传入的 JSON 数据或TopicProperties,确认包含了”topicType”: “LITE”修正 API 调用参数,确保属性设置正确。
大量 LiteTopic 后,单个 Topic 写入性能下降共享的 CommitLog 文件成为性能瓶颈(在极高并发下可能出现)。监控 Broker 的 IO 等待和 CPU 使用率。使用mqadmin brokerStatus查看写入状态。考虑进行 Broker 水平扩展,将 LiteTopic 分散到多个 Broker 节点上。或评估是否将少数极高吞吐的 Topic 转为普通 Topic。

9. 最佳实践与使用建议

基于测试和经验,总结以下几点建议,帮助你在生产环境中更好地使用 LiteTopic。

  1. 规划先行:在架构设计阶段就明确哪些业务场景适合使用 LiteTopic(海量、中低吞吐、逻辑隔离),哪些适合普通 Topic(高吞吐、需要独立存储策略)。
  2. 配置标准化:将enableLiteTopic=true作为 Broker 集群的标准配置之一,即使初期不使用,也为未来扩容做好准备。通过运维平台或配置中心统一管理 Topic 的创建模板,强制指定类型。
  3. 命名规范:为 LiteTopic 建立统一的命名规范,例如前缀lite_或后缀_lite,便于在监控和日志中快速识别和过滤。
  4. 监控与告警:调整监控看板,不仅关注单个 Topic 的堆积和延迟,更要关注承载 LiteTopic 的 Broker 节点整体的资源使用率(CPU、内存、磁盘 IO、网络)。设置 Broker 节点原生内存使用率的告警阈值。
  5. 渐进式迁移:如果是从现有普通 Topic 迁移到 LiteTopic,建议采用双写方案,先创建新的 LiteTopic,让消费者同时从新旧 Topic 消费,待数据同步完成后再切换生产者,最后下线旧 Topic。
  6. 备份与恢复:定期测试 LiteTopic 数据的备份与恢复流程。由于存储共享,备份策略可能需要调整,确保能恢复特定 LiteTopic 的数据。
  7. 客户端兼容性测试:虽然 API 兼容,但仍建议对生产环境使用的所有 RocketMQ 客户端版本(特别是旧版本)进行完整的业务场景测试,确保在 LiteTopic 上一切功能正常。
  8. 文档与培训:在团队内部明确 LiteTopic 的使用规范和边界,避免开发人员误用。将本文中的测试用例和排查方法纳入团队的运维知识库。

10. 总结与下一步

LiteTopic 是 RocketMQ 面向云原生和海量细分场景交出的一份优秀答卷。它精准地命中了传统消息队列在海量 Topic 管理上的痛点,通过“共享存储、轻量元数据”的巧妙设计,在保持 API 兼容性和核心功能不变的前提下,实现了资源利用率的质的提升。

对于开发者而言,最直接的行动建议是:立即在你的测试环境中部署一个 RocketMQ 5.x 集群,按照本文的步骤,亲手创建几百个 LiteTopic 和普通 Topic,观察内存和磁盘的占用差异。这种直观的对比,比任何理论描述都更有说服力。

最容易踩的坑主要在两个地方:一是Broker 配置未启用(enableLiteTopic=true),二是创建 Topic 时未指定类型,结果创建成了普通 Topic。务必通过管理命令或日志反复确认。

下一步,你可以深入探索 RocketMQ 5.x 的其他新特性,如Pop 消费模式对 LiteTopic 消费端的进一步优化,或者研究如何与Kubernetes Operator结合,实现 LiteTopic 的自动化弹性管理。当你的系统真正需要管理成千上万个消息通道时,你会庆幸今天提前了解了 LiteTopic。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 18:32:35

基于Dify工作流的百科词条自动化生成:从原理到实践

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

作者头像 李华
网站建设 2026/9/4 18:28:49

【元脑服务器NF5280G7-NF5280A7技术规格分享】

##2U元脑NF5280G7 型号元脑NF5280G7分支型号NF5280-A7-A0-R0-001CPU类型2*第四代AMD EYPC 9004系列处理器内存插槽24*DDR5 4800MT/s RDIMM&#xff0c;不支持3DS存储前面板&#xff1a;①最大支持24个2.5英寸硬盘&#xff0c;支持SATA/SAS/NVMe硬盘&#xff0c;支持热插拔&…

作者头像 李华
网站建设 2026/9/4 18:26:57

基于AI代码生成的三相并网逆变器MATLAB仿真实践

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

作者头像 李华
网站建设 2026/9/4 18:24:29

Bythere白泽-恋玻璃鼠标垫评测:电竞与创作场景下的极致顺滑体验

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

作者头像 李华