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

在搜索引擎的实际应用中,用户输入的关键词往往与数据库中存储的文本存在差异。例如:

  • 用户搜 “手机”,但商品名称写的是 “智能电话”“iPhone”
  • 用户搜 “士多啤梨”,但库存里叫 “草莓”
  • 用户搜 “AWS”,但文章里写的是 “亚马逊云科技”

如果没有同义词处理,这些相关的文档将无法被检索到(召回率低)。本文将详细介绍如何在 INFINI Easysearch 中配置和优化同义词功能,弥合用户意图与数据之间的鸿沟。

一、 同义词的两种工作模式 #

在 Easysearch 中,同义词处理本质上是一个 Token Filter(词元过滤器),它可以工作在两个阶段:

1. 索引阶段(Index-time) #

在数据写入时,直接将同义词写入倒排索引。

  • 优点:查询性能略高(因为不需要实时计算)。
  • 缺点灵活性极差。一旦规则修改(例如新增“西红柿 => 番茄”),必须重建索引(Reindex)才能生效。
  • 结论不推荐,除非是极度静态的同义词规则。

2. 查询阶段(Query-time)—— 推荐 #

在搜索时,动态地将用户的查询词扩展为同义词。

  • 优点灵活。修改同义词规则后,只需刷新配置即可立即生效,无需重建索引。
  • 缺点:查询时增加了少许计算开销(但在 Easysearch 的优化下通常可忽略)。
  • 结论生产环境的最佳实践

二、 核心组件:synonym_graph #

在早期的 Elasticsearch 版本中,我们使用 synonym 过滤器。但在处理多词同义词(Multi-word synonyms,如 “domain driven design” <=> “DDD”)时,旧版过滤器容易破坏短语查询的位置信息。

在 Easysearch 中进行查询阶段处理时,请务必使用 synonym_graph 过滤器。它能正确处理跨多个 Token 的同义词图谱。


三、 实战:配置同义词搜索 #

我们将构建一个电商搜索索引,实现“查询时同义词扩展”。

3.1 准备同义词规则文件 #

建议将同义词规则独立存储在文件中,便于维护。
在 Easysearch 配置目录的 config/analysis 文件夹下(如果没有则创建),新建文件 synonyms.txt

# Solr 格式
# 1. 等价扩展(搜索任意一个词,都能搜到其他的)
番茄, 西红柿
手机, 移动电话, 智能手机
kfc, 肯德基

# 2. 单向映射(搜左边的,扩展成右边的;搜右边的,不包含左边的)
# 场景:搜 "水果" 时,希望展示 "苹果",但搜 "苹果" 不需要展示所有 "水果"
iphone, ipad, mac => apple

3.2 定义分析器与索引 #

我们需要定义一个包含 synonym_graph 的自定义分析器,并将其指定为字段的 search_analyzer

PUT /shop_index
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonym_graph": {
          "type": "synonym_graph",
          "synonyms_path": "config/synonyms.txt",
          "updateable": true
        }
      },
      "analyzer": {
        "my_synonym_search_analyzer": {
          "tokenizer": "ik_smart",
          "filter": [
            "my_synonym_graph"
          ]
        },
        "my_index_analyzer": {
          "tokenizer": "ik_max_word"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "my_index_analyzer",
        "search_analyzer": "my_synonym_search_analyzer"
      }
    }
  }
}

3.3 写入测试数据 #

POST /shop_index/_doc/1
{ "product_name": "新鲜的西红柿" }

POST /shop_index/_doc/2
{ "product_name": "新款智能手机" }

3.4 搜索验证 #

# 用户搜索 "番茄"
GET /shop_index/_search
{
  "query": {
    "match": {
      "product_name": "番茄"
    }
  }
}

结果解析

  1. 用户输入 “番茄”。
  2. search_analyzer 处理:ik_smart 将其分为 “番茄”。
  3. synonym_graph 命中规则 “番茄, 西红柿”。
  4. 查询被重写为 (product_name:番茄 OR product_name:西红柿)
  5. 文档 1(包含“西红柿”)被成功召回。

五、 常见坑与最佳实践 #

5.1 分词器的配合 #

同义词过滤器通常放在 Tokenizer 之后。

  • 英文tokenizer: standard -> filter: lowercase -> filter: synonym。注意大小写,如果在同义词规则里写了小写,一定要先做 lowercase。
  • 中文tokenizer: ik_smart -> filter: synonym。确保同义词文件里的词,能被 IK 分词器切分出来。例如,如果 IK 把 “奥迪A6” 切分成 “奥迪” 和 “A6”,而同义词规则写的是 “奥迪A6, 豪车”,则可能无法匹配。建议同义词规则使用简单的词汇。

5.2 避免同义词风暴 #

不要滥用同义词将通用词映射到大量具体词(例如 a,b,c,d,e,f...)。这会导致 Query 被扩展得非常长,严重拖慢搜索速度。

5.3 排序与高亮 #

当使用同义词扩展查询时,搜索结果中可能会出现不包含用户原始关键词的文档(搜“番茄”出了“西红柿”)。

  • 高亮(Highlighting):Easysearch 的高亮功能通常能自动识别同义词并进行高亮。
  • 评分(Scoring):如果希望原始词(番茄)的权重高于同义词(西红柿),可以使用 Boost 或使用 simple_query_string 等支持权重的查询方式,或者在同义词规则中使用单向映射控制。

总结 #

在 INFINI Easysearch 中实现同义词搜索,关键在于选择 查询时(Query-time) 策略,并结合 synonym_graph 过滤器。