使用Elasticsearch實現Spring Boot的自動完成功能 -Milos Biljanovic

banq發表於2020-01-07

有幾種方法可以通過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應用程式新增了一個很酷的自動完成功能。自己嘗試一下,因為該專案可以作為您進行測試和新增其他有趣功能的遊樂場。

相關文章