在 Spring Boot 中使用搜索引擎 Elasticsearch


Elasticsearch 建立在?Apache Lucene 之上,于 2010 年由 Elasticsearch NV(現(xiàn)為 Elastic)首次發(fā)布。據(jù)?Elastic?網(wǎng)站稱(chēng),它是一個(gè)分布式開(kāi)源搜索和分析引擎,適用于所有類(lèi)型的數(shù)據(jù),包括文本、數(shù)值 、地理空間、結(jié)構(gòu)化和非結(jié)構(gòu)化。
Elasticsearch 操作通過(guò) REST API 實(shí)現(xiàn)。主要功能是:
將文檔存儲(chǔ)在索引中,
使用強(qiáng)大的查詢(xún)搜索索引以獲取這些文檔,以及
對(duì)數(shù)據(jù)運(yùn)行分析函數(shù)。
Spring Data Elasticsearch 提供了一個(gè)簡(jiǎn)單的接口來(lái)在 Elasticsearch 上執(zhí)行這些操作,作為直接使用 REST API 的替代方法。
在這里,我們將使用 Spring Data Elasticsearch 來(lái)演示 Elasticsearch 的索引和搜索功能,并在最后構(gòu)建一個(gè)簡(jiǎn)單的搜索應(yīng)用程序,用于在產(chǎn)品庫(kù)存中搜索產(chǎn)品。
? 代碼示例
本文附有?GitHub 上的工作代碼示例。
Elasticsearch 概念
了解 Elasticsearch 概念的最簡(jiǎn)單方法是用數(shù)據(jù)庫(kù)進(jìn)行類(lèi)比,如下表所示:
我們要搜索或分析的任何數(shù)據(jù)都作為文檔存儲(chǔ)在索引中。在 Spring Data 中,我們以 POJO 的形式表示一個(gè)文檔,并用注解對(duì)其進(jìn)行修飾以定義到 Elasticsearch 文檔的映射。
與數(shù)據(jù)庫(kù)不同,存儲(chǔ)在 Elasticsearch 中的文本首先由各種分析器處理。默認(rèn)分析器通過(guò)常用單詞分隔符(如空格和標(biāo)點(diǎn)符號(hào))拆分文本,并刪除常用英語(yǔ)單詞。
如果我們存儲(chǔ)文本“The sky is blue”,分析器會(huì)將其存儲(chǔ)為包含“術(shù)語(yǔ)”“sky”和“blue”的文檔。我們將能夠使用“blue sky”、“sky”或“blue”形式的文本搜索此文檔,并將匹配程度作為分?jǐn)?shù)。
除了文本之外,Elasticsearch 還可以存儲(chǔ)其他類(lèi)型的數(shù)據(jù),稱(chēng)為?Field Type(字段類(lèi)型),如文檔中?mApping-types (映射類(lèi)型)部分所述。
啟動(dòng) Elasticsearch 實(shí)例
在進(jìn)一步討論之前,讓我們啟動(dòng)一個(gè) Elasticsearch 實(shí)例,我們將使用它來(lái)運(yùn)行我們的示例。有多種運(yùn)行 Elasticsearch 實(shí)例的方法:
使用托管服務(wù)
使用來(lái)自?AWS?或?Azure?等云提供商的托管服務(wù)
通過(guò)在虛擬機(jī)集群中自己安裝 Elasticsearch
運(yùn)行 Docker 鏡像
我們將使用來(lái)自 Dockerhub 的 Docker 鏡像,這對(duì)于我們的演示應(yīng)用程序來(lái)說(shuō)已經(jīng)足夠了。讓我們通過(guò)運(yùn)行 Docker run 命令來(lái)啟動(dòng) Elasticsearch 實(shí)例:
復(fù)制代碼
執(zhí)行此命令將啟動(dòng)一個(gè) Elasticsearch 實(shí)例,偵聽(tīng)端口 9200。我們可以通過(guò)點(diǎn)擊 URL http://localhost:9200 來(lái)驗(yàn)證實(shí)例狀態(tài),并在瀏覽器中檢查結(jié)果輸出:
復(fù)制代碼
如果我們的 Elasticsearch 實(shí)例啟動(dòng)成功,應(yīng)該看到上面的輸出。
使用 REST API 進(jìn)行索引和搜索
Elasticsearch 操作通過(guò) REST API 訪問(wèn)。 有兩種方法可以將文檔添加到索引中:
一次添加一個(gè)文檔,或者
批量添加文檔。
添加單個(gè)文檔的 API 接受一個(gè)文檔作為參數(shù)。
對(duì) Elasticsearch 實(shí)例的簡(jiǎn)單 PUT 請(qǐng)求用于存儲(chǔ)文檔如下所示:
復(fù)制代碼
這會(huì)將消息 - “The Sky is blue today”存儲(chǔ)為“messages”的索引中的文檔。
我們可以使用發(fā)送到搜索 REST API 的搜索查詢(xún)來(lái)獲取此文檔:
復(fù)制代碼
這里我們發(fā)送一個(gè)?match 類(lèi)型的查詢(xún)來(lái)獲取匹配字符串“blue sky”的文檔。我們可以通過(guò)多種方式指定用于搜索文檔的查詢(xún)。Elasticsearch 提供了一個(gè)基于 JSON 的 查詢(xún) DSL(Domain Specific Language - 領(lǐng)域特定語(yǔ)言)來(lái)定義查詢(xún)。
對(duì)于批量添加,我們需要提供一個(gè)包含類(lèi)似以下代碼段的條目的 JSON 文檔:
復(fù)制代碼
使用 Spring Data 進(jìn)行 Elasticsearch 操作
我們有兩種使用 Spring Data 訪問(wèn) Elasticsearch 的方法,如下所示:

Repositories:我們?cè)诮涌谥卸x方法,Elasticsearch 查詢(xún)是在運(yùn)行時(shí)根據(jù)方法名稱(chēng)生成的。
ElasticsearchRestTemplate:我們使用方法鏈和原生查詢(xún)創(chuàng)建查詢(xún),以便在相對(duì)復(fù)雜的場(chǎng)景中更好地控制創(chuàng)建 Elasticsearch 查詢(xún)。
我們將在以下各節(jié)中更詳細(xì)地研究這兩種方式。
創(chuàng)建應(yīng)用程序并添加依賴(lài)項(xiàng)
讓我們首先通過(guò)包含 web、thymeleaf 和 lombok 的依賴(lài)項(xiàng),使用?Spring Initializr 創(chuàng)建我們的應(yīng)用程序。添加?thymeleaf 依賴(lài)項(xiàng)以便增加用戶(hù)界面。
在 Maven?pom.xml 中添加?spring-data-elasticsearch 依賴(lài)項(xiàng):
復(fù)制代碼
連接到 Elasticsearch 實(shí)例
Spring Data Elasticsearch 使用 Java High Level REST Client (JHLC) 連接到 Elasticsearch 服務(wù)器。JHLC 是 Elasticsearch 的默認(rèn)客戶(hù)端。我們將創(chuàng)建一個(gè) Spring Bean 配置來(lái)進(jìn)行設(shè)置:
復(fù)制代碼
在這里,我們連接到我們之前啟動(dòng)的 Elasticsearch 實(shí)例。我們可以通過(guò)添加更多屬性(例如啟用 ssl、設(shè)置超時(shí)等)來(lái)進(jìn)一步自定義連接。
為了調(diào)試和診斷,我們將在?logback-spring.xml 的日志配置中打開(kāi)傳輸級(jí)別的請(qǐng)求/響應(yīng)日志:
復(fù)制代碼
表達(dá)文檔
在我們的示例中,我們將按名稱(chēng)、品牌、價(jià)格或描述搜索產(chǎn)品。因此,為了將產(chǎn)品作為文檔存儲(chǔ)在 Elasticsearch 中,我們將產(chǎn)品表示為 POJO,并加上?Field 注解以配置 Elasticsearch 的映射,如下所示:
復(fù)制代碼
?注解指定索引名稱(chēng)。
注解使注解字段成為文檔的
,作為此索引中的唯一標(biāo)識(shí)符。id 字段有 512 個(gè)字符的限制。
注解配置字段的類(lèi)型。我們還可以將名稱(chēng)設(shè)置為不同的字段名稱(chēng)。
在 Elasticsearch 中基于這些注解創(chuàng)建了名為?productindex 的索引。
使用 Spring Data Repository 進(jìn)行索引和搜索
存儲(chǔ)庫(kù)提供了使用 finder 方法訪問(wèn) Spring Data 中數(shù)據(jù)的最方便的方法。Elasticsearch 查詢(xún)是根據(jù)方法名稱(chēng)創(chuàng)建的。但是,我們必須小心避免產(chǎn)生低效的查詢(xún)并給集群帶來(lái)高負(fù)載。
讓我們通過(guò)擴(kuò)展?ElasticsearchRepository?接口來(lái)創(chuàng)建一個(gè) Spring Data 存儲(chǔ)庫(kù)接口:
復(fù)制代碼
此處?ProductRepository 類(lèi)繼承了?ElasticsearchRepository 接口中包含的 save()、saveAll()、find() 和?findAll() 等方法。
索引
我們現(xiàn)在將通過(guò)調(diào)用?save() 方法存儲(chǔ)一個(gè)產(chǎn)品,調(diào)用?saveAll() 方法來(lái)批量索引,從而在索引中存儲(chǔ)一些產(chǎn)品。在此之前,我們將存儲(chǔ)庫(kù)接口放在一個(gè)服務(wù)類(lèi)中:
復(fù)制代碼
當(dāng)我們從 JUnit 調(diào)用這些方法時(shí),我們可以在跟蹤日志中看到 REST API 調(diào)用索引和批量索引。
搜索
為了滿(mǎn)足我們的搜索要求,我們將向存儲(chǔ)庫(kù)接口添加 finder 方法:
復(fù)制代碼
在使用 JUnit 運(yùn)行?findByName() 方法時(shí),我們可以看到在發(fā)送到服務(wù)器之前在跟蹤日志中生成的 Elasticsearch 查詢(xún):
復(fù)制代碼
類(lèi)似地,通過(guò)運(yùn)行 findByManufacturerAndCategory()方法,我們可以看到使用兩個(gè)?query_string 參數(shù)對(duì)應(yīng)兩個(gè)字段——“manufacturer”和“category”生成的查詢(xún):
復(fù)制代碼
有多種方法命名模式可以生成各種 Elasticsearch 查詢(xún)。
使用?ElasticsearchRestTemplate 進(jìn)行索引和搜索
當(dāng)我們需要更多地控制我們?cè)O(shè)計(jì)查詢(xún)的方式,或者團(tuán)隊(duì)已經(jīng)掌握了 Elasticsearch 語(yǔ)法時(shí),Spring Data 存儲(chǔ)庫(kù)可能就不再適合。
在這種情況下,我們使用?ElasticsearchRestTemplate。它是 Elasticsearch 基于 HTTP 的新客戶(hù)端,取代以前使用節(jié)點(diǎn)到節(jié)點(diǎn)二進(jìn)制協(xié)議的?TransportClient。
ElasticsearchRestTemplate?實(shí)現(xiàn)了接口 ElasticsearchOperations,該接口負(fù)責(zé)底層搜索和集群操的繁雜工作。
索引
該接口具有用于添加單個(gè)文檔的方法?index() 和用于向索引添加多個(gè)文檔的?bulkIndex() 方法。此處的代碼片段顯示了如何使用?bulkIndex() 將多個(gè)產(chǎn)品添加到索引“productindex”:
復(fù)制代碼
要存儲(chǔ)的文檔包含在?IndexQuery 對(duì)象中。bulkIndex() 方法將?IndexQuery 對(duì)象列表和包含在?IndexCoordinates 中的 Index 名稱(chēng)作為輸入。當(dāng)我們執(zhí)行此方法時(shí),我們會(huì)獲得批量請(qǐng)求的 REST API 跟蹤:
復(fù)制代碼
接下來(lái),我們使用?index() 方法添加單個(gè)文檔:
復(fù)制代碼
跟蹤相應(yīng)地顯示了用于添加單個(gè)文檔的 REST API PUT 請(qǐng)求。
復(fù)制代碼
搜索
ElasticsearchRestTemplate?還具有 search()方法,用于在索引中搜索文檔。此搜索操作類(lèi)似于 Elasticsearch 查詢(xún),是通過(guò)構(gòu)造?Query 對(duì)象并將其傳遞給搜索方法來(lái)構(gòu)建的。
Query?對(duì)象具有三種變體 - NativeQueryy、StringQuery 和 CriteriaQuery,具體取決于我們?nèi)绾螛?gòu)造查詢(xún)。讓我們構(gòu)建一些用于搜索產(chǎn)品的查詢(xún)。
NativeQuery
NativeQuery?為使用表示 Elasticsearch 構(gòu)造(如聚合、過(guò)濾和排序)的對(duì)象構(gòu)建查詢(xún)提供了最大的靈活性。這是用于搜索與特定制造商匹配的產(chǎn)品的 NativeQuery:
復(fù)制代碼
在這里,我們使用?NativeSearchQueryBuilder 構(gòu)建查詢(xún),該查詢(xún)使用?MatchQueryBuilder 指定包含字段“制造商”的匹配查詢(xún)。
StringQuery
StringQuery?通過(guò)允許將原生 Elasticsearch 查詢(xún)用作 JSON 字符串來(lái)提供完全控制,如下所示:
復(fù)制代碼
在此代碼片段中,我們指定了一個(gè)簡(jiǎn)單的?match 查詢(xún),用于獲取具有作為方法參數(shù)發(fā)送的特定名稱(chēng)的產(chǎn)品。
CriteriaQuery
使用 CriteriaQuery,我們可以在不了解 Elasticsearch 任何術(shù)語(yǔ)的情況下構(gòu)建查詢(xún)。查詢(xún)是使用帶有 Criteria 對(duì)象的方法鏈構(gòu)建的。每個(gè)對(duì)象指定一些用于搜索文檔的標(biāo)準(zhǔn):
復(fù)制代碼
在此代碼片段中,我們使用?CriteriaQuery 形成查詢(xún)以獲取價(jià)格大于 10.0 且小于 100.0 的產(chǎn)品。
構(gòu)建搜索應(yīng)用程序
我們現(xiàn)在將向我們的應(yīng)用程序添加一個(gè)用戶(hù)界面,以查看產(chǎn)品搜索的實(shí)際效果。用戶(hù)界面將有一個(gè)搜索輸入框,用于按名稱(chēng)或描述搜索產(chǎn)品。輸入框?qū)⒕哂凶詣?dòng)完成功能,以顯示基于可用產(chǎn)品的建議列表,如下所示:

我們將為用戶(hù)的搜索輸入創(chuàng)建自動(dòng)完成建議。然后根據(jù)與用戶(hù)輸入的搜索文本密切匹配的名稱(chēng)或描述搜索產(chǎn)品。我們將構(gòu)建兩個(gè)搜索服務(wù)來(lái)實(shí)現(xiàn)這個(gè)用例:
獲取自動(dòng)完成功能的搜索建議
根據(jù)用戶(hù)的搜索查詢(xún)處理搜索產(chǎn)品的搜索
服務(wù)類(lèi)?ProductSearchService 將包含搜索和獲取建議的方法。
GitHub 存儲(chǔ)庫(kù)
中提供了帶有用戶(hù)界面的成熟應(yīng)用程序。
建立產(chǎn)品搜索索引
productindex?與我們之前用于運(yùn)行 JUnit 測(cè)試的索引相同。我們將首先使用 Elasticsearch REST API 刪除 productindex,以便在應(yīng)用程序啟動(dòng)期間使用從我們的 50 個(gè)時(shí)尚系列產(chǎn)品的示例數(shù)據(jù)集中加載的產(chǎn)品創(chuàng)建新的 productindex:
curl -X DELETE http://localhost:9200/productindex
如果刪除操作成功,我們將收到消息 {"acknowledged": true}。
現(xiàn)在,讓我們?yōu)閹?kù)存中的產(chǎn)品創(chuàng)建一個(gè)索引。我們將使用包含 50 種產(chǎn)品的示例數(shù)據(jù)集來(lái)構(gòu)建我們的索引。這些產(chǎn)品在?CSV 文件中被排列為單獨(dú)的行。
每行都有三個(gè)屬性 - id、name 和 description。我們希望在應(yīng)用程序啟動(dòng)期間創(chuàng)建索引。請(qǐng)注意,在實(shí)際生產(chǎn)環(huán)境中,索引創(chuàng)建應(yīng)該是一個(gè)單獨(dú)的過(guò)程。我們將讀取 CSV 的每一行并將其添加到產(chǎn)品索引中:
復(fù)制代碼
在這個(gè)片段中,我們通過(guò)從數(shù)據(jù)集中讀取行并將這些行傳遞給存儲(chǔ)庫(kù)的?saveAll() 方法以將產(chǎn)品添加到索引中來(lái)進(jìn)行一些預(yù)處理。在運(yùn)行應(yīng)用程序時(shí),我們可以在應(yīng)用程序啟動(dòng)中看到以下跟蹤日志。
復(fù)制代碼
使用多字段和模糊搜索搜索產(chǎn)品
下面是我們?cè)诜椒?processSearch() 中提交搜索請(qǐng)求時(shí)如何處理搜索請(qǐng)求:
復(fù)制代碼
在這里,我們對(duì)多個(gè)字段執(zhí)行搜索 - 名稱(chēng)和描述。 我們還附加了 fuzziness()來(lái)搜索緊密匹配的文本以解釋拼寫(xiě)錯(cuò)誤。
使用通配符搜索獲取建議
接下來(lái),我們?yōu)樗阉魑谋究驑?gòu)建自動(dòng)完成功能。 當(dāng)我們?cè)谒阉魑谋咀侄沃休斎雰?nèi)容時(shí),我們將通過(guò)使用搜索框中輸入的字符執(zhí)行通配符搜索來(lái)獲取建議。
我們?cè)?fetchSuggestions() 方法中構(gòu)建此函數(shù),如下所示:
復(fù)制代碼
我們以搜索輸入文本的形式使用通配符查詢(xún),并附加?* 以便如果我們輸入“red”,我們將獲得以“red”開(kāi)頭的建議。我們使用?withPageable() 方法將建議的數(shù)量限制為 5??梢栽诖颂幙吹秸谶\(yùn)行的應(yīng)用程序的搜索結(jié)果的一些屏幕截圖:


結(jié)論
在本文中,我們介紹了 Elasticsearch 的主要操作——索引文檔、批量索引和搜索——它們以 REST API 的形式提供。Query DSL 與不同分析器的結(jié)合使搜索變得非常強(qiáng)大。
Spring Data Elasticsearch 通過(guò)使用 Spring Data Repositories 或?ElasticsearchRestTemplate 提供了方便的接口來(lái)訪問(wèn)應(yīng)用程序中的這些操作。
我們最終構(gòu)建了一個(gè)應(yīng)用程序,在其中我們看到了如何在接近現(xiàn)實(shí)生活的應(yīng)用程序中使用 Elasticsearch 的批量索引和搜索功能。
本文譯自: Using Elasticsearch with Spring Boot - Reflectoring
有關(guān) ELK 套件請(qǐng)參考: ELK 教程 – 高效發(fā)現(xiàn)、分析和可視化你的數(shù)據(jù) – 信碼由韁 (icodewalker.com)