Elasticsearch之自定义同义词开发实践

1.什么是同义词查询

ES(Elasticsearch)作为一个开源的、高扩展的分布式全文检索引擎,具有近实时的索引、搜索和分析等优点。用户在使用ES时,主要青睐其快速的查询性能。不同于传统的数据库,它具有非常灵活的查询方式,不但支持精确查询、模糊匹配、聚合查询等传统的数据查询方式,而且支持同义词查询。同义词查询,顾名思义就是根据相同意思的词进行查询,比如数据库里面存储的数据为“马铃薯”,我们不仅可以通过关键字“马铃薯”匹配到该数据,还可以通过关键字“土豆”得到我们想要的数据(“马铃薯”),该功能可以让查询更加的全面、方便和快捷,可以让用户体会到“随心所欲”般的查询快感,迫不及待的小伙伴赶快行动起来吧。

2.同义词查询解决了什么问题

同义词查询可以根据同义或近义词进行查询,它的使用主要针对以下两种应用场景:

  1. 获取一个词汇的同义词,比如搜索:京东,获取的结果:京东,淘宝,拼多多……。
  2. 获取一个词汇的同义词汇总,比如搜索:京东、淘宝或者拼多多,获取结果:电商平台。

对于场景一,如果不使用同义词查询,可能会有查询条件太苛刻,以及查询结果不全面等问题。例如某银行推出的有京东联名信用卡,在业务搜索关键字“京东”的时候,业务人员更想看到与京东联名卡相关的信息,所以仅仅返回京东相关的数据是不全面的,使用同义词搜索可以返回京东及京东联名卡的所有数据,这样可以更加全面得到我们想要的数据。

对于场景二,如果不使用同义词查询,可能需要存储更多的数据,浪费存储空间。例如某些项目仅仅需要电商平台的一些公共数据,而在搜索时,我们搜索不同的电商平台就需要存储不同电商平台的数据,造成存储浪费,使用同义词查询仅需要存储一份数据,搜索不同电商平台会得到同一个结果,可以更加准确、方便的得到我们需要的数据,使得查询条件更加宽泛。

ES集群在同义词配置时,传统模式是在analysis文件夹下面创建一个synonym文件,当用户创建索引时,指定同义词文档地址,搜索时会自动加载本地文件。传统模式为离线模式,需要在每个ES节点上面都配置一份一模一样的synonym文件,且每次进行更改或追加操作时,需要重启集群才能生效,这对于大数据平台来说是不符合实际的。为了实现同义词的热加载,我们开发了同义词服务器,只需把同义词文档synonym放在服务器上一份即可,每次更改或追加操作都会在自己设置的时间间隔之后生效,这样不但使集群使用者使用更加方便,而且对于集群维护人员也极大的减少了工作量,提高了集群的可用性。

3. ES同义词查询原理

ES同义词查询基于ES的分词原理,ES的分词原理可以查看之前的文章:让搜索引擎更懂你-Elasticsearch自定义分词开发实践,本文不再赘述,这里仅对ES同义词查询的原理进行介绍。

Elasticsearch之自定义同义词开发实践   

数据index到ES集群的过程依次为字符过滤器(Character filter)、分词器(Tokenizer)和Token过滤器(Token filter)。一个Analyzer可以包括多个Character filter、一个Tokenizer和多个Token filter,Character filter 用于分词前对原搜索的句子进行处理, Tokenizer 用于将搜索的句子分成多个词组,Token filter 用于处理Tokenizer输出的词组,比如删除某些词,修改某些词,增加某些词。

同义词查询的关键是自定义Token filter。该filter在收到Tokenizer发来的数据时,首先会获取synonym文件,比对Tokenizer处理后的词组,当出现同义词时,就按照synonym文件规则选定待搜索的词组,进行同义词搜索。ES在创建索引时进行预先设置同义词filter,并制定该filter为索引的Token filter,创建过程如下图所示:

Elasticsearch之自定义同义词开发实践

上面的例子是在名为synonym的index下面创建了名为ik_sync_smart的分析器,该分析器的tokenizer为ik_smart, filter为word_sync.,word_sync是自定义Token filter,该filter的type是synonym,synonyms_path是指定的同义词词典的路径。

4.自定义同义词服务开发

Synonym Server的开发需要满足以下两点:

  1. 该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,两者都是字符串类型,只要有一个发生变化,ES就会去抓取新的同义词。
  2. 该 http 请求返回的内容格式是一行一个同义词,换行符用\n 。

满足以上两个条件之后,可以把更新的同义词文档放到一个UTF-8编码的.txt文件中,当该文件中的内容发生变化时,Synonym Server 会在客户端请求该文件时自动返回相应的 Last-Modified 和 ETag,ES集群默认每隔60s请求一次server获取该文件,用户也可以在在创建索引时自己设定时间间隔,第一次请求该文件时,ES会把返回的Last-Modified 和 ETag放在本地,等到再次请求时,一旦发现Last-Modified 和 ETag中任一个发生变化,ES就会把synonym文件下载到集群中,如果Last-Modified 和 ETag都没有发生变化,便不会重新加载数据。

Synonym Server的开发中,Last-Modified使用了文件更改的最后时间,ETag记录了文件内容的hash值。

outputStream.write(data.getBytes("utf-8"));
response.setHeader("ETag", data.hashCode() + "");
response.setHeader("Last-Modified", date + "");
response.setHeader("Content-Length", file.length() + "");

与分词Server不同的是,Synonym Server还需要设置文件类型为text/plain。

response.setContentType("text/plain");

最后,文件以流的形式写入到Response中,写入到ES集群中。

String line;
StringBuffer sb = new StringBuffer();
while ((line = bufferedReader.readLine()) != null) {
    sb.append(line);
    sb.append("\n");
}
String data = sb.toString();

outputStream.flush();
bufferedReader.close();
inputStreamReader.close();
fileInputStream.close();

SSynonym Server也支持用户进行手动更新或追加自己的同义词文档或上传自己的同义词文件到远程仓库中,做到动态更新同义词词典的功能。

5.结果展示

5.1环境准备

自定义同义词服务采用SpringBoot进行开发,通过配置文件方式配置服务启动端口、synonym地址等信息,服务启动方式:java –jar Synonym-1.0-SNAPSHOT.jar

Elasticsearch之自定义同义词开发实践 

服务启动之后,需要创建同义词索引。创建索引时,需要创建自定义的filter,例如我们创建一个名为zyf3的索引:

Elasticsearch之自定义同义词开发实践

图中红色标注部分为自定义filter,与离线同义词解析方式不同,这里synonyms_path为服务器的IP、端口号和资源请求接口,interval代表修改synonym文件之后ES请求同义词文档的时间间隔。

自定义filter完成之后,在索引的settings中设置了该索引的Analyzer,Analyzer在文章让搜索引擎更懂你-Elasticsearch自定义分词开发实践有详细的介绍,这里自定义了一个analyzer,设置该analyzer的分词(Tokenizer)为IK的Ik_max_word算法,并指定Token filter为自定义的filter(remote_synonym)。

索引zyf3仅预定义了一个字段(STANDER_COMP_NAME),类型为text,并指定该字段的analyzer为自定义的analyzer(synonym)。

索引创建完成,接下来我们分别对上面的两种场景进行结果演示。

5.2场景一

首先,我们在synonym.txt文件中追加一行:

京东,淘宝

经过30s之后,ES集群开始加载synonym文件。

Elasticsearch之自定义同义词开发实践 

从上图可以看出,同义词“京东”和“淘宝”已经加载成功,由于同义词服务的优点之一是不用重启集群,synonym文件会自动生效,所以我们直接对zyf3进行查询。

Elasticsearch之自定义同义词开发实践

上图直接使用了自定义的analyzer(synonym)对“京东”和“淘宝”分别进行查询分析,最后的得到的结果相同,即无论我们查询“京东”还是“淘宝”,因为它们两个在synonym文件中定义的是同义词,所以查询分析两个词是等价的。

从这个结果也可以看出,对于文章开头提出来的场景一,同义词服务可以很完美的解决出现的问题,使得查询可以更加全面得到我们想要的数据。

5.3场景二

同样,我们首先对synonym文件进行修改,使得同义词适合于场景二,这里我们同样定义“京东”和“淘宝”为同义词,但它们都归属于“平台”,具体的synonym文件添加如下信息:

京东,淘宝 => 平台

同样不需要对集群和索引进行操作,经过30s之后,集群自定加载synonym文件。

Elasticsearch之自定义同义词开发实践 

从图中可以看出,同义词加载成功,我们直接查询分析“京东”和“淘宝”,查询结果如下:

Elasticsearch之自定义同义词开发实践

同样分别对“京东”和“淘宝”进行查询分析,从图中可以看到,最后的结果是完成一样的,都是返回结果“平台”,即返回同义词的汇总结果。

从这个结果中,可以看出同义词服务可以很好的解决场景二出现的问题,通过同义词查询,可以更加准确、方便的得到我们需要的数据,使得查询条件更加宽泛。

6.遇到的问题

本次主要是SynonymServer和SynonymClient的开发,在开发和使用过程中,出现一些问题,总结如下:

  1. IkServer接口返回的response需要指定ContentType,否则会出现ES请求接口时,加载词典失败。
  2. 同义词查询需要和自定义分词相结合使用,如果在synonym文件中定义的同义词被IK分词器进行了切分,那么同义词查询将会出现查询不到的情况,所以自定义同义词最好也同时出现在自定义分词文件中。

7.总结与展望

本文重点讲解什么是同义词查询、同义词查询解决了什么问题、原理以及开发的过程细节,最后对不同场景的结果进行了展示。可以看到,自定义同义词查询可以更好的对数据进行查询,可以使查询性能更加的全面、快捷、准确。本文是让搜索引擎更懂你-Elasticsearch自定义分词开发实践的续集,里面有很多的概念都在该文中做了详细介绍,如果小伙伴遇见什么不懂的概念,可以研读该文章,如果大家有什么问题或建议,也欢迎大家积极与我们联系、沟通,共同学习。

作者:张亚飞

参考文献:

Elasticsearch Plugins and Integrations [7.10] | Elastic

Elasticsearch学习笔记6: 同义词搜索实现_weixin_34112181的博客-CSDN博客

https://blog.csdn.net/yexiaomodemo/article/details/86519753

《Elasticsearch源码解析及优化实战》

更多交流,欢迎关注我个人公众号“数匠笔谈”

上一篇:2022.02.22 - 248.八皇后


下一篇:ElasticSearch安装(后续插图)