JMeter HTTP缓存管理器:构建真实性能测试场景的核心配置

1. 项目概述:为什么性能测试必须关注HTTP缓存?

如果你做过Web应用的性能测试,尤其是用JMeter模拟用户行为,你很可能遇到过这样的困惑:为什么脚本里明明设置了100个并发用户,但后端服务器的请求数却远低于预期?或者,为什么同一个脚本,第一次运行和第二次运行的响应时间、服务器负载差异巨大?很多时候,问题的根源不在于你的脚本写错了,而在于你忽略了一个关键因素——浏览器缓存

在真实的用户场景中,浏览器会缓存静态资源,如图片、CSS、JavaScript文件。用户首次访问一个电商首页,可能会加载几十个请求;但当他刷新页面或浏览其他页面时,大量静态资源会直接从本地缓存读取,不再向服务器发起请求。这种缓存行为,对服务器压力、网络流量和用户体验有决定性影响。如果你的性能测试脚本不模拟这种行为,那么你测出来的“性能”就是一个失真的、过于乐观的假象。你可能会高估服务器的并发处理能力,低估了真实场景下的资源消耗,最终导致线上系统在流量高峰时崩溃。

HTTP缓存管理器(HTTP Cache Manager)就是JMeter中用来解决这个问题的核心配置元件。它不是一个可有可无的“高级功能”,而是构建一个真实、可信性能测试场景的基石。今天,我就结合自己多年踩坑的经验,把这个看似简单的配置元件掰开揉碎了讲清楚,让你不仅知道怎么配,更明白为什么要这么配,以及在不同场景下如何灵活运用。

2. HTTP缓存管理器核心原理与工作机制

2.1 缓存管理器在JMeter架构中的角色

要理解HTTP缓存管理器,首先要把它放在JMeter的整个请求处理流程中来看。JMeter处理一个HTTP请求,大致会经历以下几个阶段:

  1. 线程组/控制器调度:决定何时执行哪个取样器(Sampler)。
  2. 配置元件预处理:在取样器执行前,配置元件(如HTTP请求默认值、HTTP头管理器、HTTP缓存管理器)会先对请求进行预处理和配置。
  3. 取样器执行:发送实际的HTTP请求。
  4. 后置处理器/断言处理:对服务器的响应进行处理和验证。
  5. 监听器记录结果:收集并展示测试结果。

HTTP缓存管理器属于配置元件,并且它的执行顺序非常靠前。它的核心工作是:在HTTP请求取样器即将发送请求之前,根据一套规则判断本次请求的资源是否已经存在于“缓存”中,并且该缓存是否仍然有效(未过期)。如果判断为“缓存命中且有效”,JMeter就会跳过实际的网络请求,直接使用缓存中的响应数据来生成一个“虚拟”的采样结果,从而模拟浏览器从本地磁盘读取缓存的行为。

这个“缓存”并非存储在磁盘上,而是存在于JMeter运行时的内存中。每个缓存条目(Cache Entry)通常由请求URL请求方法(如GET)作为键(Key),而缓存的值(Value)则包括上次请求返回的响应体、响应头(特别是与缓存相关的头信息)以及一个时间戳。

2.2 缓存决策的核心逻辑:RFC协议与头信息解析

HTTP缓存管理器模拟的是符合HTTP/1.1 RFC 7234等标准的浏览器缓存逻辑。它主要依据服务器返回的响应头(Response Headers)来决定资源的缓存行为。以下几个头信息是关键:

  • Cache-Control: 这是现代HTTP缓存控制最常用、最强大的指令集。
    • max-age=<seconds>: 指定资源从被请求开始算起,在多长时间内(秒)被认为是新鲜的(fresh),可以直接使用缓存,无需向服务器验证。例如Cache-Control: max-age=3600表示缓存1小时。
    • no-cache: 不是“不缓存”,而是“使用缓存前必须向服务器验证”。每次请求都会发起到服务器的验证(通常带If-None-MatchIf-Modified-Since头),如果服务器返回304 Not Modified,则使用本地缓存。
    • no-store: 真正的“禁止缓存”,响应内容不得被存储在任何形式的缓存中(包括浏览器和代理缓存)。
    • public/private:public表示响应可被任何中间缓存(如CDN、代理)缓存;private通常表示响应只针对单个用户,不应被共享缓存存储。
  • Expires: 一个绝对的过期时间(GMT格式),例如Expires: Wed, 21 Oct 2024 07:28:00 GMT。这是一个较老的字段,在存在Cache-Control: max-age时,后者优先级更高。
  • ETagLast-Modified: 这两个是验证器(Validator)。当缓存过期(stale)后,浏览器会带着这些信息(通过If-None-MatchIf-Modified-Since请求头)询问服务器资源是否变更。若未变(304响应),则刷新缓存新鲜度并继续使用;若已变(200响应),则下载新资源。

HTTP缓存管理器会解析这些响应头,并据此在内存中维护一个缓存表。当下次同线程(或共享缓存的线程)发起相同请求时,它会:

  1. 查找缓存中是否存在该URL的条目。
  2. 检查缓存是否过期(根据max-ageExpires计算)。
  3. 如果未过期,则直接命中缓存,不发送请求。
  4. 如果已过期,则根据配置决定是直接发送新请求,还是先发送一个验证请求(带条件头)。

实操心得:理解“模拟”的边界JMeter的HTTP缓存管理器是一个“模拟器”,它无法100%复现真实浏览器的所有缓存细节。例如,浏览器缓存有磁盘和内存之分,有复杂的启发式过期算法,而JMeter的缓存完全在内存中,且逻辑相对标准。但这并不妨碍它成为性能测试中至关重要的工具。我们的目标是模拟缓存对服务器请求量的核心影响,只要这个核心影响被正确模拟,测试结果就是有参考价值的。不要陷入追求绝对一致的牛角尖。

3. HTTP缓存管理器的详细配置与参数解读

在JMeter中添加HTTP缓存管理器后,你会看到一个配置界面,里面有几个关键的选项。每一个选项背后都有其设计意图和使用场景。

3.1 基础配置项详解

  1. “Use Cache Control?” / “使用缓存?”

    • 作用:这是缓存管理器的总开关。只有勾选此项,缓存功能才会生效。
    • 默认值:勾选。
    • 配置建议绝大多数情况下都应勾选。除非你在进行一种特殊的测试,比如故意测试“禁用所有缓存”时服务器的极限压力,否则都应该开启它以模拟真实用户行为。
  2. “Clear cache each iteration?” / “每次迭代清除缓存?”

    • 作用:控制是否在每个线程的每次循环迭代开始时,清空其缓存。
    • 默认值:不勾选。
    • 配置建议:这是最容易配置错误的地方之一。需要根据测试场景仔细斟酌。
      • 不勾选(默认):模拟同一个浏览器标签页的会话行为。用户第一次访问网站,加载所有资源并缓存;在后续的页面刷新或跳转中(同一次浏览器会话),只要缓存未过期,就会使用缓存。这是最常见的场景,用于模拟用户在一个会话内的持续操作。
      • 勾选:模拟每次访问都像是打开一个新的浏览器窗口或隐身模式。每次迭代开始,缓存都是空的,所有资源都会重新请求。这适用于测试“新用户首次访问”场景下的服务器压力,或者测试缓存完全失效时的性能表现。
  3. “Max Number of elements in cache” / “缓存中的最大元素数”

    • 作用:限制缓存中可以存储的条目数量。当缓存条目数达到上限后,旧的条目会根据LRU(最近最少使用)等算法被移除。
    • 默认值:5000。
    • 配置建议:对于现代富Web应用,一个页面可能加载上百个资源。如果模拟的用户路径较长(访问多个页面),5000条可能不够。你可以根据测试估算:并发用户数 × 每个用户会话访问的独特资源数 × 安全系数(如1.5)。如果设置过小,可能导致缓存频繁失效,影响测试真实性。通常可以先保持默认,通过监听器观察缓存命中率,如果命中率异常低,再考虑调大。

3.2 高级配置与自定义行为

在基础配置下方,通常还有一个“Advanced”或类似区域,包含更精细的控制。

  1. “Use ‘Expires’ header?” / “使用‘Expires’头?”

    • 作用:是否遵循响应头中的Expires字段来决定缓存过期时间。
    • 默认值:勾选。
    • 配置建议:通常勾选。如果服务器同时返回了Cache-Control: max-ageExpires,JMeter会优先使用max-age。只有当没有max-age时,才会使用Expires。保持勾选可以兼容更老的服务器配置。
  2. “Cache only responses with HTTP status 200?” / “仅缓存状态码为200的响应?”

    • 作用:是否只缓存服务器返回200 OK的响应。
    • 默认值:不勾选(即也会缓存如304 Not Modified等响应)。
    • 配置建议强烈建议勾选。这是基于真实浏览器行为的最佳实践。浏览器通常只缓存成功的、可缓存的响应(200 OK)。缓存一个304响应(只是一个“未修改”的指示)或错误响应(如404)没有意义,甚至可能导致脚本行为异常。勾选此项能使模拟更准确。
  3. “Apply ‘Last-Modified’ header?” / “应用‘Last-Modified’头?”

    • 作用:是否在缓存条目过期后,发送带有If-Modified-Since头的条件请求(Conditional Request)去服务器验证。
    • 默认值:勾选。
    • 配置建议必须勾选。这是实现“协商缓存”的关键。当缓存过期后,浏览器不会直接重新下载整个资源,而是先问服务器“我这个版本(基于最后修改时间)的资源还有效吗?”。如果服务器说有效(返回304),浏览器就继续使用缓存并更新其新鲜度。这能极大地节省带宽和服务器资源。不勾选此项,JMeter会在缓存过期后直接发起全新的GET请求,这与真实浏览器行为不符,会高估服务器负载。
  4. “Apply ‘ETag’ header?” / “应用‘ETag’头?”

    • 作用:是否在缓存条目过期后,发送带有If-None-Match头的条件请求去服务器验证。ETag是比Last-Modified更精确的验证器,通常是一个哈希值。
    • 默认值:勾选。
    • 配置建议必须勾选。理由同上,ETag是更现代的验证机制。同时启用Last-ModifiedETag可以让JMeter的模拟行为覆盖更广的服务器配置。

3.3 配置位置策略:全局、线程组还是控制器?

HTTP缓存管理器应该放在哪里?这取决于你的测试计划结构和对缓存作用域的需求。

放置位置作用域适用场景操作步骤
测试计划(Test Plan)根节点全局共享。所有线程组中的所有线程共享同一个缓存池。模拟所有用户共享公共CDN或代理缓存的场景。例如,测试一个新闻网站,所有用户访问的首页静态资源是相同的,可以被公共缓存服务。右键点击测试计划名称 -> 添加 -> 配置元件 -> HTTP缓存管理器。
线程组(Thread Group)内部线程组内共享。该线程组内的所有线程(虚拟用户)共享缓存,但不同线程组之间的缓存隔离。最常见的配置。模拟一组具有相似行为的用户。例如,一个线程组模拟“浏览用户”,另一个线程组模拟“搜索用户”,他们的缓存行为可能不同,需要隔离。右键点击线程组 -> 添加 -> 配置元件 -> HTTP缓存管理器。
逻辑控制器(如Simple Controller)内部控制器内共享。只对该控制器下的取样器生效。用于精细化控制。例如,在一个复杂的业务流中,只有其中几个步骤需要模拟特定的缓存行为(如清空缓存),可以单独为这几个步骤配置一个带缓存管理器的控制器。右键点击逻辑控制器 -> 添加 -> 配置元件 -> HTTP缓存管理器。

注意事项:作用域与性能将缓存管理器放在更高级别(如测试计划级)可以减少内存开销,因为只有一个缓存实例。但如果测试场景要求不同用户群有独立的缓存状态(比如模拟新用户和老用户),就必须放在线程组级别。不建议在同一个作用域内添加多个HTTP缓存管理器,这会导致行为不可预测。

4. 实战:构建一个模拟真实用户行为的测试计划

理论讲完了,我们动手搭建一个测试场景。假设我们要测试一个博客网站的文章列表页和文章详情页。

场景描述

  1. 用户首次访问博客首页 (/),加载页面框架、通用CSS/JS、Logo图片等。
  2. 用户点击第一篇文章进入详情页 (/article/1)。
  3. 用户返回首页,然后点击第二篇文章 (/article/2)。
  4. 我们模拟10个这样的并发用户。

我们的目标是:模拟用户在同一个浏览器会话中的行为,即首页的公共资源只在第一次访问时加载,后续访问从缓存读取。

4.1 测试计划结构搭建

  1. 创建线程组

    • 添加一个Thread Group
    • 设置线程数:10, 循环次数:2(模拟每个用户执行两次“首页->文章A->首页->文章B”的流程)。Ramp-Up时间设为1秒。
  2. 添加HTTP请求默认值

    • 右键线程组 -> 添加 -> 配置元件 -> HTTP请求默认值。
    • 填写服务器名称或IP(如www.myblog.com)。这样后续的HTTP请求就不用重复填写了。
  3. 添加HTTP缓存管理器(核心步骤)

    • 右键线程组 -> 添加 -> 配置元件 -> HTTP缓存管理器。
    • 配置如下:
      • Use Cache Control?:勾选
      • Clear cache each iteration?:不勾选(因为我们模拟同一会话内的多次访问)。
      • 其他高级选项保持默认勾选(使用Expires、仅缓存200、应用Last-Modified和ETag)。
  4. 添加HTTP信息头管理器

    • 右键线程组 -> 添加 -> 配置元件 -> HTTP信息头管理器。
    • 添加一个通用的头,例如User-Agent: JMeter-Performance-Test/1.0,以标识测试流量。

4.2 设计用户操作流(使用逻辑控制器)

我们使用Once Only ControllerSimple Controller来组织请求顺序。

  1. 添加“首次访问-加载缓存”控制器

    • 右键线程组 -> 添加 -> 逻辑控制器 -> 仅一次控制器(Once Only Controller)。将其重命名为“01_First Visit - Load Cache”。
    • 这个控制器下的请求在每个线程的整个生命周期内只执行一次,完美模拟用户首次打开浏览器访问网站的场景。
    • 在该控制器下,添加HTTP请求取样器:
      • 名称:GET Homepage (First Time)
      • 路径:/
    • (可选)为了更真实,你可以在这个控制器下添加对首页关键静态资源的请求,比如:
      • GET /static/css/app.css
      • GET /static/js/app.js
      • GET /static/images/logo.png
    • 实际上,如果首页是HTML,浏览器会解析HTML并自动发起对这些静态资源的请求。在JMeter中,我们需要手动添加这些请求来模拟,或者使用像“HTML Link Parser”这样的后置处理器来自动抓取和请求。为了简化示例,我们假设首页请求本身就能代表对核心静态资源的加载。
  2. 添加“用户浏览循环”控制器

    • 右键线程组 -> 添加 -> 逻辑控制器 -> 简单控制器(Simple Controller)。将其重命名为“02_User Browsing Loop”。
    • 在这个控制器下,我们将放置模拟用户后续操作的请求。因为线程组循环2次,所以这个控制器下的请求会执行两次。
    • 在该控制器下,按顺序添加HTTP请求取样器:
      • 请求1:GET Homepage (Cached)
        • 路径:/
        • 预期行为:由于首页HTML可能设置了较短的缓存时间或无缓存,这个请求可能还是会发到服务器。但首页引用的CSS/JS/图片等静态资源,只要缓存未过期,就不会再请求。
      • 请求2:GET Article 1
        • 路径:/article/1
      • 请求3:GET Homepage (Cached)
        • 路径:/
      • 请求4:GET Article 2
        • 路径:/article/2

4.3 添加监听器与分析结果

为了观察缓存的效果,我们需要添加监听器。

  1. 添加“查看结果树”

    • 右键线程组 -> 添加 -> 监听器 -> 查看结果树。
    • 在测试运行时,观察请求的发送情况。对于缓存命中的请求,你会在“取样器结果”中看到类似Response code: Non HTTP response code: java.net.UnknownServiceException吗?不,更典型的是,JMeter会记录一个成功的采样,但其字节数(Bytes)会显著减少,并且响应时间极短(接近0ms),因为根本没有发生网络传输。在“请求”标签页,你甚至可能看不到HTTP请求头被发送出去。这是缓存生效的直接证据。
  2. 添加“聚合报告”或“汇总报告”

    • 右键线程组 -> 添加 -> 监听器 -> 聚合报告。
    • 运行测试后,关注关键指标:
      • 样本数(Samples):这是实际发送到服务器的请求总数。由于缓存的存在,这个数会远小于线程数 × 循环次数 × 请求数的理论值。
      • 平均响应时间(Average):缓存命中的请求响应时间极短,会拉低整体平均值。因此,分析时最好结合“90%百分位(90% Line)”或“中位数(Median)”来看,它们更能反映真实服务器处理的请求耗时。
      • 吞吐量(Throughput):单位时间处理的请求数。因为总请求数减少,吞吐量指标的意义会发生变化,它现在更接近于“服务器实际需要处理的请求吞吐”,而不是“客户端发起的请求吞吐”。
  3. (高级)使用“后端监听器”或“Prometheus监听器”

    • 为了更细致地分析,可以将结果发送到时序数据库如InfluxDB,然后用Grafana绘图。你可以创建两个图表:
      • 总请求率(Client-side):所有JMeter取样器被调用的频率。
      • 实际服务器请求率(Server-side):通过过滤掉响应时间<1ms的请求来近似得到。
      • 两者之间的差距,就是缓存命中率的直观体现。

运行这个测试计划,你就能清晰地看到,在第二次循环时,对首页(/)和文章页的请求,其关联的静态资源请求量会大幅下降,服务器负载显著降低,这就是正确模拟缓存带来的效果。

5. 高阶技巧与常见问题排查

5.1 动态资源与缓存破坏

现代Web应用大量使用动态资源,其URL中可能包含版本号或哈希值,例如/static/js/app.abc123.js。每次构建后,哈希值改变,URL就变了,这本身就是一种“缓存破坏”策略。对于这种资源,即使你设置了缓存管理器,由于每次测试的URL可能不同(如果你在测试不同版本),缓存也不会命中。

应对策略

  • 在测试中,如果静态资源的URL是动态生成的,你需要使用正则表达式提取器JSON提取器从HTML响应中提取出这些资源的实际URL,然后在后续的请求中引用这些变量。这样,在同一轮测试中,URL是固定的,缓存才能生效。
  • 这更贴近真实用户行为:用户访问一个页面,浏览器解析HTML,得到一堆资源URL并发起请求。我们的JMeter脚本也应该做到这一点。

5.2 处理Cache-Control: no-cacheno-store

  • no-cache:如前所述,JMeter在配置了应用Last-ModifiedETag后,会正确处理no-cache。它会为每个请求添加上条件头(If-None-Match,If-Modified-Since)向服务器验证。你需要确保你的脚本能正确处理304响应。通常,JMeter的HTTP请求默认能处理304,但如果你在响应断言中写了“期望响应代码是200”,就会失败。需要将断言修改为“响应代码匹配 200 或 304”。
  • no-store:这是最严格的指令。JMeter的HTTP缓存管理器在遇到Cache-Control: no-store的响应时,不会将其存入缓存。这是符合RFC规定的。如果你的测试资源都返回no-store,那么缓存管理器将完全不起作用。这时你需要检查服务器配置,或者明确你的测试目标就是“无缓存”场景。

5.3 调试缓存是否生效

如果你不确定缓存管理器是否在工作,可以按以下步骤排查:

  1. 启用JMeter调试日志

    • 修改JMeter的log4j2.xml配置文件(位于JMeter的bin目录下)。
    • 找到org.apache.jmeter.protocol.http.control这个Logger,将其级别改为DEBUG
    <Logger name="org.apache.jmeter.protocol.http.control" level="debug" />
    • 重启JMeter并运行测试,在jmeter.log文件中你会看到详细的缓存决策日志,例如“Found cached entry...”、“Caching response for...”等。
  2. 使用“查看结果树”过滤

    • 在“查看结果树”监听器中,添加一个“仅展示错误日志”的过滤器可能看不到缓存相关消息。更好的方法是观察请求的“响应数据”大小和“耗时”。缓存命中的请求,响应数据为空或极小,耗时在毫秒级。
  3. 对比测试

    • 最直接的方法:复制一份测试计划,一份启用缓存管理器,一份不启用。使用相同的线程组设置运行,然后对比聚合报告中的“样本数”。启用缓存的样本数应该明显更少。

5.4 常见问题速查表

问题现象可能原因排查与解决方案
缓存似乎完全没起作用,所有请求都发送了。1. “Use Cache Control?”未勾选。
2. 服务器响应头包含Cache-Control: no-store
3. 每次请求的URL都不同(如包含时间戳或随机参数)。
4. 将缓存管理器放在了错误的作用域(如放在某个不包含目标请求的控制器下)。
1. 检查缓存管理器配置。
2. 使用“查看结果树”检查服务器响应头。
3. 检查请求URL是否动态变化,考虑使用变量或固定值。
4. 确保缓存管理器的作用域能覆盖到需要缓存的HTTP请求取样器。
测试结果中出现了大量304状态码。这是正常且期望的行为!说明缓存管理器正确工作了。资源已缓存但已过期,JMeter发送了条件请求,服务器返回304表示资源未修改。无需解决。这恰恰模拟了真实的浏览器行为。确保你的监听器和断言能正确处理304响应。
同一个线程组内,用户A的缓存影响了用户B的请求。将缓存管理器放在了测试计划级别,导致所有线程共享全局缓存。这模拟的是公共CDN,而非用户私有缓存。如果目的是模拟每个用户的独立浏览器缓存,应将缓存管理器移至线程组级别。
第一次迭代缓存有效,第二次迭代缓存失效了。勾选了“Clear cache each iteration?”。这导致每次迭代开始前缓存被清空。根据测试场景决定:若模拟同一会话,则不勾选;若模拟每次都是新会话,则勾选。
缓存命中后,响应时间不为0,反而有一个固定的小值。这是正常的。JMeter即使从缓存返回数据,也需要经过内部的处理流程,会消耗极少的CPU时间。这个时间通常稳定在1毫秒左右,可以视为缓存命中的标志。在分析平均响应时间时,可以过滤掉这些极小值,或者关注90%百分位、95%百分位等指标,它们更能反映真实服务器请求的延迟。

6. 性能测试策略与缓存管理器的结合运用

在实际的性能测试项目中,HTTP缓存管理器不是孤立使用的,它需要融入整体的测试策略。

策略一:基线测试与负载测试

  • 基线测试(无缓存):首先,在禁用缓存(或不添加缓存管理器)的情况下运行测试,获取服务器在“最坏情况”——即所有用户都是首次访问,没有任何缓存——下的性能表现(吞吐量、响应时间、错误率)。这个数据是系统的性能底线。
  • 负载测试(有缓存):然后,在启用缓存管理器并合理配置(模拟真实用户会话)的情况下,运行相同场景的测试。你会得到系统在“典型情况”下的性能表现。对比两个结果,你可以量化缓存为系统带来的性能收益,例如:“在我们的测试场景下,有效的浏览器缓存减少了约70%的静态资源请求,使系统整体吞吐量提升了35%”。这个结论对运维和开发团队极具价值。

策略二:混合场景建模一个真实的系统,用户不会是同质的。通常会有:

  • 新用户(无缓存):占比X%。
  • 回访用户(有完整缓存):占比Y%。
  • 活跃用户(部分缓存过期):占比Z%。

你可以通过创建多个线程组来模拟:

  • 线程组A(新用户):设置Clear cache each iteration?=勾选,或者使用Once Only Controller配合CSV Data Set Config来为每个线程提供唯一的用户标识,确保缓存不共享。
  • 线程组B(回访用户):设置Clear cache each iteration?=不勾选,并在线程组启动前,先运行一个“预热”脚本,让这些线程预先访问关键页面填充缓存。
  • 线程组C(活跃用户):可以配置一个更短的缓存过期时间,或者使用JSR223 PreProcessor在请求前随机地清除部分缓存条目,来模拟缓存部分失效的状态。

策略三:缓存失效测试缓存能提升性能,但缓存失效策略如果设计不当,会导致用户看到过期数据。我们可以设计测试来验证:

  1. 模拟大量用户访问一个带有缓存资源的页面(缓存未过期)。
  2. 在测试运行中,后台更新服务器上的资源(例如,更新一个CSS文件)。
  3. 观察后续用户的请求:他们是否收到了更新后的资源?还是旧的缓存?
  4. 这需要配合服务器的缓存策略(如通过修改文件名哈希或使用Cache-Control: max-age配合Last-Modified)来测试。JMeter脚本可以设计在特定时间点后,检查响应内容是否包含预期的更新版本。

配置和使用HTTP缓存管理器的过程,本质上是一个不断追问“真实用户是如何操作的?”的过程。它迫使测试人员去理解HTTP协议、浏览器行为和应用架构,从而设计出更具说服力的性能测试场景。记住,一个不考虑缓存的性能测试,就像在真空中测试汽车油耗——数据可能很漂亮,但毫无现实意义。