📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

适用版本: Elasticsearch 5.x、6.x、7.x、8.x(全版本均适用) 影响版本说明: 该错误自 more_like_this 查询引入以来即存在,各版本报错信息一致,仅在异常类名上有细微差异(7.x 前后由 ParsingException 统一处理)。

1. 错误异常的基本描述 #

more_like_this requires 'like' to be specified 是 Elasticsearch 在执行 More Like This(相似文档查询) 时抛出的参数校验异常。该错误表示:在解析 more_like_this 查询 DSL 时,Elasticsearch 发现请求中缺少必填的 like 参数,因此拒绝执行查询。

more_like_this 查询的核心作用是根据一段文本或已有文档,查找索引中与之内容相似的文档。它依赖于 like 参数来指定"相似于什么",缺少该参数时查询语义不完整,因此 Elasticsearch 在查询解析阶段就直接报错。

常见现象 #

  • 搜索接口返回 HTTP 400(Bad Request),响应体中包含 parse_exceptionparsing_exception 错误类型。
  • Elasticsearch 服务端日志中出现 more_like_this requires 'like' to be specified 异常信息,并附带解析位置(行号、列号)。
  • 应用侧表现为搜索功能失效,前端或调用方收到 400 错误,相关搜索请求全部失败。
  • 如果是通过脚本或批量方式构造查询,可能只有部分请求失败,表现为间歇性错误。

典型报错与异常栈 #

以下是真实可复现的完整报错示例:

HTTP 响应示例(Elasticsearch 7.x/8.x):

{
  "error": {
    "root_cause": [
      {
        "type": "parsing_exception",
        "reason": "more_like_this requires 'like' to be specified",
        "line": 3,
        "col": 17
      }
    ],
    "type": "parsing_exception",
    "reason": "more_like_this requires 'like' to be specified",
    "line": 3,
    "col": 17
  },
  "status": 400
}

Elasticsearch 服务端日志(摘自 elasticsearch.log):

[2024-01-15T10:23:45,123][DEBUG][o.e.a.s.SearchTransportService] [node-1] failed to execute query
org.elasticsearch.index.query.QueryShardException: failed to parse query [more_like_this]
        at org.elasticsearch.index.query.MoreLikeThisQueryBuilder.doToQuery(MoreLikeThisQueryBuilder.java:312)
        at org.elasticsearch.index.query.AbstractQueryBuilder.toQuery(AbstractQueryBuilder.java:86)
        ...
Caused by: org.elasticsearch.common.ParsingException: more_like_this requires 'like' to be specified
        at org.elasticsearch.index.query.MoreLikeThisQueryBuilder$Parser.parse(MoreLikeThisQueryBuilder.java:589)
        at org.elasticsearch.index.query.QueryParseContext.parseInnerQueryBuilder(QueryParseContext.java:315)
        ...

Elasticsearch 5.x/6.x 版本异常栈(类名略有差异):

org.elasticsearch.index.query.QueryParsingException: [more_like_this] requires 'like' to be specified
        at org.elasticsearch.index.query.MoreLikeThisQueryBuilder.doToQuery(MoreLikeThisQueryBuilder.java:278)
        ...

2. 为什么会发生这个错误 #

more_like_this 查询的语义是"找到与给定内容相似的文档",因此 like 参数是该查询的核心输入——没有它,Elasticsearch 无从得知要与什么内容做相似度比较。

从源码层面看,在 MoreLikeThisQueryBuilder.java 的解析逻辑中有明确的校验:

if (likeTexts.isEmpty() && likeItems.isEmpty()) {
    throw new ParsingException(parser.getTokenLocation(), "more_like_this requires 'like' to be specified");
}

常见触发原因包括:

2.1 DSL 中遗漏了 like 参数(最常见) #

开发者在构造 more_like_this 查询时,只配置了 fieldsmin_term_freq 等可选参数,但忘记了添加必填的 like 参数。

错误示例:

{
  "query": {
    "more_like_this": {
      "fields": ["title", "content"],
      "min_term_freq": 1,
      "max_query_terms": 12
    }
  }
}

2.2 动态拼接 DSL 时 like 值为空 #

在应用代码中通过字符串拼接或模板引擎动态生成 DSL 时,like 对应的变量为空字符串、null 或空数组,最终生成的 JSON 中 like 不存在或值为空。

错误示例(生成的无效 DSL):

{
  "query": {
    "more_like_this": {
      "fields": ["title"],
      "like": "",
      "min_term_freq": 1
    }
  }
}

2.3 like 参数位置写错或嵌套层级错误 #

like 参数必须位于 more_like_this 对象的直接子级。如果错误地将其嵌套在其他对象中,Elasticsearch 解析时同样会认为 like 未指定。

错误示例(嵌套层级错误):

{
  "query": {
    "more_like_this": {
      "fields": ["title"],
      "params": {
        "like": "Elasticsearch 查询优化"
      }
    }
  }
}

2.4 从旧版本迁移或手动编写 DSL 时格式错误 #

more_like_this 在 Elasticsearch 5.x 前后有过语法变化。旧版本的 mlt API(REST 端点 /_mlt)在 5.x 后被废弃,迁移到 Query DSL 时容易遗漏 like 参数。

2.5 客户端 SDK 使用不当 #

部分 Elasticsearch 客户端库的 MoreLikeThisQueryBuilder 在未设置 like 时就调用 build()toQuery(),不会在客户端提前报错,而是把不完整的查询发到服务端才触发异常。

3. 如何排查和解决这个异常 #

3.1 排查步骤 #

步骤一:获取完整报错信息和请求体 #

首先确认报错发生时的完整请求。使用以下方式获取:

# 查看 Elasticsearch 服务端日志
grep -r "more_like_this requires" /var/log/elasticsearch/ | tail -50

# 如果使用 INFINI Gateway,可直接在 Gateway 日志中查看完整请求和响应
# 或通过 INFINI Console 的请求日志功能查看

步骤二:复现并验证 DSL #

将触发报错的 DSL 完整取出,使用 curl 直接发给 Elasticsearch 进行复现:

curl -X POST "localhost:9200/your_index/_search?pretty" \
  -H "Content-Type: application/json" \
  -d '{
  "query": {
    "more_like_this": {
      "fields": ["title", "content"],
      "min_term_freq": 1
    }
  }
}'

如果返回 400 且错误信息包含 more_like_this requires 'like' to be specified,则确认为 like 参数缺失问题。

步骤三:检查 DSL 结构 #

使用以下 API 验证索引的 mapping,确认 fields 中引用的字段存在且为 text 类型(more_like_this 只支持 textkeyword 类型字段):

# 查看索引 mapping
curl -X GET "localhost:9200/your_index/_mapping?pretty"

# 重点确认 fields 中列出的字段类型
# more_like_this 的 fields 必须是 text 类型字段,否则即使 like 参数正确也会报错

步骤四:检查应用代码中的 DSL 构造逻辑 #

如果是通过代码动态生成 DSL,在代码中添加日志打印最终的查询 JSON:

// Java 示例:打印最终生成的查询 DSL
String queryJson = moreLikeThisQueryBuilder.toString();
logger.info("Generated MLT query: {}", queryJson);
// 检查 like 参数是否存在且非空
# Python 示例:打印最终生成的查询 DSL
import json
query_dict = {"query": {"more_like_this": mlt_params}}
print(json.dumps(query_dict, indent=2))
# 检查 like 是否存在于 more_like_this 对象中

3.2 修复方案与正确 DSL 示例 #

方案一:补充 like 参数(文本形式) #

这是最简单直接的修复方式,适用于根据用户输入的文本查找相似文档:

{
  "query": {
    "more_like_this": {
      "fields": ["title", "content"],
      "like": "Elasticsearch 是一个分布式搜索和分析引擎",
      "min_term_freq": 1,
      "max_query_terms": 12,
      "min_doc_freq": 1
    }
  }
}

方案二:使用文档 ID 作为 like 参数 #

适用于"找出与某篇文档相似的其他文档"的场景:

{
  "query": {
    "more_like_this": {
      "fields": ["title", "content"],
      "like": [
        {
          "_index": "your_index",
          "_id": "document_id_123"
        }
      ],
      "min_term_freq": 1,
      "max_query_terms": 12
    }
  }
}

方案三:like 指定多组文本或文档 #

like 参数支持数组形式,可同时传入多段文本或多个文档:

{
  "query": {
    "more_like_this": {
      "fields": ["title", "content"],
      "like": [
        "Elasticsearch 查询优化技巧",
        "分布式搜索引擎原理",
        {
          "_index": "your_index",
          "_id": "doc_1"
        }
      ],
      "min_term_freq": 1,
      "max_query_terms": 20,
      "minimum_should_match": "30%"
    }
  }
}

方案四:在应用层添加参数校验 #

在发送查询前,在应用代码中校验 like 参数:

def build_mlt_query(fields, like_text, min_term_freq=1):
    if not like_text or not like_text.strip():
        raise ValueError("more_like_this 查询的 like 参数不能为空")
    if not fields:
        raise ValueError("more_like_this 查询的 fields 参数不能为空")
    return {
        "query": {
            "more_like_this": {
                "fields": fields,
                "like": like_text.strip(),
                "min_term_freq": min_term_freq
            }
        }
    }

3.3 排查注意事项 #

  • 注意 like 参数值的类型like 可以是字符串、字符串数组、对象数组(文档引用)。如果传了空字符串 "",部分版本可能不会报 requires 'like' to be specified,而是报其他错误,需仔细区分。
  • 注意 fieldslike 的匹配:如果 like 是文本,则 fields 指定了从哪些字段提取相似词项;如果 like 是文档引用(_id),则 fields 指定从文档的哪些字段计算相似度。
  • 注意 unlike 参数more_like_this 还支持 unlike 参数(指定不相似的文本/文档),它与 like 是独立的,不会导致此错误,但容易混淆。

4. 如何解决这个错误 #

4.1 常用修复思路 #

  1. 最小修复:在 more_like_this 查询对象中补充 like 参数,值为需要匹配的文本内容或文档引用。
  2. 添加应用层校验:在构造 DSL 之前校验 like 参数不为空,避免将不完整查询发到 Elasticsearch。
  3. 统一 DSL 构造方式:避免手写 JSON 字符串拼接,使用官方客户端库的查询构造器(如 Java 的 MoreLikeThisQueryBuilder),利用类型系统提前发现问题。
  4. 完善错误处理:捕获 parsing_exception 后,在应用日志中记录完整 DSL,便于后续排查类似问题。

4.2 后续注意事项与优化建议 #

  • 索引字段类型检查more_like_thisfields 必须是 text 类型字段。如果字段是 keyword 类型,需要在 mapping 中额外启用 fielddata: true 或修改查询设计。
  • 性能优化more_like_this 查询在大数据集上可能较慢,建议:
    • 限制 max_query_terms(默认 25,可适当降低)
    • 使用 minimum_should_match 控制匹配精度
    • like 文本较长时,考虑先做摘要再查询
  • 版本兼容性:Elasticsearch 5.x 之前有独立的 /_mlt API,5.x 之后统一使用 Query DSL 中的 more_like_this。如果系统从旧版本迁移,需统一查询语法。

4.3 借助 INFINI 产品提升排障效率 #

INFINI Console —— 集群健康与查询观测 #

INFINI Console 提供以下能力,帮助快速定位和解决 more_like_this 相关异常:

  • 实时请求日志:查看所有发往 Elasticsearch 的请求详情,包括完整的 DSL 和响应错误信息,无需登录服务器查看日志文件。
  • 慢查询分析more_like_this 查询如果未正确设置 max_query_termsmin_term_freq,可能导致慢查询。Console 的慢查询面板可帮助识别性能异常的 MLT 查询。
  • 索引 Mapping 可视化:快速查看索引的字段类型和 mapping 配置,确认 more_like_thisfields 引用的字段是否存在且类型正确。
  • 错误趋势分析:统计一段时间内 parsing_exception 的发生频率和分布,判断是偶发问题还是系统性问题。

INFINI Gateway —— 请求治理与流量防护 #

INFINI Gateway 部署在应用与 Elasticsearch 之间,提供以下能力:

  • 请求校验与拦截:在 Gateway 层配置规则,自动拦截 like 参数为空的 more_like_this 请求,在到达 Elasticsearch 之前返回友好错误,避免无效请求消耗集群资源。
  • 请求重写:通过 Gateway 的请求重写功能,为缺失 like 参数的查询自动补充默认值或返回预设响应,作为临时的兜底方案。
  • 查询缓存:对相同的 more_like_this 查询结果进行缓存,减少重复查询对集群的压力。
  • 限流保护:对 more_like_this 类查询设置单独的限流策略,防止大量复杂相似度查询拖垮集群。
  • 请求审计:记录所有 MLT 查询的完整请求和响应,便于事后审计和问题追溯。

Gateway 请求拦截规则示例(配置文件):

# 拦截 like 参数为空的 more_like_this 查询
rules:
  - name: reject-empty-mlt-like
    condition: "query.contains('more_like_this') && !query.contains('\"like\"')"
    actions:
      - type: reject
        message: "more_like_this 查询必须包含 like 参数"
        status_code: 400

5. 小结 #

more_like_this requires 'like' to be specified 是一个典型的参数缺失类解析异常,根因是 more_like_this 查询 DSL 中缺少必填的 like 参数。该问题虽然报错信息明确,但背后可能暴露出 DSL 构造逻辑不完善、应用层参数校验缺失或索引字段类型不匹配等更深层次的问题。

解决该问题的核心是:

  1. 确保 more_like_this 查询中包含合法、非空的 like 参数;
  2. 在应用层添加参数校验,避免不完整查询发出;
  3. 使用 INFINI Console 持续观测查询质量和错误趋势;
  4. 使用 INFINI Gateway 在流量入口层做请求治理和防护。

通过建立完整的请求观测、参数校验和流量治理机制,此类问题可以在研发阶段被发现,在上线前被拦截,在生产中被快速定位。

相关错误 #

附:日志上下文 #

下面保留当前页面中的源码或日志片段,便于继续结合异常调用栈定位问题:

// 源码位置:org.elasticsearch.index.query.MoreLikeThisQueryBuilder
if (likeTexts.isEmpty() && likeItems.isEmpty()) {
    throw new ParsingException(parser.getTokenLocation(), "more_like_this requires 'like' to be specified");
}
if (fields != null && fields.isEmpty()) {
    throw new ParsingException(parser.getTokenLocation(), "more_like_this requires 'fields' to be non-empty");
}

完整 more_like_this 查询参数说明:

参数必填说明
like指定相似度参照文本或文档,支持字符串、字符串数组、文档对象数组
fields指定从哪些字段提取相似词项,默认为所有 text 类型字段
min_term_freq最小词频,默认 2
max_query_terms最大查询词项数,默认 25
min_doc_freq最小文档频率,默认 5
minimum_should_match最小应该匹配数,默认 “30%”
unlike指定不相似的文本或文档,用于排除结果
analyzer指定分析器,默认使用字段的 search analyzer