
1. es head 插件到底是什么先把它的定位讲透我是从 ES 还在用 1.x 那个年代就开始折腾集群的那批人之一。那时候团队里排查一个索引为什么突然变黄了的问题第一反应真不是打开终端敲 curl而是先开浏览器输入http://localhost:9200/_plugin/head/扫一眼集群概览。这个习惯一直保留到今天只不过地址从_plugin/head变成了http://localhost:9100。es head 插件本质上就是一个纯前端写的 Elasticsearch 集群可视化客户端它不参与数据存储不修改集群行为干的事情说白了只有一件把你本来要手写的 REST 请求包装成能点、能看、能翻页的界面。先把它的边界说清楚避免新手把它当成必须装的东西。head 不是 Elasticsearch 的组成部分不是官方组件也不在任何一份官方的生产部署清单里。它更像是一把放在工具箱里的螺丝刀——平时躺在那里没人看但真要拧一颗特定的螺丝时比整套电动工具都顺手。你在 head 上做的每一次操作背后都是一条标准的 HTTP 请求打到 9200 端口比如你点刷新按钮实际发出去的是POST /index_name/_refresh你在数据浏览里翻到第二页实际发出去的是带from和size的_search请求。理解这一点非常关键因为后面所有的连不上点了没反应报 403根子都在这个模型上。1.1 它只是一层前端壳子别把它想复杂了head 的架构简单到有点朴素一个静态的 HTML JavaScript 单页应用。所有逻辑都跑在浏览器里通过网络请求直连 Elasticsearch 暴露的 HTTP 端口。这意味着两件事第一它不需要安装在任何 Elasticsearch 节点上你完全可以在一台跳板机上跑一个 head去管理十个不同机房的集群第二它的权限完全等于你给它的那个连接地址的权限head 本身没有任何用户体系它能做什么取决于 Elasticsearch 那边允不允许。我见过不少团队踩的第一个坑就在这儿以为装个 head 就等于给集群加了一层管理后台于是按后台系统的标准去要求它比如希望它有登录页、有操作审计、有权限分级。这些它统统没有。你要权限得靠 Elasticsearch 自己的安全模块或者在前置的 Nginx 上做 Basic Auth。把它的定位摆正后面很多期待落空的失望感就没了。另一个容易被忽略的点是head 的数据全部来自 Elasticsearch 的响应它自己不做任何缓存。所以你在 head 上看到的分片分布、节点负载、索引大小都是你点下去那一瞬间的真实快照。刷新一下就是新的。这个特性和 Kibana 的图表不一样Kibana 可以做时间序列的聚合和留存head 永远只给你现在。1.2 五块面板各管什么一张表说清楚打开 head 之后顶部标签栏就那么几个入口每个都对应一类明确的操作。很多人用了半年也只点过概览和数据浏览其实另外几块面板的价值同样很高尤其是复合查询它基本可以替代 80% 的 curl 场景。面板名称背后主要调用的接口我平时用它做什么概览Overview_cluster/health、_cluster/stats、_nodes/stats一眼确认集群名称、健康色、节点数、分片数、文档总数、占用存储、JVM 堆使用率索引Indices_cat/indices、_mapping、_alias建索引、删索引、开关索引、刷新、清缓存看每个索引的分片数、副本数、文档量和大小数据浏览Browser_search、_mapping挑一个索引按字段筛选、排序、翻页看原始文档确认字段值到底写成了什么样基本查询Query_search结构化拼装用下拉框拼一个字段等于某值的查询不想手写 DSL 时的快捷入口复合查询Any Request任意 REST 接口手写 GET/POST/PUT/DELETE 请求带 JSON body这是它最有价值的一块这张表里最值得投入时间的是最后一块。复合查询面板本质上是一个极简版的 REST 客户端你可以选请求方法、填路径、贴 JSON 体然后执行。它还会保存你的历史请求记录下次直接点一下就能重放。我排查线上问题时经常用它快速验证一条 DSL 的返回结构确认没问题了再写进业务代码比在终端里拼 curl 的引号舒服得多。注意head 的数据浏览看到的永远是文档的_source原文也就是你写进去时的那个 JSON。如果某个字段在 mapping 里被定义了index: false或者用了特殊的 analyzer你在业务侧的检索结果和这里看到的内容可能对不上判断问题时要以检索实测为准别只盯着_source。2. 三种跑法怎么选site 插件、npm 源码、Docker同一个 head在不同年代有三种完全不同的启动方式这也是新手最容易绕晕的地方。你在网上搜教程一半文章告诉你去bin/plugin install另一半告诉你去npm install还有一堆说docker run。它们说的都对只是分别对应不同的 Elasticsearch 版本区间。搞清楚这个时间线能省下你至少两个小时的试错。2.1 为什么 5.x 之后_plugin/head这个地址打不开了在 Elasticsearch 2.x 及更早的版本里集群支持一种叫site plugin站点插件的扩展机制。你执行一条安装命令head 的那堆静态文件就被丢进集群的插件目录然后通过http://主机:9200/_plugin/head/这个路径直接访问。好处是极其方便集群起在哪head 就在哪连跨域问题都不存在因为页面和接口是同一个源。从 Elasticsearch 5.0 开始site plugin 机制被整体移除了官方只保留了 Java 层面的插件扩展点。理由也很直接让一个搜索引擎去托管前端静态资源属于职责越界而且插件目录里塞网页会带来一堆安全审计上的麻烦。于是 head 被迫转型成一个独立运行的前端应用默认监听 9100 端口。从那一刻起跨域问题就成了所有 head 用户的必修课因为页面在 9100接口在 9200浏览器会认定这是两个不同的源。所以如果你现在手上的集群是 7.x 或者 8.x看到老教程里那个_plugin/head地址打不开不要怀疑自己配置错了那个时代已经过去了。你要走的是下面两种方式之一。2.2 npm 源码方式从克隆到改端口这种方式适合想顺手改点源码的同学比如后面我们会讲到的Content-Type修正就必须在源码层面动手。前提是本机有 Node.js 环境版本不用太新老版本的 head 对高版本 Node 兼容性一般我一般用 Node 14 或 16 跑它比较稳。# 拉取源码 git clone https://github.com/mobz/elasticsearch-head.git cd elasticsearch-head # 安装依赖这一步偶尔会因为网络慢而卡住多试一次通常就好 npm install # 启动开发服务默认监听 9100 npm run start启动之后浏览器访问http://localhost:9100就能看到那个标志性的灰色界面左上角有一个连接地址输入框。默认它填的是http://localhost:9200如果集群不在本机改掉再点连接左侧的节点图标变绿就说明通了。这里有个实用的小改动很多人 9100 端口上已经跑了别的东西或者需要在同一台机器上跑两个 head 分别指向测试集群和生产集群。改端口的位置在项目根目录的Gruntfile.js里找到connect: { server: { options: { port: 9100这一段把数字改掉即可。改完重启生效。这个技巧看起来微不足道但当你需要在两个集群之间来回对比数据的时候两个 head 并排开在浏览器两个标签页里效率提升非常明显。2.3 Docker 方式三行命令跑起来以及要避的坑对绝大多数人来说Docker 是最省事的方式特别是你不想在服务器上装 Node 环境的时候。# 拉取镜像 docker pull mobz/elasticsearch-head:5 # 启动把容器内的 9100 映射到宿主机 9100 docker run -d --name es-head -p 9100:9100 mobz/elasticsearch-head:5跑起来之后访问http://服务器IP:9100就行了。这里有几个坑值得单独拎出来说。第一个坑是镜像标签。很多人直接docker pull mobz/elasticsearch-head不加标签拿到的是latest这个标签指向的其实是一个相当老的版本界面能用但一些小问题比较多。目前社区里普遍用的是:5这个标签兼容性最好。如果你的环境对镜像体积敏感还有:5-alpine之类的精简版本可选。第二个坑是理解容器到底在干什么。这个容器做的事情就是起一个静态文件服务器把 head 的页面吐给你的浏览器仅此而已。它和 Elasticsearch 之间没有任何网络连接。所以你把容器部署在 A 机器、集群在 B 机器完全没问题真正需要通的是你的浏览器到集群 9200这条路。很多人在这件事上绕了远路为了让容器能访问集群费劲地把两个容器放进同一个 Docker 网络结果发现该跨域的照样跨域因为真正发请求的是浏览器不是容器。第三个坑是容器的--network host误区。有些教程建议用 host 网络模式说这样能解决连接问题。实际上如果只是为了让浏览器能访问 9100端口映射就够了host 模式反而会让端口占用冲突的概率变高在某些环境下还会带来不必要的暴露面。除非你有明确的理由否则老老实实用-p映射更干净。3. 连不上集群的真正原因跨域配置逐行拆前面反复提到跨域这里把它彻底讲透。我统计过自己带过的几批新人十个人里至少有八个第一次用 head 卡在页面能打开但左边树形菜单一直是空的或者点连接提示未连接。这两个现象背后的原因几乎永远是同一个浏览器把请求发出去了Elasticsearch 收到了也返回了结果但浏览器看到响应里没有允许跨域的头部于是把结果丢掉JavaScript 拿不到任何数据界面上自然什么都不显示。3.1 浏览器同源策略为什么一定会拦你同源策略的核心判断标准是三个要素协议、域名、端口。三者完全相同才算同源。head 页面在http://192.168.1.100:9100集群接口在http://192.168.1.101:9200端口和主机都不一样妥妥的跨域。浏览器在正式发请求之前对非简单请求还会先发一个OPTIONS预检请求问一句我能不能带着这些头部访问你。Elasticsearch 默认不回应这类跨域请求预检失败真正的请求根本不会被发出去。关键点在于这个拦截发生在浏览器里不在服务器上。所以你去看 Elasticsearch 的日志往往什么都看不到会觉得集群根本没收到请求啊是不是网络不通。这时候别急着查防火墙先用 curl 在 head 所在机器上直接打一下curl http://集群IP:9200如果能返回那段经典的 JSON 问候语说明网络是通的问题百分百在跨域配置上。3.2elasticsearch.yml里那几个参数到底在写什么需要在集群的所有节点上修改配置文件然后重启节点生效。下面是完整的一组配置我逐行解释它们各自在管什么。# 打开跨域支持的总开关不开的话下面几行全是摆设 http.cors.enabled: true # 允许的源。生产环境千万不要用 *后面会讲替代方案 http.cors.allow-origin: * # 允许的请求方法head 删除索引会用到 DELETE不写全的话某些按钮会失效 http.cors.allow-methods: OPTIONS, HEAD, GET, POST, PUT, DELETE # 允许的请求头Content-Type 和 Authorization 是必须的 http.cors.allow-headers: X-Requested-With, Content-Type, Content-Length, Authorization # 如果前面用了 *这一项不是必须的如果要带认证信息才需要打开 http.cors.allow-credentials: true这里面最容易出问题的是allow-origin。写*表示接受任何来源的跨域请求功能上最省事但安全上等于把集群的 HTTP 接口向全世界所有的网页开放了——任何一个人做一个恶意页面只要诱导你的运维点开那个页面就能以你浏览器的身份去操作你的集群。测试环境无所谓生产环境这么写是实实在在的风险。替代做法是把它写成正则只放行 head 所在的地址。需要注意 Elasticsearch 对这个字段的解析规则当值以/开头和结尾时会被当作正则表达式处理。所以正确的写法长这样http.cors.allow-origin: /https?:\/\/192\.168\.1\.100(:[0-9])?/这句正则的意思是协议是 http 或 https 都行主机固定为192.168.1.100端口任意。这样即使你把 head 的端口从 9100 换到别的也不用再动集群配置。顺带说一句这个配置项是集群级别的改完需要重启节点滚动重启的时候注意别把所有节点同时停掉。3.3 用 Nginx 反代顺手把认证和只读一起做了如果你的集群已经开了安全认证或者你想给 head 加一层访问控制最省心的方案是让 Nginx 站在中间做反向代理。这样做有三个好处把跨域问题变成同源问题彻底绕开、可以顺手加上账号密码验证、可以按请求方法做只读限制。思路是让浏览器访问http://head.example.com/拿到 head 页面同时浏览器请求http://head.example.com/es/时Nginx 把这个前缀去掉转发到真正的 9200。只要页面和接口在同一个域名同一个端口下同源策略就不存在了。server { listen 80; server_name head.example.com; # head 的静态页面 location / { root /opt/elasticsearch-head/_site; index index.html; } # 把 /es/ 开头的请求转发给真实的集群 location /es/ { # 只允许读取类请求禁止一切写操作防止误删索引 limit_except GET HEAD OPTIONS { deny all; } # 加一层基础认证auth_basic_user_file 用 htpasswd 生成 auth_basic restricted; auth_basic_user_file /etc/nginx/head.htpasswd; # 去掉路径前缀后转发 rewrite ^/es/(.*)$ /$1 break; proxy_pass http://内网集群地址:9200; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这套配置里我特别想强调limit_except那一段。head 的界面上一堆红色按钮删除索引就在两次点击之内手一抖就是生产事故。把它设成只读之后所有删除、关闭、清空的操作都会被 Nginx 用 403 挡掉而浏览、查询、看分片分布完全不受影响。对绝大多数只需要看看集群状态的日常场景来说只读 head 才是正确的形态。真要执行写操作的时候再单独开一个临时通道用完就关。提示改成 Nginx 反代之后head 连接框里要填的地址就不再是 9200 了而是http://head.example.com/es。这个地址变化经常被忽略明明代理配好了却一直连不上回头一查是地址没改。4. 日常运维实录用 head 定位五类高频故障配置搞定之后剩下的就是怎么把它用出价值。我把自己这几年通过 head 快速定位问题的场景整理了一下最高频的其实就是下面这五种。掌握这几个套路之后大部分集群异常你都能在五分钟内给出方向性判断。4.1 绿黄红三色到底在告诉你什么概览面板左上角那个圆点颜色是所有人打开 head 第一眼看到的东西。它的取值只有三个绿、黄、红。绿色表示所有主分片和副本分片都已经正常分配到某个节点上集群完全健康。黄色表示所有主分片都已经分配但至少有一个副本分片没有分配出去。红色表示至少有一个主分片未分配意味着有数据暂时不可读这可能造成数据丢失如果该主分片的数据无法恢复。新手最容易慌的就是黄色。我见过太多人在单节点测试集群上看到黄色第一时间冲到群里问是不是出问题了。答案通常是没问题这是设计使然。Elasticsearch 有一条硬规则同一个分片的主分片和它的副本分片不能落在同一个节点上。你只有一个节点那副本自然无处可去只能处于未分配状态健康度就是黄色。这种情况要么加节点要么把索引的副本数改成 0两个办法都能变绿只是前者保证高可用后者牺牲冗余。红色的处理就要认真得多。第一步是在 head 的索引面板里找到具体是哪个索引红了第二步看它的分片分布第三步去查未分配的原因。常见原因有三类节点掉线导致分片丢失、磁盘水位超过阈值触发分片保护、以及分片分配规则配置不当导致找不到符合条件的节点。head 能帮你精确定位到哪个索引的哪个分片有问题但具体的分配失败原因还得看集群的分配解释接口。4.2 索引、映射、别名现场动作与容量估算索引面板是我在 head 里停留时间第二长的地方。它把每个索引的分片数、副本数、文档总量、占用空间、健康状态排成一个列表非常直观。做容量规划的时候我习惯先在这里扫一眼找出那些文档数不多但占用空间大得离谱的索引这类索引通常意味着字段映射设计有问题比如本该是数值或者日期的字段被写成了长文本或者开了不必要的分词。新建索引的时候head 会让你填分片数和副本数。这两个数字怎么定很多人是拍脑袋的。我给一个自己常用的估算逻辑。单个分片的大小控制在 10GB 到 50GB 之间是比较舒服的区间太小会导致分片数量爆炸集群元数据压力大、每次查询都要打到几十个分片上太大则单个分片恢复慢一次故障要等很久才能恢复完。假设你预估这个索引一年会产生 500GB 数据那按每分片 25GB 算大概需要 20 个分片。副本数在测试环境填 0生产环境至少填 1。还有一个必须知道的事实分片数在索引创建之后就固定了不能直接修改。所以建索引这一步是整个流程里最不能马虎的。如果事后发现分片数不合理只能通过重新索引把数据搬到新索引再用别名切换流量的方式解决。正因为如此我强烈建议所有业务索引都通过别名对外提供服务写数据写别名读数据也读别名。这样将来要调整分片、要迁移、要重建只需要切换别名的指向业务代码一行都不用改。head 的索引面板里可以看到每个索引挂了哪些别名切换前后在这里确认一下比在代码里猜安全得多。4.3 数据浏览与复合查询把它当轻量 REST 客户端用数据浏览面板我会用它来做一件特定的事确认某个字段到底被写成了什么。业务同学跑来说我搜不到这条数据你打开数据浏览找到那条文档看一眼对应的字段值经常一眼就能看出问题——比如本该是2024-05-01的日期字段写成了一串乱七八糟的字符串或者本该是纯数字的 ID 带了空格。这种问题在日志里排查半天在 head 里三秒定位。复合查询面板的用法就更多了。我列几个自己常用的// 查看某个索引的字段映射确认字段类型是否符合预期 GET /orders/_mapping // 统计某个索引的文档总数比看概览里的数字更精确 GET /orders/_count // 查看分片分配的具体情况定位黄红问题的关键 GET /_cat/shards/my_index?v // 查看某个索引的别名挂载情况 GET /orders/_alias这些请求在代理模式下会被 Nginx 拦住写操作但读取类请求全部放行用起来没有任何心理负担。另外复合查询面板会记录历史我把上面这几条常年放在历史里遇到问题点一下就行不用每次重新敲。5. 踩坑速查那些官方文档不会写的细节这一节是我觉得整篇文章里最值钱的部分。下面这些坑每一个都是我或者身边的同事真真切切踩过、并且花了时间才搞明白的。如果你正准备上手 head建议先扫一遍能省下不少试错成本。5.1 页面能开但操作报错八成是这三类原因第一类是写请求被前置代理拦了。如果你按前面说的配了只读 Nginx那所有创建索引、删除索引、提交数据的按钮点下去都会提示失败这是预期行为不是 bug。判断方法很简单看返回码是不是 403 或 405是的话就是代理层拦的。第二类是请求体格式不被识别。这个在 Elasticsearch 6.x 之后尤其常见。表现是页面能打开、能查看数据但一旦你通过 head 的界面去创建索引或者修改映射就会报一个关于Content-Type的错误大意是服务端不接受application/x-www-form-urlencoded这种内容类型。原因是 head 在提交请求时使用的默认内容类型比较老派而新版本的 Elasticsearch 只认application/json。解决办法有两个稳妥一点的直接用复合查询面板手写带 JSON body 的请求绕过去彻底一点的去改 head 的源码。改源码的具体位置在项目源码里搜索application/x-www-form-urlencoded这个字符串你会在一两个文件里找到它把它替换成application/json;charsetUTF-8然后重新构建或者重启开发服务。如果用的是 Docker 部署的构建产物你需要改的是打包后_site目录里那个经过压缩的 JS 文件搜到对应片段替换掉即可。改之前记得备份原文件。第三类是认证信息没带上。如果你的集群开了安全认证head 的连接框需要你写成带用户名密码的形式http://用户名:密码集群地址:9200。注意密码里如果包含、:、/这类特殊字符必须先做 URL 编码否则地址会被解析成另一副样子报出来的错误会很难懂。5.2 大索引打开卡死与深分页的边界数据浏览面板在打开一个几千万文档的大索引时偶尔会卡住甚至把标签页搞崩。原因在于它的实现方式——渲染表格的时候会一次性处理返回的这批文档字段又多又长的时候浏览器渲染压力很大。我的做法是永远不要在不设条件的情况下直接打开一个大索引先在基本查询面板里加一个筛选条件把范围缩小到几十条再去看明细。另一个相关的边界是深分页。Elasticsearch 有一个index.max_result_window参数默认值是 10000。意思是单次查询里from size不能超过这个数你最多只能翻到第 10000 条。head 的翻页功能同样受这个限制翻到一定页数之后会报错。这不是 head 的问题是 Elasticsearch 为了保护内存做的限制。如果你确实需要导出大量数据正确的做法是走游标查询或者导出接口而不是在 head 里一页页翻。5.3 时间少 8 小时、中文乱码这些假 bug有一类问题特别消耗沟通成本因为它们看起来像 bug实际上完全正常。时间显示比北京时间少 8 小时。Elasticsearch 内部统一用 UTC 存储时间head 只是把_source里的原始值原样展示出来它不做任何时区转换。所以你看到的时间戳比实际时间早 8 小时是正确行为。真正需要担心的是另一种情况如果这个字段在业务查询里参与时间范围过滤而写入时又没做转换那才会导致查不到数据。判断方法是对比一下业务代码里写入时的处理逻辑别只看界面。中文字段显示成乱码或者问号。这个通常出现在通过 head 手工创建索引、在映射里填写了中文注释的场景。根源还是前面提到的请求内容类型问题编码声明不对服务端按错误的字符集解析了请求体。用复合查询面板手写 JSON 提交基本能规避掉。另外浏览器端如果查看的是_source里的中文一般是正常的如果连这里都乱码那就要往数据写入环节去查了。5.4 安全红线9100 千万不要直接暴露在公网上这条我放在最后说但优先级最高。head 本身没有任何身份认证机制它的能力边界完全取决于它连接的那个集群。如果你把 9100 端口直接暴露在公网上且它连接的集群没有开安全认证那么任何知道你地址的人都可以在浏览器里点几下就把你的索引删掉。这不是危言耸听是真实发生过的事故。正确的做法有三层可以叠加。第一层head 只部署在内网或者跳板机上通过内网访问公网一律不放行 9100 端口。第二层在 head 前面的 Nginx 上加 Basic Auth至少挡住无差别的扫描。第三层把代理配置成只读即使拿到了访问权限也删不掉东西。这三层全加上成本很低但把风险压到了几乎为零。注意如果你的集群本身就没开安全认证那么任何能访问 9200 端口的人都可以直接操作集群head 只是把这个过程变得更方便了而已。所以在关心 head 安全之前先确认 9200 端口的暴露面是否可控这才是根本。6. 和 Kibana、Cerebro 摆在一起head 该占什么位置工具选型这件事脱离场景谈优劣没有意义。head 被吐槽最多的几点是界面老旧、多年没更新、不支持集群认证、看不到监控图表。这些批评都成立。但如果因此说它过时了、该淘汰了我认为也不客观。它解决的问题和 Kibana 解决的问题其实并不完全重叠。6.1 三者的能力对比维度es headKibanaCerebro部署复杂度极低一个容器或者一堆静态文件中等版本必须与集群严格对应中等需要 Java 运行时资源占用几乎可以忽略相对较重较轻核心定位集群状态速览 手搓请求数据分析、可视化、日志检索多集群管理、分片与别名运维认证支持无需依赖前置代理完整完整分片管理只能看不能操作有限强可手动分配和迁移分片多集群切换靠开多个标签页需配置多个空间原生支持集群列表更新活跃度基本停滞非常活跃中等学习成本几乎为零需要学习索引模式和查询语言中等从这张表能看出来head 赢在轻和快。你不需要为它准备服务器资源不需要对齐版本不需要配置索引模式打开就能用。Kibana 赢在深尤其是它的开发者工具控制台自动补全、语法高亮、历史记录管理都做得比 head 好很多长期做查询开发的团队基本离不开它。Cerebro 赢在专它的分片管理和多集群能力是另外两个都比不上的。6.2 我的实际选型建议我自己的习惯是三个都留着分工明确。日常巡检、临时看一眼集群状态、验证一条小查询的返回结构用 head因为它开得最快一个浏览器标签页的事不用等 Kibana 加载。长期的数据分析、日志检索、做仪表盘、写复杂的聚合查询用 Kibana这些场景 head 根本做不了。需要做分片层面的运维动作比如某个索引的分片分布严重不均需要手动迁移或者同时管理好几个集群需要统一视图用 Cerebro。如果你的团队规模小、资源紧张只能选一个我的建议是如果核心诉求是看得见集群在发生什么选 Kibana它是唯一一个能跟得上版本演进的选项如果核心诉求是快速看一下状态、手搓几条请求head 就够了一个 30MB 的容器能解决的事没必要上更重的东西。另外提一句head 还有一个浏览器扩展形态装完之后它把界面资源放在本地你只需要在扩展里填集群地址就能用省掉了部署静态文件这一步。这个形态特别适合个人开发者在自己电脑上临时排查问题不用为了一次检查去开容器。不过它的功能和独立部署版完全一样该有的跨域配置一个都不能少。我在实际使用中最大的体会是head 这类工具的价值从来不是它功能有多强而是它把想到和看到之间的距离压缩到了最短。以前排查一个问题要经历想清楚要查什么、写一条 curl、检查返回、再想下一步这样一个来回现在变成看到概览里那个索引是黄的、点进索引面板、看一眼分片分布、换一个请求继续这样一个连续的动作流。工具好不好用最终衡量的就是这条动作流够不够顺。head 在这个指标上到今天为止依然没被完全替代。