ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JMeter分布式压测实战:从单机瓶颈到多机协同施压的完整指南

JMeter分布式压测实战:从单机瓶颈到多机协同施压的完整指南 做性能测试的人早晚会撞上这么一堵墙单机JMeter压到某个量级TPS还没打上去施压机的CPU先满了线程数稍微调大GC就开始疯狂抖动出来的数据根本没法参考。这时候多数人的第一反应是换台更猛的机器但真正系统的解法是JMeter分布式测试让多台机器协同施压把压测能力横向扩出去。这篇文章是我基于实际压测项目整理的分布式实战笔记覆盖架构原理、环境搭建、脚本设计、命令行运行、问题排查以及从分布式压测走向完整性能评估的方法适合正在做性能测试、想突破单机瓶颈的测试开发和性能工程师。1. 分布式压测架构与核心场景判断1.1 单机瓶颈到底卡在哪先说清楚一个基本事实JMeter本质上是一个基于Java线程模型的工具一个虚拟用户对应一个线程一个请求在线程里同步执行。线程本身是有开销的线程越多CPU用在上下文切换上的比例就越大真正花在发送请求、解析响应上的时间反而变少。再加上断言、正则提取、JSON解析这些操作都要消耗CPU所以单机JMeter的并发能力并不是“你设置多少就能跑多少”。我自己的实测经验是在普通的4核8G云主机上压测一个HTTP接口不跑复杂断言、不开结果树监听器JMeter的CPU到达80%以上时单机吞吐量大约在1200到1800 QPS之间就顶天了。如果你还习惯开着图形界面、挂着聚合报告、把响应体都保存下来那这个数字还会继续往下掉掉到几百也不奇怪。除了CPU内存和端口也是常见的隐形瓶颈。监听器会把采样结果存在内存里线程多、请求快很快就能把堆撑爆。短连接压测还会大量产生TIME_WAIT状态的本地端口一旦4万多个临时端口被占满新请求就发不出去表现就是报错率飙升、吞吐量断崖式下降。所以单机瓶颈不是一个点而是CPU、内存、端口、网络带宽这几个因素叠加出来的结果。分布式测试做的事情也很直接把这些资源诉求拆到多台机器上让每台施压机只承担整体压力的一小部分总并发和总吞吐自然就上去了。但分布式也不是灵丹妙药后面会专门讲什么情况下不该用。1.2 Master/Slave 架构原理与通信机制JMeter分布式测试的架构是一主多从专业叫法是Controller和AgentJMeter里习惯叫Master和Slave。Master负责下发测试脚本、汇总结果、控制启停真正干活的是Slave也就是跑jmeter-server的机器它们负责实际产生并发请求。Master和Slave之间通过RMI通信默认端口是1099。很多人第一次接触分布式时容易有一个误解以为Master也会帮忙一起发请求。其实不会Master的角色更像“指挥官”只做调度和收集不参与施压。如果你想用Master本机也发一部分请求就要把本机地址也加进remote_hosts列表里让它同时扮演Slave但这种做法我不推荐会让调度机的资源更加紧张。还有一个非常关键的细节分布式执行时每个Slave跑的是同一个脚本的完整副本线程数不会自动按节点数拆分。假设你目标并发是1000有4台Slave那脚本线程组里的线程数要自己改成250左右4台加起来才是1000。这个手动拆分的逻辑一定不能忘否则实际并发会远超出你的预期直接把被测系统打挂。用表格看单机和分布式的差异会更清楚对比项单机压测分布式压测施压能力上限受限于单机CPU/内存/端口随节点数近似线性扩展并发模型单进程内多线程多进程多机多线程结果汇总本地直接生成Slave回传Master统一汇总可靠性施压机故障则测试中断单节点故障可定位可替换运维复杂度低需要维护多台环境、证书、端口典型适用场景并发几百、接口联调上千上万并发、容量验证分布式架构理解透了很多配置问题都能自己推出来。比如为什么调不通先想想是RMI端口被墙了还是Master和Slave的JMeter/RMI配置不一致后面排查章节会具体展开。1.3 什么情况下不该上分布式分布式虽好但真不是所有压测场景都需要。我见过不少团队目标并发只有两三百单机JMeter跑得轻轻松松结果非要去搭分布式最后花了两天调试环境压测数据却没比单机强多少。这是典型的为了分布式而分布式。什么情况下不应该上分布式我按实际经验总结了几条第一被测系统本身的处理能力远大于你单机施压能力但你没有明确的更高并发目标。比如单机已经能稳定压出你需要的两倍以上TPS那分布式没有意义问题不在施压端。第二被测系统是单点部署且资源有限。施压端再强后端Tomcat线程池100就被打满数据库连接池50就扛不住加再多的施压机也只是在加大错误率测不出什么有价值的信息。第三网络带宽本身是瓶颈。如果所有施压机都放在同一个机房同一台交换机下压力会叠加在交换机和负载均衡上你测出来的瓶颈可能是网络设备而不是被测服务。分布式压测最好让施压机与被测系统之间有充足的带宽冗余或者至少知道这个链路瓶颈在哪。第四个情况比较隐蔽脚本里用了大量的BeanShell、正则提取、JSON解析还要开结果树保存响应。这种脚本本身就是施压机的“性能杀手”先优化脚本和断言方案比盲目加机器划算得多。我自己的判断标准很简单先跑一轮单机测试观察施压机自身指标。如果CPU已经持续超过70%或者端口耗尽、内存飙升但被测系统的CPU和响应时间还很健康那就可以考虑上分布式。反过来加机器解决不了问题先回去修脚本。2. 环境准备与压测节点部署2.1 JMeter 安装与 JDK 版本选型分布式环境里每台机器都要装JMeter版本必须保持一致这是最容易踩的坑。版本不一致的Master和Slave之间常常会在RMI握手时直接报EOFException或者莫名其妙收不到完整数据。所以先把所有机器归到一个统一的JMeter版本上再谈其他。JDK版本的选择也需要提前确认。JMeter不同版本对JDK的兼容情况不一样我整理了一个简单的对照JMeter版本建议JDK5.1及更早JDK 85.2~5.4JDK 8或JDK 115.5JDK 8、11、17均可5.6.x官方支持8、11、17建议11长期实践下来的结论是JDK 8是兼容性最稳的选择很多老插件和第三方jar在JDK 11或17下会出现莫名其妙的反射权限问题。如果你用到的自定义函数、插件不多那JDK 11启动更快、性能也更好。安装方式没什么花活官网下载zip包解压就行别用来路不明的集成包。Linux下可以用tar.gz直接装例如把apache-jmeter-5.6.3.tgz解压到/opt下再在.bashrc里配置JAVA_HOME和JMETER_HOME。Mac用户可以直接用brew install jmeter但Homebrew的版本更新有滞后性用官网包是最可控的。Ubuntu用户用apt install jmeter也会拿到相对旧的版本不是不能用而是在做分布式时要保证所有节点版本一致统一从官网拿包最省事。安装路径有两个坑一定要避开第一路径不要带空格和中文第二千万不要解压到C:\Windows\System32这类有权限限制的系统目录。我之前帮一个同事排错他报的就是JMeter运行时弹出could not delete existing file c:\windows\system32类似错误根因就是装在系统目录下又以非管理员身份去运行JMeter连自己的临时文件都删不掉。老老实实放到D:\tools\apache-jmeter这种地方能省掉一大半权限问题。所有节点装好后跑一下jmeter -v确认版本再检查JMeter运行脚本里的堆内存参数。强烈建议把HEAP调大一点尤其是Slave节点默认的1g很容易在处理大量采样结果时出现OOM。一般压测场景改成HEAP-Xms2g -Xmx4g内存充足的机器可以给到8G。2.2 分布式节点启动参数与端口配置环境准备好后开始配置节点。所有Slave机器在bin目录下找到jmeter.properties需要重点确认几个参数。第一个是server_port这是RMI的服务端口默认是1099一般不用改但要确认没有被防火墙挡掉。第二个是server.rmi.localport这个参数的作用是把RMI的动态端口固定下来否则JMeter每次连接会随机开放一个高位端口防火墙很难放行。建议在所有Slave上固定成一个端口比如server.rmi.localport4000安全组和防火墙只放行1099和4000两个端口比较干净。第三个是RMI SSL问题。JMeter从4.0开始默认启用RMI SSLMaster连接Slave时会做证书握手环境配置不一致时经常报SSL相关错误。如果你们是在内网压测不涉及敏感数据直接在jmeter.properties里把server.rmi.ssl.disable设为true同时Master端也要保持一致省掉很多证书交换的麻烦。Master端需要配置remote_hosts格式是Slave的IP加RMI端口多个节点用逗号分隔remote_hosts192.168.1.10:1099,192.168.1.11:1099配置完成后先到每台Slave本机运行jmeter-server启动脚本Windows是jmeter-server.batLinux/Mac是jmeter-server。看到日志输出类似“Starting the service”和“Created remote object”就说明启动成功这时候它会一直挂着监听RMI请求压测结束后也不会自动退出。再从Master上用telnet验证端口连通性telnet 192.168.1.10 1099 telnet 192.168.1.10 4000连通后再用命令行跑一次很小的测试脚本验证全链路。这一步不要跳过很多人直接上大并发结果Slave一个都连不上排查半天才发现是防火墙安全组没放行。我曾经在一个云环境里折腾过一整个下午最后发现是云安全组策略默认拦了所有非80/443端口不是JMeter本身的问题。2.3 录制 HTTPS 脚本时的安全证书处理很多JMeter新手是从录制脚本开始入门的录制HTTPS流量时会涉及到安全证书的处理。JMeter自身会生成一个临时根证书位置在bin目录下文件名大概是ApacheJMeterTemporaryRootCA.crt启动HTTP(S) Test Script Recorder时它会动态加载这个证书。录制步骤并不复杂先在工作台中添加HTTP(S) Test Script Recorder设置代理端口默认8888。接着在浏览器或手机上把代理指向JMeter所在机器的IP和端口然后访问HTTPS网站时浏览器会提示证书不被信任这时候就需要手动导入JMeter的根证书。Windows系统最简单双击crt文件选择“安装证书”导入到“受信任的根证书颁发机构”即可。Mac系统在钥匙串访问里导入证书后还要手动把该证书的信任策略改为“始终信任”否则Safari和Chrome依然不认。Android手机比较麻烦要先把代理设好然后浏览器访问http://代理IP:8888下载证书去系统设置里安装。需要提一句的是Android 7以上版本对用户证书的限制变严了部分App不走系统信任链录制到的HTTPS请求可能仍然抓不到明文这种情况我一般建议改用API文档直接手写脚本而不是继续跟证书较劲。证书相关的另一个坑是有效期。JMeter生成的临时证书默认有效期很短我印象里大概7天左右就会换新证书过期后需要重新导入。压测项目如果持续一两个月中间很容易踩到“昨天还能录今天突然全部失败”的坑优先检查证书是否过期。这里还要区分一下录制HTTP脚本用的证书和分布式RMI的SSL并不是一回事。很多人搜“JMeter安全证书”容易把这两个混在一起。录制证书解决的是浏览器信任问题RMI SSL解决的是Master和Slave通信加密问题排查时要能分清。3. 分布式场景下的脚本设计与运行3.1 设计可分布执行的测试脚本理想的分布式压测脚本应该是先在本地用GUI跑通逻辑再交给多台机器去放大执行。脚本设计上有几个关键点直接影响分布式执行的效果。第一个是线程数的规划。前面说过JMeter不会自动按节点拆分线程所以脚本里的线程数要填“目标总并发 ÷ 节点数”。如果你用插件或参数化来动态控制线程数也得在命令行用-G参数给每个节点传一样的值脚本里只引用变量。第二个是断言的选择。JMeter自带的响应断言能覆盖大部分场景但从JSON里取字段、判断某个值是否在合理区间、甚至调用一段自定义逻辑就得写脚本。例如用BeanShell断言判断响应文本String response prev.getResponseDataAsString(); if (response.contains(\code\:0)) { prev.setSuccessful(true); } else { prev.setSuccessful(false); prev.setResponseMessage(业务返回码异常); }这种方式很直观适合在调试阶段用。但我要提醒一句BeanShell是解释执行的性能很差高并发场景下会严重拖慢施压机。真到了大规模分布式压测建议把同样的逻辑改写成JSR223断言语言选Groovy执行效率高一个量级。JMeter 5.x里很多地方对BeanShell的支持已经在逐步弱化我也建议新项目直接用Groovy。第三个是数据文件的位置问题。脚本里如果有CSV Data Set Config要保证每台Slave都能读到同一份数据。最简单粗暴的方案是把数据文件复制到每台机器同一个绝对路径下比如/home/testdata/users.csv。如果脚本多、数据量大用NFS共享目录是更省心的做法。JMeter新版本提供的运行时文件根目录也可以设一个统一路径但底层还是要解决文件分发问题。3.2 命令行驱动分布式压测的正确姿势分布式压测一定要用命令行跑不要在GUI里点“远程启动”。GUI模式下监听器会把所有采样结果都维护在内存里Slave数量一多Master直接卡死。基础命令长这样jmeter -n -t plan.jmx -l result.jtl -R 192.168.1.10:1099,192.168.1.11:1099 -j run.log其中-R是指定远程Slave列表-r是使用remote_hosts配置里的所有Slave-j指定运行日志文件。如果你想传递一些全局属性给所有节点用-G参数jmeter -n -t plan.jmx -l result.jtl -r -Gusers2000 -Grampup60脚本里所有放置${users}和${rampup}的地方会被统一替换这个功能在做多轮梯度压测时特别有用不用每轮都改脚本。测试结束后想直接生成HTML报告加两个参数就行jmeter -n -t plan.jmx -l result.jtl -e -o report_dir-o指定的报告目录如果已存在需要先清空或者用-f强制覆盖。这里也有个很容易忽略的细节result.jtl文件路径不要复用上一次的文件名JMeter默认不会自动覆盖加-f参数会强制删除旧文件但一旦按错按钮之前的基线数据就没了。我的习惯是文件名里带时间戳比如result_20240520_1830.jtl压测数据自动留档。还有个经验脚本里的“查看结果树”和“聚合报告”监听器在命令行模式下并不会把数据写到jtl文件但会白白增加内存开销。分布式跑之前把脚本里这些监听器全部删掉只保留一个如果有必要可以写文件的Simple Data Writer否则并发一大施压机自身先崩。3.3 数据库压测脚本实操做后端性能测试经常要直接压数据库JMeter操作起来很顺手。首先把数据库驱动jar包放到JMeter的lib/ext目录下比如MySQL用的mysql-connector-java-8.0.x.jar然后重启JMeter。添加JDBC Connection Configuration时关键参数这样填配置项示例值Variable NamedbDatabase URLjdbc:mysql://10.0.0.5:3306/test_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiJDBC Driver Classcom.mysql.cj.jdbc.Driver连接池最大连接数50再添加JDBC Request选择Select类型SQL语句可以直接写也可以用参数占位符例如SELECT order_id, amount, status FROM orders WHERE user_id ? AND status 1 ORDER BY create_time DESC LIMIT 10参数值用${userId}从CSV文件里读取这样每个虚拟用户查的是不同人的订单更贴近真实场景。数据库压测最容易忽略的是连接池配置。单机跑的时候连接池默认不会成为瓶颈但分布式一上每台Slave的JMeter进程都会建立自己的一套连接池。假设数据库max_connections是200你有4台Slave每台连接池设成100那实际建立的连接马上就能打满数据库压出来的错误全是Too many connections。所以在设置连接池上限之前先算一下节点的数量。我的操作流程是先在一个Slave上用单线程循环执行三次SQL确认驱动加载正常、DSL语法正确然后再把线程数拉起来。这一步能在脚本错误阶段省下大量时间不然分布式全跑起来日志里全是SQL执行失败的记录排查起来心烦。压测数据库时除了看响应时间和TPS一定要同时监控数据库侧的连接数、慢查询数和锁等待。JMeter这边数据再好看数据库侧如果有大量慢查询那这个压测对优化就没有参考价值。3.4 文件上传场景中文文件名乱码解决压测文件上传接口的场景很常见但坑也最多。JMeter里做文件上传在HTTP请求的Files Upload标签页配置就行文件名填实际文件路径参数名称填接口约定好的字段名MIME类型按文件类型填。我第一次遇到中文文件名乱码是在Windows环境压测一个OSS类似的上传接口服务端收到的文件名全是乱码第一反应是去改HTTP请求的“内容编码”填上UTF-8发现问题依旧后来才发现是JMeter在Windows下读取文件路径时用的默认编码不是UTF-8导致的。这里的问题本质上有两层一是HTTP请求体里文件名参数的编码二是JVM处理文件系统路径时的默认编码。稳妥的方案是在JMeter的启动脚本里加一个JVM参数-Dfile.encodingUTF-8 -Dsun.jnu.encodingUTF-8Windows下尤其管用。修改后重启JMeter再跑脚本上传的中文文件名就能正常到达服务端。除此之外还有一个在分布式场景下被人忽略的问题每台Slave的文件路径必须存在且一致。Master上脚本指定的是D:\data\upload\test.jpg那所有Slave上也得有同样的路径和文件。我建议把所有节点上的上传文件放到同一个绝对路径下并在脚本里用CSV参数化文件名方便统一管理。上传大文件时也要注意JMeter会在内存里缓冲整个文件内容一个100MB的文件、同时50个并发单机内存直接翻车。压测超大文件接口时要么用更小的样本文件要么考虑换用JSR223和FileInputStream逐块读取否则没压垮被测系统先把施压机压垮了。4. 常见问题与排查实录4.1 节点连不上、端口不通怎么办分布式压测的报错类型来来回回就那么几种我把最常见的整理成一个速查表报错或现象可能原因常用解法Connection refused to hostSlave没有启动或端口不对确认jmeter-server进程存在netstat -anpRMI SSL connection failedMaster和Slave的SSL配置不一致两端统一设置server.rmi.ssl.disabletrueEOFExceptionMaster和Slave的JMeter版本不一致所有节点统一JMeter版本Engine is busySlave上一个任务没有正常结束重启jmeter-server进程Could not delete existing file权限不足或安装目录受限换到普通用户目录运行管理员授权结果树里大量connection reset施压机本地端口耗尽调大临时端口范围降低短连接比例排查的第一步永远先看日志。Master的运行日志在-r参数指定的run.log里Slave的日志在jmeter-server.log里。很多问题其实是同一个根因版本不一致。有一次我压测到一半一个节点怎么都连不上查了半天发现那台机器的JMeter是同事手动升级到5.6.3其他节点还是5.4.1版本一统一问题瞬间消失。端口不通的问题先用telnet测试不通就查防火墙和安全组不要一上来怀疑JMeter配置。RMI和动态端口两个都要放行这是我在云环境里踩过最多次的坑。4.2 结果收集不完整与文件同步分布式压测的结果文件由Master汇总生成但如果网络不稳或者某个Slave中途掉了就会出现jtl文件行数偏少、部分时间段数据缺失的情况。我排查这类问题时会先检查Master的运行日志搜索“Error in rconfigure”或者“Connection refused to host”这类关键字基本上能定位到是哪个Slave在什么时间点断开了。另一个隐蔽问题在CSV数据文件上。脚本依赖的CSV如果只在Master上有Slave文件路径下不存在JMeter不会直接报错而是所有用户都拿不到数据导致请求参数变成空值接口报参数校验错误。测试结果看起来一片红真正的问题却在施压端。我的标准做法是建立文件同步的固定流程每次调脚本版本就同步一次所有节点上的脚本、CSV数据文件和上传附件路径保持完全一致。同步完之后在每台Slave上跑一个单用户冒烟测试确认能读到数据再开始正式压测。4.3 数据隔离时间戳、随机数与并发命中分布式压测一个容易被忽略的问题是所有Slave跑的是同一份脚本如果不做数据隔离流量放大后会产生大量冲突数据。最常见的例子下单接口使用固定的订单编号并发一高大量请求命中同一条数据数据库锁竞争激烈测出来的响应时间完全失真。正确做法是把每个线程的请求数据做参数化。JMeter自带了很多好用的函数比如${__threadNum}_${__time(yyyyMMddHHmmss)}_${__RandomString(8,abcdefghijklmnopqrstuvwxyz)}这样生成的ID包含线程编号、精确到秒的时间戳和随机字符全局冲突概率极低。如果涉及业务数据比如手机号、身份证号我习惯按节点分桶给每台Slave分配不同的号码段避免跨节点撞车。还有一个经验是“压测结束后检查数据清洗”。分布式压测会往被测系统写入大量垃圾数据如果环境是长期的最好在脚本里预留清理后置处理器或者在压测前和开发约定好数据归档方案。这个问题没人提醒的话很容易被忽略等被别人找过来问“数据库多了几十万条测试订单”的时候就尴尬了。4.4 调度机自身压力过大Master不参与实际请求发送但它的工作并不轻松。每一条采样结果都要从Slave回传Master收到后还要解析、暂存、写文件。Slave一多Master的CPU和内存很快就顶不住了。缓解这个问题的招数有三板斧。第一脚本里不要放任何监听器尤其是结果树和聚合报告这个前面已经强调过。第二在jmeter.properties里调整回传数据模式让Slave只回传必要字段modeStrippedStripped模式下响应体数据不会被回传到Master能减少大量的网络和内存开销。缺点是你在命令行模式下拿不到响应体内容但对于压测结果统计来说响应时间、状态码、错误信息这些关键字段都还在影响不大。第三合理规划Master和Slave的配比。我自己的经验是一个Master最多管理五六个Slave再多的话调度开销会反噬测试效果。如果压测规模确实非常大可以考虑多组分布式环境并行跑每组独立Master汇总最后再手工合并分析。5. 从分布式测试走向系统性性能评估5.1 GB/T 39788-2021 对性能测试的划分分布式压测只是手段最终目标是搞清系统的性能边界。行业内现在有国标可以参考就是GB/T 39788-2021《系统与软件工程 性能测试方法》。这个标准把性能测试的类型和流程做了规范里面提到的负载测试、压力测试、容量测试、并发测试、疲劳测试等概念在做分布式压测方案设计时非常有用。简单区分一下这些类型负载测试是在预期负载下验证系统表现压力测试是逐步加负载直到系统崩溃边缘找拐点容量测试是确定系统能承载的最大业务量并发测试关注同时操作下资源的竞争疲劳测试则是长时间运行下检查资源泄露和性能衰减。我实际做分布式压测时最常用的是负载和压力两种用梯度加压往系统上顶观察TPS和响应时间什么时候开始恶化。标准里定义的核心性能指标也值得记牢响应时间、吞吐量、资源利用率和错误率。对应到JMeter的聚合报告里就是平均值、百分位、TPS和Error%。90%或95%百分位响应时间比平均值更能反映用户体验这个在方案里一定要写上。如果你们团队需要出正规的性能测试报告把测试结果跟这套标准术语对应起来报告会专业很多。比如“本次负载测试2500并发95%响应时间1.8秒TPS 7200达到容量测试目标”要比“我们压了一下系统还行”有说服力得多。5.2 分布式压测后的性能分析与调优建议压测跑完拿到jtl文件只是开始。先把jtl加载到聚合报告里看整体数据再按时间维度切成多个区间看趋势。我习惯先把报告导出来看响应时间百分位和TPS的曲线关系。如果TPS能随并发上升而上升说明系统还有余量如果TPS到了某个值之后不再增长甚至开始下降而响应时间快速恶化那这个拐点就是系统的性能瓶颈点。定位瓶颈要靠后端监控被测服务的CPU、内存、JVM GC日志、数据库连接数、慢查询、Redis/MQ等中间件的指标这时候分布式压测的优势就体现出来了流量足够大能把并发问题充分暴露出来。常见的瓶颈类型也有规律可循应用层线程池被打满表现是TCP连接建立后迟迟没有响应JMeter侧大量超时数据库慢SQL或锁等待表现是整体响应时间被拉长数据库侧连接数飙升连接池配置过小表现是错误率升高但服务端CPU和数据库都还很空闲。调优是循环过程定位瓶颈、修改配置、重新压测、对比数据。每一轮压测的结果都要保留好基线没有基线的性能测试等于白做。你可以用表格记录每轮调整压测轮次并发数TPS95%响应时间错误率瓶颈定位调整项基线10003200280ms0.1%应用线程池满调大线程池至300第二轮10003800210ms0.05%数据库连接池连接池50→100分布式测试的意义在于你能够把压测负载真正推到系统上限让这些瓶颈有序暴露出来而不是被施压机自身的能力挡住。最后分享一个我个人用了很久的小习惯压测环境里的JMeter版本、脚本、数据文件、参数配置全部用一份部署清单记录下来每台机器核对一遍再开工。分布式压测最耗时的往往不是压测本身而是环境不一致带来的排查成本。把这些前置工作做成硬流程后面能少熬夜好几宿。
返回列表