news 2026/9/19 2:08:37

2026年13款主流性能测试工具选型指南与JMeter高并发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
2026年13款主流性能测试工具选型指南与JMeter高并发实战

1. 性能测试工具选型的底层逻辑

1.1 为什么2026年还要重新盘点压测工具

做性能测试这行十来年,我最大的感受是:工具本身没有绝对的好坏,只有合不合适。2026年的技术栈和五年前已经完全不是一回事了——微服务拆得越来越细、容器化部署成了标配、云原生架构遍地开花,这些变化直接影响了压测工具的选择标准。以前一个JMeter走天下的日子早就过去了,现在你可能需要同时掌握三四款工具才能覆盖不同的测试场景。

我见过太多团队在工具选型上踩坑:有的团队用JMeter去压测gRPC接口,折腾了一周才跑通;有的团队用Locust做协议级压测,结果发现单机并发上不去;还有的团队花大价钱买了商业工具,最后发现80%的功能用开源方案就能搞定。这些问题的根源都在于——没有搞清楚自己的测试需求到底是什么。

这篇文章我会把目前主流的13款压测工具掰开揉碎了讲清楚,包括它们各自适合什么场景、有什么坑、怎么快速上手。不管你是刚入行的测试新人,还是带团队的技术负责人,都能从中找到适合自己的方案。

1.2 选工具之前先搞清楚这三个问题

在动手选工具之前,我建议你先回答三个问题。第一个问题是:你要测的是什么协议?HTTP、gRPC、WebSocket、MQTT、JDBC,不同工具对这些协议的支持程度差异很大。第二个问题是:你需要多大的并发量?是几百并发还是几十万并发?这直接决定了你是用单机方案还是分布式方案。第三个问题是:你的团队技术栈是什么?如果团队全是Java背景,那JMeter和Gatling上手会快很多;如果团队以Python为主,Locust和k6会更顺手。

这三个问题看起来简单,但我见过太多人跳过这一步直接选工具,结果做到一半发现方向错了,推倒重来的成本非常高。举个例子,之前有个朋友的公司要做物联网平台的压测,需要模拟十万台设备同时上报MQTT消息。他们一开始选了JMeter,装了MQTT插件之后发现单机只能模拟几千个连接,最后换成了专门做MQTT压测的emqtt_bench才解决问题。如果一开始就想清楚协议和并发量这两个维度,就能少走很多弯路。

提示:选型时不要只看工具的功能列表,一定要做POC验证。花两天时间用真实业务场景跑一遍,比看一百篇对比文章都有用。

1.3 2026年压测工具的四大分类

目前市面上的压测工具大致可以分为四类。第一类是基于Java生态的传统工具,代表就是JMeter,优点是生态成熟、插件丰富、上手门槛低,缺点是资源消耗大、GUI模式不适合高并发。第二类是代码驱动的现代化工具,比如k6、Locust、Gatling,它们用代码定义测试场景,更适合CI/CD集成和版本管理。第三类是云原生压测平台,比如k6 Cloud、BlazeMeter,它们提供分布式压测能力,按需付费。第四类是轻量级专用工具,比如wrk、hey、vegeta,适合快速验证单个接口的性能。

理解这个分类很重要,因为它能帮你快速缩小选型范围。如果你需要做复杂的业务链路压测,那第一类和第二类更合适;如果你只是想在开发阶段快速验证接口性能,第四类工具几秒钟就能跑起来;如果你需要模拟百万级并发又不想维护压测集群,那第三类云平台是首选。

2. 十三款主流压测工具逐一拆解

2.1 JMeter:绕不开的行业标准

JMeter不用多介绍,做性能测试的基本都用过。它最大的优势就是生态太成熟了——你能想到的协议它几乎都支持,HTTP、HTTPS、FTP、JDBC、JMS、SOAP、REST、MQTT、gRPC,通过插件还能扩展更多。而且它的GUI界面对于新手来说非常友好,拖拖拽拽就能搭出一个测试计划。

但JMeter的坑也不少。首先是资源消耗问题,GUI模式下跑高并发,JMeter自己就能把内存吃满。正确的做法是用CLI模式跑压测,GUI只用来编写和调试脚本。其次是分布式压测的配置比较繁琐,需要配置master和slave节点,网络和防火墙稍微有点问题就跑不起来。还有就是JMeter的HTML报告虽然功能强大,但默认模板的汉化一直是个痛点,很多人不知道怎么把报告改成中文的。

关于JMeter的具体操作,我后面会专门用一个章节来讲,这里先给几个关键数据:单机CLI模式下,JMeter大概能支撑3000-5000并发(取决于脚本复杂度和机器配置);分布式模式下,理论上可以无限扩展,但master节点的聚合能力会成为瓶颈。

2.2 k6:代码驱动的新一代选择

k6是这几年上升势头最猛的压测工具,它的核心理念是"测试即代码"。你用JavaScript写测试脚本,然后用k6命令行工具执行。这种方式的优势非常明显:脚本可以纳入版本管理、可以在CI/CD流水线里自动执行、可以用npm生态的各种库来扩展功能。

k6的性能表现也很出色。它底层用Go语言实现,单机并发能力比JMeter强不少。我实测下来,同样的机器配置,k6跑HTTP压测的QPS大概是JMeter的1.5到2倍。而且k6的资源占用很低,一台4核8G的机器跑一万并发没什么压力。

不过k6也有它的局限性。首先它主要聚焦在HTTP/1.1、HTTP/2、WebSocket和gRPC这几种协议上,不像JMeter那样什么协议都支持。其次它的脚本编写需要一定的JavaScript基础,对于纯测试背景的同学来说有一定学习成本。另外k6的分布式压测需要用到k6 Cloud或者自己搭建k6 operator,不如JMeter那么直接。

2.3 Locust:Python系的首选方案

Locust是Python技术栈团队的首选压测工具。它的脚本用纯Python编写,对于有Python基础的测试同学来说几乎没有学习成本。Locust的另一个亮点是它的Web UI,可以实时看到压测的RPS、响应时间、错误率等指标,而且支持动态调整并发数,不用重启压测。

Locust的分布式架构设计得比较优雅,master节点负责调度,worker节点负责施压,通过消息队列通信。这种架构的好处是扩展方便,加worker节点就行。但Locust的性能一直是它的短板——因为Python的GIL限制,单个worker的并发能力有限,要跑高并发需要开很多worker。

我个人的经验是,Locust适合做业务链路的压测,特别是需要复杂逻辑判断和参数关联的场景。但如果纯粹追求高QPS,k6或者wrk会更合适。

2.4 Gatling:Scala加持的高性能方案

Gatling是基于Scala的压测工具,底层用了Akka框架和Netty网络库,性能非常强悍。它的脚本用Scala DSL编写,虽然学习曲线比JMeter陡,但写出来的脚本非常优雅,可读性很强。

Gatling的HTML报告是我见过所有开源工具里最漂亮的,没有之一。报告里不仅有常规的响应时间、QPS、错误率,还有详细的百分位分布、请求时间线、活跃用户曲线等。而且Gatling的报告是自包含的HTML文件,分享起来很方便。

Gatling的缺点主要是Scala的学习成本。虽然它的DSL设计得很好,但遇到复杂场景还是需要写一些Scala代码。另外Gatling的社区规模比JMeter小不少,遇到问题能找到的资料相对有限。

2.5 wrk:轻量级HTTP压测利器

wrk是一个用C语言写的轻量级HTTP压测工具,它的特点就是快、简单、资源占用极低。一条命令就能跑起来,不需要写脚本,不需要装依赖。wrk支持Lua脚本扩展,可以用Lua来做参数化和逻辑控制。

wrk适合在开发阶段快速验证接口性能。比如你刚写完一个API,想看看它的QPS和延迟大概是多少,wrk几秒钟就能给你答案。但wrk不适合做复杂的业务链路压测,它的定位就是单接口的性能基准测试。

2.6 hey:最简单的HTTP压测工具

hey是Google开源的一个HTTP压测工具,用法比wrk还简单。一条命令指定URL、并发数和请求总数就行。hey的输出结果很直观,包括响应时间分布、状态码统计、每秒请求数等。

hey的定位和wrk类似,都是轻量级的单接口压测工具。但hey的功能比wrk更简单,不支持脚本扩展,适合快速验证和演示。

2.7 vegeta:Go语言写的HTTP压测工具

vegeta是Go语言写的HTTP压测工具,它的特点是支持多种压测模式(恒定速率、递增速率、自定义速率),而且输出格式丰富(JSON、CSV、HTML)。vegeta的脚本可以用Go编写,也可以用命令行参数配置。

vegeta适合做持续压测和趋势分析。比如你想观察系统在长时间运行下的性能变化,vegeta的恒定速率模式就很合适。

2.8 ab:Apache自带的经典工具

ab是Apache HTTP Server自带的一个压测工具,历史非常悠久。它的用法极其简单,一条命令就能跑。但ab的局限性也很明显:只支持HTTP/1.0和HTTP/1.1,不支持HTTPS(需要特殊编译),不支持并发场景下的复杂逻辑。

ab适合做最简单的性能验证,比如刚装好一个Web服务器,想快速看看它的处理能力。但在正式的性能测试中,ab已经很少被使用了。

2.9 Tsung:Erlang打造的多协议压测工具

Tsung是用Erlang写的多协议压测工具,支持HTTP、WebSocket、MQTT、XMPP、LDAP等多种协议。Erlang的并发模型让Tsung在高并发场景下表现不错,而且资源占用比较低。

Tsung的配置用XML文件定义,对于不熟悉XML的同学来说有一定学习成本。另外Tsung的社区活跃度一般,资料相对较少。

2.10 Siege:老牌HTTP压测工具

Siege是一个老牌的HTTP压测工具,支持基本的HTTP/HTTPS压测,可以通过配置文件定义多个URL和请求参数。Siege的输出结果比较简洁,适合快速查看压测结果。

Siege的优点是稳定、简单,缺点是功能相对单一,不支持复杂的测试场景。

2.11 Artillery:Node.js生态的压测工具

Artillery是基于Node.js的压测工具,脚本用YAML定义,也可以用JavaScript扩展。Artillery支持HTTP、WebSocket、Socket.io等协议,而且有云服务版本可以扩展压测能力。

Artillery适合Node.js技术栈的团队,脚本编写比较直观。但它的性能表现一般,高并发场景下不如k6和Gatling。

2.12 BlazeMeter:商业级云压测平台

BlazeMeter是JMeter的商业化版本,提供了云端的分布式压测能力。你可以直接上传JMeter脚本,然后在BlazeMeter的云平台上执行,支持百万级并发。BlazeMeter还提供了详细的报告和分析功能,以及团队协作和CI/CD集成。

BlazeMeter的缺点是价格不便宜,适合预算充足的企业用户。另外因为它是基于JMeter的,所以JMeter的优缺点它都继承了下来。

2.13 k6 Cloud:k6的云端版本

k6 Cloud是k6的官方云服务,提供了分布式压测、结果分析和团队协作功能。你可以用本地的k6脚本,然后在k6 Cloud上执行大规模压测。k6 Cloud的界面很现代化,报告也很直观。

k6 Cloud的定价模式是按压测时长和并发数计费,对于偶尔做大规模压测的团队来说比较划算。

3. JMeter实战:从安装到高并发压测

3.1 JMeter安装与环境配置

JMeter的安装其实很简单,但有几个细节不注意就会踩坑。首先你需要确认Java环境,JMeter 5.6版本要求Java 8以上,推荐用Java 11或17。安装Java之后,去Apache官网下载JMeter的二进制包,解压到任意目录就行。

Windows环境下,你需要配置两个环境变量:JMETER_HOME指向JMeter的安装目录,然后在PATH里加上%JMETER_HOME%\bin。配置完成后,在命令行输入jmeter -v,如果能显示版本信息就说明安装成功了。

Linux环境下,除了配置环境变量,还需要注意两点:一是给jmeter脚本加执行权限,二是调整系统的文件句柄数和网络参数。高并发压测时,Linux默认的文件句柄数(通常是1024)会成为瓶颈,需要调到65535以上。

注意:JMeter的GUI模式只用来编写和调试脚本,正式压测一定要用CLI模式。GUI模式下的内存消耗和CPU占用会严重影响压测结果的准确性。

3.2 JMeter压测脚本编写核心步骤

写一个完整的JMeter压测脚本,通常包含以下几个核心组件。首先是线程组,它定义了并发用户数、启动时间、循环次数等参数。线程组的配置直接决定了压测的强度,需要根据实际业务场景来设置。

然后是HTTP请求默认值,可以把协议、域名、端口、编码这些公共参数提取出来,避免在每个请求里重复填写。接着是HTTP请求本身,需要配置请求方法、路径、参数、请求头等信息。

对于需要登录的场景,还需要添加HTTP Cookie管理器或者HTTP信息头管理器来处理认证信息。如果接口之间有参数关联,比如上一个接口返回的token要传给下一个接口,就需要用到JSON提取器或者正则表达式提取器。

最后是监听器,用来收集和展示压测结果。常用的监听器有聚合报告、查看结果树、用表格查看结果等。但要注意,监听器会消耗大量内存,正式压测时应该只保留必要的监听器,或者直接用CLI模式生成HTML报告。

3.3 JMeter参数化与动态QPS调整

参数化是JMeter压测中非常重要的一个环节。最常见的参数化方式有CSV Data Set Config、用户定义的变量、函数助手等。CSV Data Set Config适合从文件读取大量测试数据,比如用户名密码列表。用户定义的变量适合配置一些全局参数,比如域名、端口。

动态调整QPS是很多同学关心的问题。JMeter本身没有直接提供动态调整QPS的功能,但可以通过几种方式实现。一种是使用Constant Throughput Timer,它可以控制每分钟的请求数。另一种是使用BeanShell脚本或者JSR223脚本,在运行时动态修改线程数或者定时器的参数。

我实测下来,用JSR223 Sampler配合Groovy脚本动态调整QPS比较稳定。你可以在脚本里读取外部变量或者根据响应时间自动调整发送速率。这种方式比Constant Throughput Timer更灵活,但需要一定的编程基础。

3.4 JMeter分布式压测配置要点

当单机压测能力不够时,就需要用到JMeter的分布式压测。分布式压测的架构是一个master节点控制多个slave节点,master负责分发脚本和收集结果,slave负责实际施压。

配置分布式压测有几个关键点。第一,所有节点上的JMeter版本和Java版本必须一致。第二,master和slave之间需要配置SSH免密登录或者RMI通信。第三,slave节点上的jmeter-server需要启动,并且防火墙要放行相关端口。第四,master节点的jmeter.properties里需要配置remote_hosts,列出所有slave的IP地址。

分布式压测最常见的坑是网络问题。如果master和slave之间的网络延迟高或者丢包,压测结果会严重失真。另外master节点的聚合能力有限,slave节点太多的话,master会成为瓶颈。我的经验是,单个master最多管理10到15个slave节点,再多就需要考虑分层架构了。

3.5 JMeter HTML报告生成与汉化

JMeter的HTML报告功能很强大,但默认是英文的,很多同学想要汉化。汉化的方法其实不复杂,找到JMeter安装目录下的bin/report-template目录,里面是生成报告用的模板文件。你可以修改这些模板文件里的英文文本,替换成中文。

不过我不建议直接修改模板文件,因为JMeter升级后修改会丢失。更好的做法是复制一份模板目录,修改后通过命令行参数指定自定义模板路径。这样既实现了汉化,又不会影响JMeter的升级。

生成HTML报告的命令是:jmeter -n -t test.jmx -l result.jtl -e -o report_output。其中-n表示非GUI模式,-t指定测试脚本,-l指定结果文件,-e表示生成报告,-o指定报告输出目录。注意报告输出目录必须为空,否则会报错。

4. 性能测试指标体系与结果分析

4.1 必须掌握的核心性能指标

做性能测试,如果连核心指标都说不清楚,那测试报告就没法看了。最基础的指标包括TPS(每秒事务数)、QPS(每秒查询数)、响应时间(RT)、并发数、错误率。这几个指标之间的关系需要搞清楚:TPS和QPS反映的是系统的处理能力,响应时间反映的是用户体验,并发数反映的是系统承受的压力,错误率反映的是系统的稳定性。

除了这些基础指标,还有一些进阶指标需要关注。比如P95、P99响应时间,它们反映的是长尾请求的情况。平均响应时间容易被少数极快或极慢的请求拉偏,而P95和P99能更真实地反映大多数用户的体验。还有吞吐量(Throughput),它和TPS的区别在于吞吐量统计的是所有请求,包括成功和失败的。

提示:看性能测试报告时,不要只看平均值。平均值会掩盖很多问题,一定要看百分位分布和最大值。

4.2 如何判断系统瓶颈在哪里

压测跑完之后,发现TPS上不去或者响应时间飙升,接下来就要定位瓶颈。瓶颈可能出现在四个地方:压测客户端、网络、应用服务器、数据库。

判断压测客户端是否是瓶颈,可以看压测机器的CPU和内存使用率。如果压测机器的CPU跑满了,那说明压测客户端能力不足,需要增加压测节点或者优化脚本。判断网络是否是瓶颈,可以看网络带宽和延迟。如果带宽跑满了或者延迟很高,那网络就是瓶颈。

应用服务器的瓶颈通常体现在CPU、内存、线程数、连接数等指标上。数据库的瓶颈则体现在慢查询、锁等待、连接池耗尽等方面。定位瓶颈需要结合多个维度的监控数据,不能只看单一指标。

4.3 压测结果分析的常见误区

压测结果分析有几个常见的误区。第一个误区是只看TPS不看响应时间。高TPS如果伴随着高响应时间,那说明系统已经在超负荷运转了,用户体验很差。第二个误区是忽略错误率。有些同学看到TPS很高就高兴了,没注意到错误率已经超过10%了,这样的压测结果没有意义。

第三个误区是不做对比分析。性能测试一定要有基线,没有基线的压测结果就是一堆数字。你需要和上次压测的结果对比,和竞品对比,和理论值对比,才能判断系统性能是否达标。第四个误区是忽略环境差异。压测环境和生产环境的配置如果不一样,压测结果就不能直接套用到生产环境。

5. 常见问题与排查技巧实录

5.1 JMeter压测中的典型报错与解决

JMeter压测过程中会遇到各种各样的报错,我整理了几个最常见的。第一个是"java.io.IOException: Error writing to server",这个错误通常是因为压测客户端和服务端之间的连接被重置了。可能的原因包括服务端连接数满了、网络不稳定、压测客户端资源不足。解决方法包括增加服务端的连接数限制、调整JMeter的httpclient参数、增加压测客户端资源。

第二个是"java.net.SocketException: Too many open files",这个错误在Linux环境下很常见,原因是文件句柄数不够。解决方法是在/etc/security/limits.conf里把nofile调到65535以上,然后重启系统或者重新登录。

第三个是JMeter内存溢出,报"java.lang.OutOfMemoryError"。这是因为JMeter默认的堆内存太小,需要在jmeter.bat或者jmeter.sh里调整HEAP参数。一般建议把堆内存设置为物理内存的一半,但不要超过8G。

5.2 接口关联与参数传递的坑

接口关联是JMeter压测中最容易出问题的地方。最常见的场景是登录后获取token,然后把token传给后续接口。这里有几个坑需要注意。第一个坑是token的提取方式,如果响应是JSON格式,用JSON提取器最方便;如果是HTML或者XML,就需要用正则表达式提取器或者XPath提取器。

第二个坑是token的作用域。如果token是在线程组级别提取的,那所有线程共享同一个token,这可能会导致并发问题。正确的做法是把token提取到线程级别,每个线程用自己的token。第三个坑是token的过期时间。如果压测时间比较长,token可能会过期,需要在脚本里加入token刷新逻辑。

5.3 高并发场景下的资源调优

高并发压测时,除了JMeter本身的配置,操作系统和网络的调优也很重要。Linux系统需要调整的参数包括:文件句柄数、TCP连接数、端口范围、TIME_WAIT状态的处理等。这些参数不调整的话,压测还没跑到目标并发,系统就先报错了。

JMeter本身的调优包括:调整堆内存、关闭不必要的监听器、使用CLI模式、启用Keep-Alive、调整HTTP请求的超时时间等。另外,如果压测脚本里有大量的参数化和逻辑判断,可以考虑用JSR223+Groovy替代BeanShell,性能会好很多。

5.4 常见问题速查表

问题现象可能原因解决方法
压测TPS上不去压测客户端瓶颈增加压测节点,优化脚本
响应时间波动大网络不稳定或服务端GC检查网络质量,调整JVM参数
错误率突然升高服务端连接数满或超时增加连接数限制,调整超时时间
JMeter报内存溢出堆内存不足调整HEAP参数,关闭不必要的监听器
分布式压测结果不一致节点时间不同步配置NTP时间同步
参数化数据读取错误CSV文件编码或路径问题检查文件编码,使用绝对路径
HTTPS请求失败证书问题导入证书或配置信任所有证书
数据库压测连接失败JDBC驱动或连接池配置检查驱动版本,调整连接池参数

6. 云原生时代的压测新思路

6.1 容器化环境下的压测挑战

现在越来越多的系统跑在Kubernetes上,这给性能测试带来了新的挑战。首先是压测环境的搭建,以前装个JMeter就行,现在需要考虑容器化部署、服务发现、网络策略等问题。其次是压测流量的注入,K8s环境下的网络模型和传统环境不一样,压测流量可能会被Ingress、Service Mesh等组件拦截或限流。

还有一个挑战是压测资源的弹性伸缩。K8s环境下,压测客户端也可以容器化部署,按需扩缩容。但这需要和K8s的调度系统配合,确保压测Pod能够及时启动并分配到足够的资源。

6.2 不停服迁移场景下的压测验证

我最近参与了一个比较有意思的项目:把一个单节点K8s上的微服务整套环境,准不停服、不丢数据地迁移到云上。迁移完成后,需要由压测人员用JMeter脚本做高并发测试,验证云上环境的承载能力。

这种场景下的压测有几个特殊要求。第一,压测要在迁移完成后尽快执行,确保业务还没正式切流量之前就发现问题。第二,压测脚本要覆盖核心业务链路,不能只压单个接口。第三,压测结果要和迁移前的基线对比,确认云上环境的性能没有下降。

实际操作中,我们先用JMeter的录制功能把核心业务操作录制成脚本,然后做参数化和关联处理,最后用分布式模式跑高并发。压测过程中重点关注TPS、响应时间、错误率三个指标,同时监控云上环境的CPU、内存、网络、数据库等资源使用情况。

6.3 压测左移与持续性能测试

传统的性能测试通常在项目后期才介入,这时候发现问题修复成本很高。现在越来越提倡"压测左移",也就是在开发阶段就开始做性能测试。开发人员写完接口后,用k6或者wrk快速跑一下基准测试,确保单个接口的性能达标。然后在集成阶段,用JMeter或者Locust做业务链路的压测。

持续性能测试是另一个趋势。把压测脚本纳入CI/CD流水线,每次代码合并后自动执行性能回归测试。如果性能指标下降超过阈值,就自动阻断发布。这种方式可以及早发现性能退化,避免问题积累到生产环境才暴露。

我个人在实际操作中的体会是,性能测试不是一个一次性的任务,而是一个持续的过程。工具的选择、脚本的维护、指标的监控,都需要长期投入。但只要你把基础打好了,后面的事情就会越来越顺。最后再分享一个小技巧:压测脚本一定要纳入版本管理,每次修改都要记录变更原因和测试结果,这样出了问题才能快速回溯。

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

RCNP C8311备考指南:锐捷整网配置与实验手册拆解

简介:这是锐捷认证方向的技术文档,聚焦C8311交换机配置与管理,适合网络工程师备考或处理实际运维场景。内容围绕虚拟局域网规划、干线链路修剪、802.1Q标记、子接口封装等核心知识点展开,并以选择题形式附带标准答案,便…

作者头像 李华
网站建设 2026/9/19 2:04:20

C++与Python混合编程:pybind11、ctypes、Python C API选型对比

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

作者头像 李华
网站建设 2026/9/19 2:01:21

HikariCP生产调优与故障防御实战指南

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

作者头像 李华