配置和使用同义词

在搜索场景中,用户使用不同词语表达同一概念(例如“手机”与“智能手机”),可能导致搜索结果不全。同义词功能将这些词语视为等同,以扩展搜索范围,提升搜索召回率和用户体验。

操作前检查清单

在执行同义词文件变更前,务必确认以下事项:

  • 确认关键索引至少拥有一个副本分片,以保障服务在单节点重启期间的可用性。(删除词典时将引起集群重启)

  • 监控并确认集群负载处于健康水平(建议:CPU使用率 < 60%,堆内存使用率 < 50%)。

    连接集群,通过GET /_nodes/stats/jvm?filter_path=nodes.*.jvm.mem.heap_*查看所有节点的CPU和堆内存使用率。

工作原理与决策指南

理解同义词的两种配置方式及其利弊,有助于做出正确的技术选型。

同义词语法规则

同义词文件必须为 UTF-8 编码的 .txt 文件。文件中的每一行定义一组同义词规则,支持以下两种格式:

  • 等价同义词(Solr格式)
    用英文逗号分隔的词语将被视为完全等价。搜索其中任意一个词,都会匹配包含这组词中任何一个的文档。

    # 示例:搜索“手机”、“智能手机”或“移动电话”时,效果相同。
    手机,智能手机,移动电话
    ipod,i-pod,i pod
  • 定向映射(WordNet格式)
    使用 => 将一组词语映射到一个标准词,通常用于归一化,即将不规范的用词指向规范用词。

    # 示例:将"usa"和"us"都映射到“美国”。
    usa,us => 美国

两种配置方式对比与决策

支持通过上传文件或在索引配置中内联定义同义词两种方式,两种方式的对比如下表所示。

对比项

方式一:上传文件

方式二:内联定义

操作方式

上传TXT文件到ES集群,然后在索引settings中通过synonyms_path引用。

直接在索引settingssynonym过滤器中写入同义词规则。

优点

  • 易于管理和复用大型词典。

  • 词典与索引配置解耦,多索引可共享。

  • 更新配置可即时生效。

  • 适合小型、不常变化的同义词集。

缺点

已有索引无法动态加载新词典。

  • 难以复用,每个索引需单独定义。

  • 不适合管理大型或复杂的词典。

适用场景

词典内容稳定、不常变更,且需要在多个索引间共享的场景。

对可用性要求极高,或需要频繁、快速更新同义词的场景。

操作步骤

方式一:通过上传文件配置(可复用)

此方式适合词典相对固定的场景。

步骤一:上传同义词文件

以下示例通过filter过滤器配置同义词,使用测试文件aliyun_synonyms.txt,其内容为:begin, start。

  1. 登录阿里云Elasticsearch控制台,选择目标地域和资源组,然后单击目标实例ID。

  2. 在左侧导航栏,选择配置与管理 > ES集群配置,在基础配置区域找到同义词配置,单击上传

  3. 在弹出的面板中,单击配置,然后选择上传方式:

    文件后缀必须是.txt,文件名包含大小写字母、数字和下划线,且长度不超过30个字符
    • 上传文件:从本地选择同义词TXT文件。

    • 添加OSS文件:输入Bucket名称和同义词文件名称后,单击添加。

      限制:OSS Bucket必须与ES实例在同一地域。
  4. 单击保存,确认后执行。

步骤二:创建索引并引用同义词文件

等待实例状态恢复为正常,连接集群后创建索引,并配置其使用已上传的同义词文件。

PUT /aliyun-index-test
{
  "settings": {
    "index":{
      "analysis": {
          "analyzer": {
            "by_smart": {
              "type": "custom",
              "tokenizer": "ik_smart",
              "filter": ["by_tfr","by_sfr"],
              "char_filter": ["by_cfr"]
            },
            "by_max_word": {
              "type": "custom",
              "tokenizer": "ik_max_word",
              "filter": ["by_tfr","by_sfr"],
              "char_filter": ["by_cfr"]
            }
         },
         "filter": {
            "by_tfr": {
              "type": "stop",
              "stopwords": [" "]
              },
           "by_sfr": {
              "type": "synonym",
              "synonyms_path": "analysis/aliyun_synonyms.txt"
              }
          },
          "char_filter": {
            "by_cfr": {
              "type": "mapping",
              "mappings": ["| => |"]
            }
          }
      }
    }
  }
}
不同版本的集群,其索引创建语法存在一定差异,请参见ES常见版本操作索引示例

步骤三:配置同义词字段title

  • ES 7.0以下版本

    PUT /aliyun-index-test/_mapping/doc
    {
    "properties": {
     "title": {
       "type": "text",
       "analyzer": "by_max_word",
       "search_analyzer": "by_smart"
     }
    }
    }
  • ES 7.0及以上版本

    PUT /aliyun-index-test/_mapping/
    {
    "properties": {
     "title": {
       "type": "text",
       "analyzer": "by_max_word",
       "search_analyzer": "by_smart"
     }
    }
    }

步骤四:验证同义词效果

使用_analyze API验证分词器是否已正确加载同义词,假设同义词文件包含 begin,start

GET /aliyun-index-test/_analyze
{
"analyzer": "by_smart",
"text":"begin"
}

成功的返回结果应同时包含 begin 和 start 两个词元(token)。

{
  "tokens" : [
    {
      "token" : "begin",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "start",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "SYNONYM",
      "position" : 0
    }
  ]
}

步骤五:测试搜索效果

  1. 写入两份包含同义词的文档

    PUT /aliyun-index-test/doc/1
    {
    "title": "Shall I begin?"
    }
    PUT /aliyun-index-test/doc/2
    {
    "title": "I start work at nine."
    }
  2. 搜索其中一个词(例如 begin),可同时召回包含 begin 和 start 的文档。

    GET /aliyun-index-test/_search
    {
     "query" : { "match" : { "title" : "begin" }},
     "highlight" : {
         "pre_tags" : ["<red>", "<bule>"],
         "post_tags" : ["</red>", "</bule>"],
         "fields" : {
             "title" : {}
         }
     }
    }

    返回结果:

    {
      "took" : 70,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 2,
          "relation" : "eq"
        },
        "max_score" : 0.28247005,
        "hits" : [
          {
            "_index" : "aliyun-index-test",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 0.28247005,
            "_source" : {
              "title" : "Shall I begin?"
            },
            "highlight" : {
              "title" : [
                "Shall I <red>begin</red>?"
              ]
            }
          },
          {
            "_index" : "aliyun-index-test",
            "_type" : "_doc",
            "_id" : "2",
            "_score" : 0.25069216,
            "_source" : {
              "title" : "I start work at nine."
            },
            "highlight" : {
              "title" : [
                "I <red>start</red> work at nine."
              ]
            }
          }
        ]
      }
    }
    

方式二:在索引设置中内联配置(不便复用)

此方式将同义词规则直接写入索引配置,适合词典小且需要快速迭代的场景。

步骤一:创建索引并定义同义词

连接集群,在创建索引时直接将同义词规则定义在synonyms数组中。

PUT /my_index
{
 "settings": {
     "analysis": {
         "analyzer": {
             "my_synonyms": {
                 "filter": [
                     "lowercase",
                     "my_synonym_filter"
                 ],
                 "tokenizer": "ik_smart"
             }
         },
         "filter": {
             "my_synonym_filter": {
                 "synonyms": [
                     "begin,start"
                 ],
                 "type": "synonym"
             }
         }
     }
 }
}

上述命令创建了一个名为 my_index 的索引,并配置了自定义文本分析,处理流程为:
当文本字段使用 my_synonyms 分析器时,ik_smart 分词器将输入文本切分成词元;lowercase 过滤器将所有词元转换为小写;my_synonym_filter 过滤器将小写后的词元(如 begin 或 start)映射到其同义词组(即两者都被视为同一个规范词)。

步骤二:配置同义词字段title

  • ES 7.0以下版本

    PUT /my_index/_mapping/doc
    {
    "properties": {
     "title": {
       "type": "text",
       "analyzer": "my_synonyms"
     }
    }
    }
  • ES 7.0及以上版本

    PUT /my_index/_mapping/
    {
    "properties": {
     "title": {
       "type": "text",
       "analyzer": "my_synonyms"
     }
    }
    }

步骤三:验证同义词效果

GET /my_index/_analyze
{
 "analyzer":"my_synonyms",
 "text":"Shall I begin?"
}

返回结果:

{
  "tokens" : [
    {
      "token" : "shall",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "i",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "ENGLISH",
      "position" : 1
    },
    {
      "token" : "begin",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "ENGLISH",
      "position" : 2
    },
    {
      "token" : "start",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "SYNONYM",
      "position" : 2
    }
  ]
}

删除同义词将触发集群重启

通过控制台或API删除同义词文件,将触发整个集群的滚动重启,期间集群性能可能出现短暂波动。

  • 服务抖动:滚动重启意味着集群节点将逐一重启,即使索引配置了副本,也可能导致查询延迟短暂增加或服务抖动。

  • 服务中断风险:在集群高负载或索引无副本的极端情况下,集群重启可能导致部分请求失败或服务短暂中断。

  • 生效耗时:重启和词典下发的总耗时与集群规模、数据量及负载有关,可能持续数分钟甚至更长时间。