使用Elasticsearch实现Spring Boot的自动完成功能 -Milos Biljanovic


有几种方法可以通过Elasticsearch将自动完成功能添加到您的Spring Boot应用程序中:

  1. 使用通配符搜索
  2. 将自定义分析器与ngrams一起使用
  3. Elasticsearch完成建议器

我们将专注于使用ngrams的自定义分析器。听起来有点复杂,但实际上并非如此。让我们开始吧!
内容列表:
  • 需求用例
  • 使用SpringData与Elasticsearch的基本设置SpringBoot
  • Elasticsearch和自定义分析器
  • 将自定义分析器与Ngrams一起使用
  • 结论

需求用例
我们想要创建一个简单的REST API,以搜索存储在Elasticsearch中的用户列表。将有一个GET端点,我们可以在其中发送有关正在寻找谁的搜索输入。假设我们有兴趣按国家/地区搜索用户。
我们希望我们的搜索支持以下查询:

  1. 完整的单词:Bahamas/bahamas寻找来自Bahamas的用户。
  2. 分词:baham,bah从Bahamas寻找用户。
  3. 多个完整的单词:bahamas belize从Bahamas或belize寻找用户。
  4. 多个不完整词:baham beliz从Bahamas或belize寻找用户。
  5. 部分和完整单词的混合单词:trin和toba从Trinidad 和Tobago寻找用户。

基本设置:带有Elasticsearch的SpringBoot
首先,我们需要启动Elasticsearch,其次,我们需要使用Spring Boot应用程序来实现搜索。
检查SpringData Elasticsearch版本支持当前6.8.4是SpringBoot 2.2.x支持的最新Elasticsearch"

启动Spring Boot应用程序
Clone或下载此git repo(签出分支master-prefix-phrase-match),然后在您喜欢的IDE中打开项目。首次启动应用程序时,样本数据中的用户将被添加到Elasticsearch中。
您可以检查使用此命令添加的用户列表:
curl localhost:8080/users

现在让我们搜索。以下是我们使用词组前缀查询进行搜索的逻辑核心。

public List<User> search(String keywords) {
        MatchPhrasePrefixQueryBuilder searchByCountries = QueryBuilders.matchPhrasePrefixQuery("country", keywords);
        return this.userRepository.search(searchByCountries);
    }

短语前缀工作示例:

关键字:“ puerto r”表示“ puerto”是需要在国家名称中输入的确切单词,而“ r”则是“ puerto”之后的任何单词的前缀。这将匹配“puerto”。

让我们尝试以下搜索:

curl localhost:8080/users/search?keywords=bahamas
curl localhost:8080/users/search?keywords=baham

太好了,这将返回来自Bahamas的用户。我们的第一个实现了1.和2.需求,但是由于我们使用Elasticsearch的方式以及前缀短语匹配的工作方式,因此其余所有方法都失败了。
我们可以使用通配符搜索来解决此问题,但这将对性能产生影响,我们避免使用Elasticsearch的核心,即它的反向索引。因此,在下一节中,我们将介绍Elasticsearch如何进行索引和搜索,以及如何在Spring Boot应用程序中使用它来进行更灵活的搜索。

Elasticsearch和自定义分析器
分析器用于添加到Elasticsearch的数据,也可以用于在Elasticsearch中用于查询数据的搜索输入。
分析器分为三个部分:

  1. 字符过滤器:我们可以剥离,删除或更改输入数据。基本示例是使用html_strip过滤器,该过滤器将删除html标签。
  2. 分词;我们可以打破输入数据的简单标记。默认情况下,使用标准标记器。示例:输入数据:“fox in a forest”令牌:[fox,in,a,forest]
  3. 令牌过滤器:我们可以添加,修改或删除上一步中拥有的令牌。基本示例是小写令牌过滤器,它将所有令牌都转换为小写。

对于我们的自动完成功能,我们将创建使用edge_ngram令牌过滤器的自定义分析器,以便创建与我们的关键字匹配的其他令牌。将数据添加到Elasticsearch(索引时间)时,将使用此分析器。
"autocomplete_filter": {
       
"type": "edge_ngram",
       
"min_gram": 1,
       
"max_gram": 20
}

edge_ngram的工作方式示例:

输入令牌:bahamas
输出令牌:[b,ba,bah,baha,baham,bahama,bahamas]
它创建指定了最小和最大长度的前缀。

将自定义分析器与Ngrams一起使用
使用自定义分析器的代码位于master分支上。以下是对先前解决方案的必要更改。

  • 创建自定义分析器并设置为“用户”中的“国家/地区”字段

分析器配置:
{
  "analysis": {
   
"filter": {
     
"autocomplete_filter": {
       
"type": "edge_ngram",
       
"min_gram": 1,
       
"max_gram": 20
      }
    },
   
"analyzer": {
     
"autocomplete_search": {
       
"type": "custom",
       
"tokenizer": "standard",
       
"filter": [
         
"lowercase"
        ]
      },
     
"autocomplete_index": {
       
"type": "custom",
       
"tokenizer": "standard",
       
"filter": [
         
"lowercase",
         
"autocomplete_filter"
        ]
      }
    }
  }

用户User类,使用带有@Setting和@Field批注的新配置:

@Document(indexName = "users")
@Setting(settingPath =
"es-config/elastic-analyzer.json")
@Getter
@Setter
public class User {
    @Id
    private String id;
    private String firstName;
    private String lastName;

    @Field(type = FieldType.Text, analyzer =
"autocomplete_index", searchAnalyzer = "autocomplete_search")
    private String country;
}

修改搜索使用查询匹配而不是前缀匹配:

public List<User> search(String keywords) {
        MatchQueryBuilder searchByCountries = QueryBuilders.matchQuery("country", keywords);
        return this.userRepository.search(searchByCountries);
    }

匹配查询工作方式的一个示例:

关键字:“ puerto baham”
它将查找名称中带有“ puerto”或“ baham”的国家,因此它将返回恰好想要的来自Puerto Rico 和 Bahamas的用户。

从Elasticsearch中删除旧索引:

curl -X DELETE localhost:9200/users

现在,我们可以启动Spring Boot应用程序并测试我们的新搜索:

curl localhost:8080/users/search?keywords=trin%20and%20toba

太好了,现在返回了来自Trinidad 和Tobago的用户。

多个国家/地区的另一个示例:

curl localhost:8080/users/search?keywords=bel%20bahamas

它返回 Belize和Bahamas的用户。这样,我们满足了所有用例要求。


结论
仅用几行代码,我们就使用Elasticsearch Spring Data向Spring Boot应用程序添加了一个很酷的自动完成功能。自己尝试一下,因为该项目可以作为您进行测试和添加其他有趣功能的游乐场。