第13章 集成NoSQL數(shù)據(jù)庫,實現(xiàn)Elasticsearch和Solr搜索引擎
?????????關(guān)于搜索引擎 我們很難實現(xiàn) Elasticseach 和 Solr兩大搜索框架的效果;所以本章針對兩大搜索框架,非常詳細(xì)地講解 它們的原理和具體使用方法,
首先 介紹什么是搜索引擎 、如何用 MySQL實現(xiàn)簡單的搜索引擎,以及Elasticseach 的 概念和接口類;
然后介紹Elasticseach 的精準(zhǔn)、模糊、范圍、組合、分頁、聚合查詢;
最后介紹 Solr的概念、安裝、配置和使用,并對兩大搜索框架進(jìn)行比較。
13.1?Elasticseach --- 搜索應(yīng)用服務(wù)器
13.1.1什么是搜索引擎
????????搜索引擎(search engine)通常意義上是指:根據(jù)特定策略,運用特定的爬蟲程序從互聯(lián)網(wǎng)?上搜集信息,然后對信息進(jìn)行處理后,為用戶提供檢索服務(wù),將檢索到的相關(guān)信息展示給用戶的系統(tǒng)。
本章主要講解的是搜索的索引和檢索,不涉及爬蟲程序的內(nèi)容爬取。大部分公司的業(yè)務(wù)也不會有爬取工作,而只提供查詢服務(wù),而且Elasticsearch也只是提供這方面的功能。
13.1.2用數(shù)據(jù)庫實現(xiàn)搜索功能
????????在極少量的數(shù)據(jù)應(yīng)用中,可以利用關(guān)系型數(shù)據(jù)庫的Select語句實現(xiàn)搜索功能。比如有一個電子?商務(wù)系統(tǒng),采用MySQL數(shù)據(jù)庫,其產(chǎn)品數(shù)據(jù)表如圖13-1所示。
需要實現(xiàn)產(chǎn)品搜索功能,可以使用如下SQL語句:
select * from product where 字段 like '%關(guān)鍵詞%' select * from product where 字段 like '關(guān)鍵詞%'
上述SQL語句中,如果要使用索引提高性能,則like就必須寫成like 'a%'或 '%a'形式。兩邊都加上“%”是不會觸發(fā)索引的。
我們先不考慮性能,只從商業(yè)效果上來看下面的演示是否能滿足用戶的需求。
1.簡單查詢
????????假如,用戶想搜索“紅富士”的蘋果,當(dāng)搜索關(guān)鍵詞“紅富士”之后,MySQL執(zhí)行SQL語句,?如圖13-2所示。
????????????????查詢結(jié)果是“name”字段中出現(xiàn)“紅富士”的產(chǎn)品信息。
2.多字段模糊查詢
????????上面已經(jīng)實現(xiàn)了簡單的查詢。但是,我們發(fā)現(xiàn)產(chǎn)品“金帥”的“body”字段中是"金帥,蘋果中一種好吃的蘋果,和紅富士一樣好吃”,這包含了 “紅富士”關(guān)鍵詞,如何才能把“body”字段?中的關(guān)鍵詞也搜索到呢?
????????現(xiàn)在對SQL語句進(jìn)行改進(jìn),改為如圖13-3所示的SQL語句。可以看到,"body"字段中有?“紅富士”的詞也被檢索出來了。
3.分詞查詢
????????由于用戶輸入時可能會存在輸入錯誤的情況,假設(shè)用戶輸入的是“紅富s”,那么數(shù)據(jù)庫是無法查詢到結(jié)果的。這時需要用分詞算法對輸入數(shù)據(jù)進(jìn)行分詞,可以分為:
紅、富、S
紅富、S
紅、富S
然后對詞進(jìn)行分別查詢,查詢結(jié)果如圖13-4所示。
????????我們會發(fā)現(xiàn)結(jié)果中把“name”和“body”字段都搜索出來了,但是出現(xiàn)了一個問題一蘋果XS?手機殼不該出現(xiàn)卻出現(xiàn)了。當(dāng)然這里排在最后,可能對用戶影響不大,但是如果加上排序字段,
根據(jù)最新添加來排序呢?則變成了如圖13-5所示的效果。
????????這就會影響用戶的搜索體驗,本意是查詢水果的,結(jié)果手機配件卻排在了第一位,問題在哪里?呢?其中一個原因就是分詞沒分好,然后沒有權(quán)重設(shè)置(權(quán)重這里暫時不涉及)。所以,此時需要一?個很好的分詞系統(tǒng)。
????????從MySQL 5.7開始內(nèi)置了?ngram全文檢索插件,用來支持中文分詞,并且對MylSAM和?InnoDB引擎有效。在使用中文檢索分詞插件ngram之前,需要在MySQL配置文件里面設(shè)置它的?分詞大小,比如設(shè)置為2:
ngram_token_size=2
然后可以使用match命令進(jìn)行查詢。
select * from product where match (name, body)? against? ('紅富?s');
分詞的結(jié)果就變成了:
紅富、富S、紅S
查詢結(jié)果如圖13-6所示。
????????乍一看效果很好,但實際并不太科學(xué),配置限定死了,沒法根據(jù)應(yīng)用環(huán)境來配置。比如搜索“蘋 果XR"這個手機,因為分詞長度為“2”,那么蘋果這種水果也會被搜索岀來,所以結(jié)果不盡如?人意。
????????通過以上實例可以看出,如果用MySQL等關(guān)系型或NoSQL數(shù)據(jù)庫去實現(xiàn)搜索引擎還是很麻?煩的,即使在不考慮性能的情況下,考慮的問題依然非常多。
????????如果加上性能因素來考量,即使對數(shù)?據(jù)庫進(jìn)行索引,在面對復(fù)雜情況下的查詢時,效果和性能都是不盡如人意的。
????????所以,在現(xiàn)在信息爆炸的時代,處理大規(guī)模數(shù)據(jù)就顯得力不從心,這時需要一種專業(yè)、配置簡?單、性能極優(yōu)的搜索引擎系統(tǒng),如果能開源、可實現(xiàn)分布式、支持RESTfulAPI,則學(xué)習(xí)、使用成?本、性能和實
現(xiàn)就非常完美了。這時,Elasticsearch和Solr就出現(xiàn)在我們面前了。
13.1.3?認(rèn)識?Elasticsearch
????????Elasticsearch是一個分布式、RESTful風(fēng)格的搜索和數(shù)據(jù)分析引擎。通過它,能夠執(zhí)行及合并多種類型的搜索(結(jié)構(gòu)化數(shù)據(jù)、非結(jié)構(gòu)化數(shù)據(jù)、地理位置、指標(biāo)),解決不斷涌現(xiàn)出的各種需求。
????????Elasticsearch使用的是標(biāo)準(zhǔn)的RESTful風(fēng)格的API,使用JSON提供多種語言(Java、?Python、Net、SQL和PHP)的支持,它可以快速地存儲、搜索和分析海量數(shù)據(jù)。
????????Elasticsearch是用Java語言開發(fā)的,并使用Lucene作為其核心來實現(xiàn)所有索引和搜索的功?能。它的目的是:通過簡單的RESTful API來隱藏Lucene的復(fù)雜性,從而讓全文搜索變得簡單。
????????Elasticsearch是一個幵源的高擴展的分布式全文檢索引擎,可以近乎實時地存儲、檢索數(shù)據(jù);?本身擴展性很好,允許多臺服務(wù)器協(xié)同工作,每臺服務(wù)器可以運行多個實例。單個實例稱為一個節(jié)?點(node),—組節(jié)點構(gòu)成一個集群(cluster)?分片是底層的工作單元,文檔保存在分片內(nèi),分片?又被分配到集群內(nèi)的各個節(jié)點里,每個分片僅保存全部數(shù)據(jù)的一部分。
????????當(dāng)Elasticsearch的節(jié)點啟動后,它會使用多播(multicast)或單播(用戶更改了配置)尋找?集群中的其他節(jié)點,并與之建立連接。
13.1.4?Elasticsearch 應(yīng)用案例
- GitHub: 2013年年初,GitHub把Solr緩存改成了?Elasticsearch,以便用戶搜索20TB 的數(shù)據(jù),包括13億個文件和1300億行代碼。
- 維基百科:啟動以Elasticsearch為基礎(chǔ)的核心搜索架構(gòu)SoundCloud,為1.8億用戶提供?即時而精準(zhǔn)的音樂搜索服務(wù)。
- 百度:百度使用Elasticsearch作為數(shù)據(jù)分析引擎,20多個業(yè)務(wù)線采集服務(wù)器上的各類數(shù)?據(jù)及用戶自定義數(shù)據(jù),通過對各種數(shù)據(jù)進(jìn)行多維分析,輔助定位異常。其單集群最大100臺?機器,200個Elasticsearch節(jié)點,每天導(dǎo)入超過30TB的數(shù)據(jù)。
除這些公司外,Stack Overflow.新浪、阿里、360、攜程、有贊、蘇寧都在使用它。它被廣泛地用于各大公司的站內(nèi)搜索、IT系統(tǒng)搜索(OA、CRM、ERP)、數(shù)據(jù)分析等工作中。
13.1.5?對比?Elasticsearch?與?MySQL
????????盡管將Elasticsearch與MySQL進(jìn)行對比并不科學(xué),但是這樣的對比能區(qū)分日asticsearch 和MySQL數(shù)據(jù)庫的區(qū)別,便于快速用熟悉的知識來理解Elasticsearcho所以,本節(jié)采用對比的
方式來講解Elasticsearch。Elasticsearch與MySQL的結(jié)構(gòu)對比見表13-1。
表?13-1 Elasticsearch?與?MySQL 的結(jié)構(gòu)對比
ElasticSearch |
MySQL |
ElasticSearch |
MySQL |
index |
database |
everything is indexed |
index |
type |
table |
query dsl |
sql |
document |
row |
get url |
select *from table |
field |
column |
put url |
update table set |
mapping |
schema |
(1)關(guān)系型數(shù)據(jù)庫中的數(shù)據(jù)庫,相當(dāng)于Elasticsearch中的索引(index )
(2)?—個數(shù)據(jù)庫下面有多張表(table),相當(dāng)于一個索引(index)下面有多個類型(type)。
(3)?—個數(shù)據(jù)庫表(table)下的數(shù)據(jù)由多行(row)多列(column,屬性)組成,相當(dāng)于一個?type由多個文檔(document)和多個field組成。
(4)?在關(guān)系型數(shù)據(jù)庫中,schema定義了表、每個表的字段,還有表和字段之間的關(guān)系;? ? ?在?Elasticsearch中,mapping定義索引下的type的字段處理規(guī)則,即索引如何建立、索引類型、?是否保存原始索引JSON文檔、是否壓縮原始JSON文檔、是否需要分詞處理、如何進(jìn)行分詞處?理等。
(5)?在?MySQL?數(shù)據(jù)庫中的增(insert )、刪(delete )、改(update )、查(select)操作相當(dāng)于?Elasticsearch 中的增(put/post )、刪(delete )、改(update)、查(get)。
????????客戶端主要通過“方法(PUT/POST/GET/DELETE ) + http://ip:端口/索引名稱/類型/主鍵” 來訪問內(nèi)容。
13.1.6?認(rèn)識?ElasticSearchRepository
????????Spring-data-elasticsearch?是?Spring?提供的操作?Elasticsearch 的數(shù)據(jù)接口,它封裝了大量的基礎(chǔ)操作,通過它可以很方便地操作Elasticsearch的數(shù)據(jù)。
????????通過繼承ElasticsearchRepository來完成基本的CRUD及分頁操作,和普通的JPA沒有什么區(qū)別。比如下面實體Product的Repository繼承ElasticsearchRepository后,可以在?Elasticsearch文檔中進(jìn)行查找和比較等操作。具體使用方法見以下代碼:
?Component
public interface ProductRepository extends ElasticsearchRepository<Product,Long> {
Product findByld(long id);
Product findByName(String name);
List<Product> findByPriceBetween(double price 1, double price2);
}
????????ElasticsearchRepository有幾個特有的search方法,用來構(gòu)建一些Elasticsearch查詢,?主要由QueryBuilder 和 SearchQuery兩個參數(shù)來完成一些特殊查詢。
????????實現(xiàn)類NativeSearchQuery實現(xiàn)了?QueryBuilder和SearchQuery方法,要構(gòu)建復(fù)雜查詢,?可以通過構(gòu)建NativeSearchQuery類來實現(xiàn)。
????????—般情況下,不是直接新建NativeSearchQuery類,而是使用NativeSearchQueryBuilder 來完成NativeSearchQuery的構(gòu)建。具體用法見以下代碼:
NativeSearchQueryBuilder
.withQuery(QueryBuilder1)
.withFilter(QueryBuilder2)
.withSort(SortBuilder1)
.withXxx().build()
13.1.7?認(rèn)識?ElasticsearchTemplate
????????ElasticsearchTemplate是Spring對Elasticsearch的API進(jìn)行的封裝,主要用來對索引進(jìn)行倉U建、刪除等操作。它繼承了?Elasticsearchdperations?和?ApplicationContextAware?接口。?ElasticSearchTemplate?提供一些比?ElasticsearchRepository 更底層的方法。
ElasticsearchOperations接口中常用的方法如下。
- createlndex()方法:創(chuàng)建索引,返回值為布爾類型數(shù)據(jù)。
- indexExists()方法:查詢索引是否存在,返回值為布爾類型數(shù)據(jù)。
- putMapping()方法:創(chuàng)建映射,返回值為布爾類型數(shù)據(jù)。
- getMapping()方法:得到映射,返回值為一個Map數(shù)據(jù)。
- deletelndex()方法:刪除索引,返回值為布爾類型數(shù)據(jù)。
13.1.8?認(rèn)識注解@Document
????????注解@Document作用于類,用于標(biāo)記實體類為文檔對象。
存儲在Elasticsearch中的一條數(shù)據(jù),即是一個文檔,類似關(guān)系型數(shù)據(jù)庫的一行數(shù)據(jù)。?Elasticsearch會索引每個文檔的內(nèi)容,以便搜索。它使用JSON格式,將數(shù)據(jù)存儲到Elasticsearch 中,實際上是將JSON格式的字符串發(fā)送給了?Elasticsearch。
1.、document的核心元數(shù)據(jù)
document有三個核心元數(shù)據(jù),分別是_index、_type、_id。
(1) _index;代表一個document存放在哪個index中,類似的數(shù)據(jù)放在一個索引中,非類似的數(shù)據(jù)放在不同的索引中。index中包含了很多類似的document,這些document的field很大一部分是相同的。索引名稱必須小寫,不能用下畫線開頭,不包含逗號。
(2) _type;代表document屬于index的哪個類別,一個索引通常會劃分為多個type,邏輯?上對index不同的數(shù)據(jù)進(jìn)行分類。type名稱可以是大寫或小寫,但是不能用下畫線開頭,不能包含?逗號。
(3 ) _id;代表document的唯一標(biāo)識,與Jndex和_type —起可以標(biāo)識和定位一個 documento默認(rèn)自動創(chuàng)建id,也可以手動指定document的id。
2、document id的手動指定和自動生成
(1)手動指定document id。
????????如果需要從某些其他系統(tǒng)中導(dǎo)入一些數(shù)據(jù)到Elasticsearch,則會采用手動指定id的形式,因為一般情況下系統(tǒng)中已有數(shù)據(jù)的唯一標(biāo)識,可以用作Elasticsearch中的document的id
其語法格式為:
put /index/type/id
{
"json"
}
(2)自動生成 document id。 其語法格式為:
post /index/type
{
"json"
}
自動生成的id長度為20個字符,URL安全、Base64編碼、GUID、分布式系統(tǒng)并行生成時不會發(fā)生沖突。
3、document的_source元數(shù)據(jù),以及定制返回結(jié)果
????????_source元數(shù)據(jù)是在創(chuàng)建document時放在body中的JSON數(shù)據(jù)。在默認(rèn)情況下,查找數(shù)據(jù)?時會返回全部數(shù)據(jù)。如果要定制返回結(jié)果,則可以指定_source中返回哪些field
例如:
GET /_index/_type/1?_source=field
13.1.9 管理索引
1、創(chuàng)建索引
(1)?根據(jù)類的信息自動生成創(chuàng)建索引。
????????下面代碼是根據(jù)實體類創(chuàng)建一個名為“ec”的索引,并定義tpye是“product”。由于是單機環(huán)境,所以定義副本為0,分片為默認(rèn)值5。
@Document(indexName = "product", type = "product", replicas = 0, shards = 5)
public class Product implements Serializable {
)
代碼解釋如下。
- indexName :對應(yīng)索引庫名稱,可以理解為數(shù)據(jù)庫名。必須小寫,否則會報 "org.elasticsearch.indices.InvalidlndexNameException"異常。
- type:對應(yīng)在索引庫中的類型,可以將其理解為“表名”。
- shards:分片數(shù)量,默認(rèn)值為5。
- replicas:副本數(shù)量,默認(rèn)值為1。如果是單機環(huán)境,則健康狀態(tài)為“yellow”。如果要成為?“green”,則指定值為0即可。
(2)?手動創(chuàng)建索引。
? ? 可以使用createlndex方法手動指定indexName和Settings,再進(jìn)行映射。在使用前,要先注入ElasticsearchTemplate。使用方法如下。
- 根據(jù)索引名創(chuàng)建索引:lasticsearchTemplate.create!ndex("indexname");
- 根據(jù)類名創(chuàng)建索引:lasticsearchTemplate.createlndex(Product.class);
2、查詢索引
- 根據(jù)索引名查詢:elasticsearchTemplate.indexExists("indexname");
- 根據(jù)類名查詢:elasticsearchTemplate.indexExists(Product.class);
3、刪除索引
? 可以根據(jù)索引名和類名對索引進(jìn)行刪除。
- 根據(jù)索引名刪除:elasticsearchTemplate.deletelndex("indexname");
- 根據(jù)類名刪除:elasticsearchTemplate.deletelndex(Product.class);
13.2?實例55:用ELK管理Spring Boot應(yīng)用程序的日志
????????ELK?是?Elasticsearch+Logstash+Kibana 的簡稱。
????????Logstash負(fù)責(zé)將數(shù)據(jù)信息從輸入端傳輸?shù)捷敵龆?,比如將信息從MySQL傳入Elasticsearch, 還可以根據(jù)自己的需求在中間加上濾網(wǎng)。Logstash提供了很多功能強大的濾網(wǎng),以滿足各種應(yīng)?用場景。
Logstash有以下兩種工作方式:
(1) 每一臺機器啟動一個Logstash服務(wù),讀取本地的數(shù)據(jù)文件,生成流傳給Elasticsearch
(2) Logback引入Logstash包,然后直接生產(chǎn)JSON流,傳給一個中心的Logstash服務(wù)?器,Logstash?服務(wù)器再傳給?Elasticsearch。最后,Elasticsearch?將其流傳給?Kibana。
????????Kibana是一個開源的分析與可視化平臺,和Elasticsearch —起使用??梢杂?strong>Kibana搜索、?查看、交互存放在日asticsearch索弓I里的數(shù)據(jù)。使用各種不同的圖標(biāo)、表格、地圖等,Kibana能夠很輕易地展示高級數(shù)據(jù)分析與可視化。
????????ELK架構(gòu)為數(shù)據(jù)分布式存儲、日志解析和可視化創(chuàng)建了一個功能強大的管理鏈。三者相互配合,?取長補短,共同完成分布式大數(shù)據(jù)處理工作。
本實例通過Logstash收集本地的log文件流,傳輸給Elasticsearch。
本實例的源代碼可以在“/13/ELKDemo”目錄下找到。
13.2.1?安裝?Elasticsearch
(1)?通過官網(wǎng)下載Elasticsearch?
(2)?在下載完成后,首先將其解壓到合適的目錄,然后進(jìn)入解壓目錄下的bin目錄,雙擊?elasticsearch.bat文件啟動Elasticsearch。這里需要確保安裝的Java版本在1.8及以上。
(3)?訪問“http://127.0.0.1:9200/”,當(dāng)看到返回如下一串JSON格式的代碼時,則說明已經(jīng)?安裝成功了。
{ "name": "1q71xef", "cluster_name": "elasticsearch", //省略 "tagline": "You Know, for Search" }
根據(jù)應(yīng)用需要,還可以安裝Elasticsearch必要的一些插件,如Head、kibana、IK (中文分 詞)、graph
????????在Elasticsearch 6.0.0或更新版本中,創(chuàng)建的索引只會包含一個映射類型(mapping type ) ;
在Elasticsearch 5.x中創(chuàng)建的具有多個映射類型的索引在Elasticsearch 6.x中依然會正常工作。
在Elasticsearch 7.0.0 中,映射類型被完全移除了。
13.2.2?安裝?Logstash
1、安裝?Logstash
(1)?訪問?Elasticsearch?官網(wǎng)下載?Logstash?
(2)?將下載文件解壓到自定義的目錄即可。
2、配置?Logstash
(1)在解壓文件的config目錄下新建Iog4j_to_es.conf文件,寫入以下代碼:
input{
tcp{
host =>"localhost"
port =>9601
mode =>"server"
tags =>["tags"]
##JSON格式
codec => jsonjines
}
}
output{
elasticsearch{
hosts=>"127.0.0.1:9200"
index =>"demolog"
}
stdout{ codec=>rubydebug }
}
????????這里一定要注意:這是UTF-8的格式,不要帶BOM。如果啟動時岀現(xiàn)錯誤,則可以用“l(fā)ogstash -f ../config/xxx.conf -tn命令檢查配置文件是否錯誤。
(2)?新建文件run.bato?寫入代碼?“l(fā)ogstash -f ../config/log4j_to_es.conf”,保存。然后雙擊該配置文件,啟動Logstash
(3)?訪問“l(fā)ocalhost:9600”,如出現(xiàn)以下內(nèi)容,則代表配置成功。
{"host"":"zhonghua","version":"6.5.0","http_address":"127.0.0.1:9600","id":"03472165-2b17-4e5f-a1a1-f4 8ea4deb9a1","name":"zhonghua","build_date":"2018-11-09T19:43:40+00:00","build_sha":"4b3a404d6751 261 d155458c1 a8454a22167b1954","build_snapshot":false}
13.2.2?安裝?Kibana
????????Kibana是官方推出的Elasticsearch數(shù)據(jù)可視化工具。
(1 )通過訪問Elasticsearch官網(wǎng)下載Kibana。
(2)?解壓下載的壓縮文件,進(jìn)入解壓目錄,雙擊Kibana目錄的bin/kibana.bat,以啟動?Kibana,當(dāng)出現(xiàn)以下提示時,代表啟動成功。
? ? ? ?log? [08:23:47.611] [info][status][plugin:spaces@6.5.0] Status changed from yellow to green - Ready
(3)?訪問localhost:5601就可以進(jìn)入Kibana控制臺。
????????單擊控制臺左邊導(dǎo)航欄的“Dev-tools”按鈕,可以進(jìn)入Dev-tools界面。單擊“Get to work“, 然后在控制臺輸入“GET/_cat/health?”命令,可以查看服務(wù)器狀態(tài)。如果在右側(cè)返回的結(jié)果中看到green或yellow ,則表示服務(wù)器狀態(tài)正常。
????????yellow表示所有主分片可用,但不是所有副本分片都可用。如果Elasticsearch只是安裝在本地,且設(shè)置了副本大于0,則會出現(xiàn)黃色,這是正常的狀態(tài)。因為并沒有分布式部署,是單節(jié)點。另外,由于Elasticsearch默認(rèn)有1個副本,主分片和副本不能在同一個節(jié)點上,因此副本就是未分配(unassigned )
所以,在設(shè)計實體時可以設(shè)置@Document(indexName= "goods",type = "goods",shards = 5, yreplicas = 0),即?“repIicas=0”?就會變成?green;
13.2.4?配置?Spring Boot 項目
(1)添加項目依賴,見以下代碼:
<dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-log4j</artifactld>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupld>net.logstash.logback</groupld>
<artifactld>logstash-logback-encoder</artifactld>
<version>5.3</version>
</dependency>
(2)添加配置文件logback.xml,這里在Spring Boot項目里添加一個配置文件,見以下代碼:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="LOGSTASH"
class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>localhost:9601</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--encoder必須配置,有多種可選字符集 -->
<encoder charset="UTF-8">
<pattern>%d{HH:mm:ss.SSS} [%thread]%-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="LOGSTASH" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
13.2.5創(chuàng)建日志計劃任務(wù)
????????在Spring Boot項目中創(chuàng)建logTest類,用于測試將日志通過Logstash發(fā)送到日asticsearch, 見以下代碼
?Component
public class logTest {
private Logger logger = LoggerFactory.getLogger(logTest.class);
@Scheduled(fixedRate = 1000)
public void logtest() {
logger.trace("trace 日志");
logger.debug("debug 日志");
logger.info("info 日志");
logger.warn("warn 日志");
logger.error("error 日志");
}
}
(1)在入口類中添加注解@EnableScheduling,開啟計劃任務(wù),然后運行項目。
(2 )訪問http://localhost:5601?
????????選擇左側(cè)導(dǎo)航欄的“Management —>Index Patterns —> Create index pattern"命令,輸?入"demolog",單擊"Next”按鈕,選擇時間過濾器字段名,單擊“Create index pattern”按鈕, 創(chuàng)建完成。
????????進(jìn)入Kibana的Discover,就可以查看日志信息了。
13.2.6用Kibana查看管理日志
????????在Kibana的Discover頁面中,可以交互式地探索自己的數(shù)據(jù)。這里可以訪問與所選擇的索引?默認(rèn)匹配的每個索引中的文檔,可以提交查詢請求、過濾搜索結(jié)構(gòu),并查看文檔數(shù)據(jù),
????????還可以看到?匹配查詢請求的文檔數(shù)量,以及字段值統(tǒng)計信息。
????????如果選擇的索引模式配置了?time字段,則文檔隨時間的分布將顯示在頁面頂部的直方圖中。?Discover
????????Kibana的功能非常強大,還可以進(jìn)行可視化設(shè)計,創(chuàng)建熱點圖、區(qū)域圖、餅圖、時間線等,也?可以監(jiān)控Elasticsearch的健康狀態(tài)。如果安裝了?APM支持,還可以進(jìn)行性能監(jiān)控。
13.3?實例?56:在?Spring Boot?中集成?Elasticsearch,實現(xiàn)增加、?刪除、修改、查詢文檔的功能
????????本實例講解如何在Spring Boot中實現(xiàn)增加、刪除、修改、查詢文檔的功能,以理解Spring Boot 的相關(guān)Starter的使用和Elasticsea「ch的知識點和具體應(yīng)用。
本實例的源代碼可以在"/13/ElasticsearchProductDemo”目錄下找到。
13.3.1?集成?Elasticsearch
????????Spring Boot?提?供?了?Starter ( spring-boot-starter-data-elasticsearch )?來集成?Elasticsearch?
- 優(yōu)點:開發(fā)速度快,不要求熟悉Elasticsearch的一些API,能快速上手。即使之前對?Elasticsearch不了解,也能通過方法名或SQL語句快速寫出自己需要的邏輯。而具體轉(zhuǎn)換成API層的操作則是由框架底層實現(xiàn)的。
- 缺點:使用的Spring Boot的版本對Elasticsearch的版本也有了要求,不能超過某些版本號,在部署時需要注意。如果采用API方式,則能解決這個問題。
(1 )添加依賴,見以下代碼:
<!--Elasticsearch 支持-->
<dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-data-elasticsearch</artifactld>
</dependency>
(2)添加application.properties配置?見以下代碼:
spring.data.elasticsearch.cluster-name=elasticsearch
#節(jié)點的地址。注意,API模式下端口號是9300,千萬不要寫成9200
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
#是否開啟本地存儲
spring.data.elasticsearch.repositories.enable=true
13.3.2創(chuàng)建實體
(1)創(chuàng)建實體。
這里根據(jù)類的信息自動生成,也可以手動指定索引名稱。ElasticsearchTemplate中提供了創(chuàng)?建索引的API,因為進(jìn)行本機測試,沒做集群,所以replicas副本先設(shè)置為0。見以下代碼:
//省略
//索引名稱可以理解為數(shù)據(jù)庫名,必須為小寫,否則會報"org.elasticsearch.indices.lnvalidlndexNameException" 異常
@Document(indexName = "ec", type = "product", replicas = 0, shards = 5)
//type (類型)可以理解為表名
@Data
public class Product implements Serializable {
/**
* Description: @ld 注解必須是 springframework 包下的
org.springframework.data.annotation.Id
/
private Long id;
@Field(type = FieldType.Text, analyzer ="ik_max_word")//ik_max_word 使用IK分詞器
private String name;
@Field(type = FieldType.Keyword)//在存儲數(shù)據(jù)時,不會對category進(jìn)行分詞
//分類
private String category;
//價格
@Field(type = FieldType.Double)
private Double price;
@Field(index = false, type = FieldType.Keyword)// index=false,表示不建立索引
//圖片地址
private String images;
private String body;
}
//省略
代碼解釋如下。
- @ld注解:作用于成員變量,標(biāo)記一個字段作為id主鍵。
- @Field注解:作用于成員變量,標(biāo)記為文檔的字段,需要指定字段映射屬性type。
- index:是否索引,布爾類型,默認(rèn)為true。
- store:是否存儲,布爾類型,默認(rèn)為false。
- analyzer:分詞器名稱,這里的ik_max_word即使用IK分詞器。
(2)創(chuàng)建數(shù)據(jù)操作接口。
????????繼承ElasticsearchRepository即可創(chuàng)建數(shù)據(jù)操作接口,這樣不用寫方法,就具備了
Elasticsearch文檔的增加、刪除、修改和查詢功能。見以下代碼:
package com.example.demo.repository;
//省略
@Component
public interface ProductRepository extends ElasticsearchRepository<Product,Long> {
Product findByld(long id);
Product findByName(String name);
}
13.3.3實現(xiàn)增加、刪除、修改和查詢文檔的功能
在測試類中,實現(xiàn)對Elasticsearch文檔進(jìn)行增加、刪除、修改和查詢的功能,見以下代碼:
//省略
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProductControllerTest {
//每頁數(shù)量
private Integer PAGESIZE=10;
@Autowired
private ProductRepository productRepository;
@Test
public void save() {
long id= System.currentTimeMillis();
Product product = new Product(id,
"紅富士","水菓", 7.99, "/img/p1.jpg", "這是一個測試商品");
productRepository.save(product);
System.out.println(product.getld());
}
@Test
public void getProduct() {
Product product = productRepository.findByName("紅富士");
System.out.println(product.getld());
}
@Test
public void update() {
long id=1557032203515L;
Product product = new Product(id,
"金帥水果", 7.99, "/img/p1.jpg", "金帥也和紅富士一樣,非常好吃,脆脆的");
productRepository.save(product);
}
@Test
public void getProductByld() {
Product product = productRepository.findByld(1557032203515L);
System.out.println(product.getName()+product.getBody());
}
@Test
public void delete() {
long id=1557032203515L;
productRepository.deleteByld(id);
}
@Test
public void getAll() {
lterable<Product> list = productRepository.findAll(Sort.by("id").ascending());
for (Product product: list) {
System.out.println(product);
}
}
}
請根據(jù)自己測試“save”方法返的id值進(jìn)行測試。這里的id值(1557032203515L ) 是筆者本機上得到的,讀者不能用這個id值進(jìn)行測試。
啟動項目,運行測試getAII,控制臺返回如下值:
Product(id=1557031659306, name=紅富士,category二水果 price=7.99, images=/img/p1.jpg, body二這是一個 測試商品) Product(id=1557032088459, name二金帥,category=水果,price二7.99, images=/img/p1.jpg, body=金帥也和紅 富士一樣,非常好吃,脆脆的) Product(id=1557032203515, name=紅富士,category=水果 price=7.99, images=/img/p1 .jpg, body二這是一個 測試商品) Product(id=1557034189287, name=紅富士,category二水果,price=7.99, images=/img/p1.jpg, body=這是一個 測試商品)
13.4 Elasticsearch?查詢
????????可以根據(jù)Spring Data提供的方法名稱,實現(xiàn)自己想自定義的查詢功能:無須寫實現(xiàn)類,只要?繼承ElasticsearchRepository接口即可。
如“findByTitle”表示根據(jù)“title”進(jìn)行查詢,具體方?法見表
關(guān)鍵詞 |
例 子 |
And |
findByNameAndPrice |
Or |
findByNameOrPrice |
Is |
findByName |
Not |
findByNameNot |
Between |
findByPriceBetween |
關(guān)鍵詞 |
例 子 |
LessThanEqual |
findByPriceLessThan |
GreaterThanEqual |
findByPriceGreaterThan |
Before |
findByPriceBefore |
After |
findByPriceAfter |
Like |
findByNameLike |
StartingWith |
findByNameStartingWith |
EndingWith |
findByNameEndingWith |
Contains/Containing |
findByNameContaining |
In |
findByNameln(Collection<String>names) |
Notln |
findByNameNotln(Collection<String>names) |
Near |
findByStoreNear |
TRUE |
findByAvailableT rue |
FALSE |
findByAvailableFalse |
OrderBy |
findByAvailableTrueOrderByNameDesc |
如果要查詢價格在7?8元的商品,貝!J可以在接口類加上“List〈P「oduct> findByFYiceBetween (double pricel, double price2); ” 方法,見以下代碼: | |
@Component public interface ProductRepository extends ElasticsearchRepository<Product,Long> { Product findByld(long id); ????????Product findByName(String name); ????????List<Product> findByPriceBetween(double pricel, double price2); } |
????????然后,在測試類中直接使用自定義的“findByP「iceBetween”方法查詢出數(shù)據(jù),見以下代碼:
@Test
public void queryByPriceBetween() {
Iterable<Product> list = productRepository.findByPriceBetween(7.00, 8.00);
for (Product product: list) {
System.out.println(product);
}
}
其他的使用方法以此類推。
13.4.2精準(zhǔn)查詢
1、單參數(shù) termQuery
用法見以下代碼:
QueryBuilder queryBuilder = QueryBuilders.termQuery("字段名","查詢值");
它是不分詞查詢。因為不分詞,所以漢字只能查詢一個字,而多字母的英語單詞算一個字。
具體實現(xiàn)見以下代碼:
@Test
public void termQuery(){
//構(gòu)建查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//查詢詞,只能查詢一個漢字,或一個英文單詞 nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termQuery("name","富"));
//搜索,獲取結(jié)果
Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
//總條數(shù)
for (Product product: products) {
System.out.println(product);
}
}
2、多參數(shù)? ?termsQuery
????????terms可以提供n個查詢的參數(shù)對一個字段進(jìn)行查詢,用法見以下代碼。注意,這里是term的復(fù)數(shù)形式terms?
QueryBuilder queryBuilder=QueryBuilders.termsQuery("字段名","查詢值", "查詢值")
具體實現(xiàn)見以下代碼:
@Test
//多參數(shù) termsQuery
public void termsQuery() {
//構(gòu)建查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder(); //查詢詞,只能查詢一個漢字或一個英文單詞
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termsQuery("name",'富',"帥"));
//搜索,獲取結(jié)果
Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
//總條數(shù)
for (Product product: products) {
System.out.println(product);
}
}
3、分詞查詢? ?matchQuery
分詞查詢采用默認(rèn)的分詞器,用法見以下代碼:
QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("字段名","查詢值");
具體實現(xiàn)見以下代碼:
@Test
//分詞查詢采用默認(rèn)的分詞器
public void matchQuery() {
//查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//查詢詞
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.matchQuery("name","紅士"));
//搜索,獲取結(jié)果
Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
for (Product product: products) {
System.out.println(product);
}
}
4、 多字段查詢 multiMatchQuery
多字段查詢采用multiMatchQuery方法,用法見以下代碼:
QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("查詢值","字段名","字段名","字段名");
具體實現(xiàn)見以下代碼:
@Test
//多字段查詢
public void multiMatchQuery() {
//構(gòu)建查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery("紅富士金帥","name", "body"));
//搜索,獲取結(jié)果
Page<Product> products = productRepository.search(nativeSearchQueryBuilder.build());
//總條數(shù)
for (Product product: products) {
System.out.println(product);
}
}
13.4.3模糊查詢
常見的模糊查詢的方法有4種。
1.左右模糊
用法見以下代碼:
QiteryBuilders.queryStringQuery("查詢值").field("字段名");
具體實現(xiàn)見以下代碼:
@Test
public void queryStringQuery() {
//查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//左右模糊
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.queryStringQuery("我覚得紅富士好吃").field("name"));
//搜索,獲取結(jié)果
Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
for (Product product: products) {
System.out.println(product);
}
}
2、前綴查詢? prefixQuery
????????如果字段沒分詞,則匹配整個字段前綴,用法見以下代碼:
QueryBuilders.prefixQuery("字段名","查詢值");
具體實現(xiàn)見以下代碼:
@Test
public void prefixQuery() {
//查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//左右模糊
ativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.prefixQuery("name","士"));
//搜索,獲取結(jié)果
Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
for (Product product: products) {
System.out.println(product);
}
}
3、通配符查詢 wildcard query
????????使用通配符方式進(jìn)行查詢,支持通配符“*”和“?”?!?”代表任意字符串,“?”代表任意一?個字符。
(1) 使用通配符 “*”。
????????通配符“*”可以匹配多個值,用法見以下代碼:
QueryBuilders.wildcardQuery("字段名", "查詢值*");
具體實現(xiàn)見以下代碼:
@Test
public void wildcardQuery() {
//查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.wildcardQuery("name","金*"));
//搜索,獲取結(jié)果
Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
for (Product product: products) {
System.out.println(product);
}
}
(2)使用通配符“? ”。
通配符“?”匹配一個詞,用法見以下代碼:
QueryBuilders.wildcardQuery("字段名", "查?值");
具體實現(xiàn)見以下代碼:
@Test
//通配符查詢
public void wildcardQuery2() {
//查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.wildcardQuery("name","金?"));
//搜索,獲取結(jié)果
Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
for (Product product: products) {
System.out.println(product);
}
)
4.分詞模糊查詢??fuzzy query
????????分詞模糊查詢即匹配截取字符串為字前或后加1個詞的文檔,這里通過增加fuzziness (模糊) 屬性來查詢,fuzziness的含義是檢索的term前后增加或減少n個詞的匹配查詢。
用法見以下代碼:
QueryBuilders.fuzzyQuery("字段名","查詢值").fuzziness(Fuzziness.ONE);
具體實現(xiàn)見以下代碼:
@Test
public void fuzzyQuery(){
//查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder
= new NativeSearchQueryBuilder();
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.fuzzyQuery("name","士")
.fuzziness(Fuzziness.ONE));
//搜索,獲取結(jié)果
Page<Product> products= productRepository.search (nativeSearchQueryBuilderQueryBuilder.build());
for (Product product: products){
System.out.println(product);
}
}
5.相似內(nèi)容推薦
????????相似內(nèi)容的推薦是給定一篇文檔信息,然后向用戶推薦與該文檔相似的文檔。通過?Elasticsearch的More like this查詢接口,可以非常方便地實現(xiàn)基于內(nèi)容的推薦,
用法見以下代碼:
?QueryBuilders.moreLikeThisQuery(new String[] {"字段名"}).addLikeText("查詢值");
如果不指定字段名,則默認(rèn)全部,常用在相似內(nèi)容的推薦上。
13.4.4范圍查詢
將文檔與具有一定范圍內(nèi)字詞的字段進(jìn)行匹配,用法如下。
- 閉區(qū)間查詢:QueryBuilder queryBuilder?=?QueryBuilders.rangeQuery("字段名").from(“值 1").to("值?2");
- 開區(qū)間查詢:QueryBuilder queryBuilder?=?QueryBuilders.rangeQuery(”字段名”).from("值 2").includeUpper(false).includeLower(false);//默認(rèn)是true,也就是包含
- 大于:QueryBuilder queryBuilder?=?QueryBuilders.rangeQuery("字段名").gt("查詢值”);
- 大于或等于:QueryBuilder queryBuilder = QueryBuilders.rangeQuery("字段名").gte(" 查詢值,
- 小于:QueryBuilderqueryBuilder =?QueryBuilders.rangeQuery(”字段名”).lt(”查詢值”);
- 小于或等于:QueryBuilder queryBuilder = QueryBuilders.rangeQuery("字段名").lte("?查詢值”);
13.4.5組合查詢
組合查詢是可以設(shè)置多個條件的查詢方式,用來組合多個查詢,有4種方式。
- must:代表文檔必須完全匹配條件,相當(dāng)于and,會參與計算分值。
- mustnot:代表必須不滿足匹配條件。
- filter:代表返回的文檔必須滿足filter條件,但不會參與計算分值。
- should:代表返回的文檔可能滿足條件,也可能不滿足條件,有多個should時滿足任何一?個就可以,相當(dāng)于or,可以通過minimum_should_match設(shè)置至少滿足幾個。
13.4.6分頁查詢
使用NativeSearchQueryBuilder實現(xiàn)分頁查詢,用法見以下代碼:
@Test
public void termQuery() {
//分頁
int page = 0;
//每頁文檔數(shù)
int size=5;
//構(gòu)建查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//查詢詞,只能查詢一個漢字,或一個英文單詞
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termQuery("name","富"));
//搜索,獲取結(jié)果 nativeSearchQueryBuilderQueryBuilder.withPageable(PageRequest.of(page, size));
Page<Product> products =
productRepository.search(nativeSearchQueryBuilderQueryBuilder.buildO);
//總條數(shù)
for (Product product: products) {
System.out.println(product);
}
}
如果要逬行排序,只要在分頁查詢上構(gòu)建withSort參數(shù)即可,用法見以下代碼:
@Test
//分頁查詢+排序
public void searchByPageAndSort() {
//分頁:
int page = 0;
//每頁文檔數(shù)
int size = 5;
//構(gòu)建查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//查詢詞,只能查詢一個漢字,或一個英文單詞
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termQuery("name","富"));
//搜索,獲取結(jié)果
nativeSearchQueryBuilderQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
nativeSearchQueryBuilderQueryBuilder.withPageable(PageRequest.of(page, size));
Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
//總條數(shù)
for (Product product: products) {
System.out.println(product);
}
}
13.4.7 聚合查詢
????????聚合(aggregation )是Elasticsearch的一個強大功能,可以極其方便地實現(xiàn)對數(shù)據(jù)的統(tǒng)計、?分析工作。搜索是查找某些具體的文檔,聚合就是對這些搜索到的文檔進(jìn)行統(tǒng)計,可以聚合出更加細(xì)致的數(shù)據(jù)。它有兩個重要概念。
- Bucket (桶/集合):滿足特定條件的文檔的集合,即分組。
- Metric (指標(biāo)/度量):對桶內(nèi)的文檔進(jìn)行統(tǒng)計計算(最小值、最大值),簡單理解就是進(jìn)行 、A-A-運算。
聚合由AggregationBuilders類來構(gòu)建,它提供的靜態(tài)方法見表13-3。
功 能 |
語 法 |
統(tǒng)計數(shù)量 |
ValueCountBuilder vcb= AggregationBuilders.count("count id").field("id"); |
去重統(tǒng)計數(shù)量 |
CardinalityBuilder cb= AggregationBuilders.cardinality("distinct count id").field("id"); |
聚合過濾 |
FilterAggregationBuilder fab二 AggregationBuilders.filter("id_filter").filter(QueryBuilders.query StringQuery("id:1")); |
按字段分組 |
TermsBuilder tb= AggregationBuilders.terms("group name").field("name"); |
求和 |
SumBuilder sumBuilder= AggregationBuilders.sum("suin price").field("price"); |
求平均值 |
AvgBuilder ab= AggregationBuilders.avg("avg price").field("price"); |
求最大值 |
MaxBuilder mb= AggregationBuilders.max("max price").field("price"); |
求最小值 |
MinBuilder min 二 AggregationBuilders.min("min price").field("price"); |
按日期間隔分組 |
DateHistogramBuilder dhb二 AggregationBuilders.dateHistogram("dhb dt").field("date"); |
獲取聚合結(jié)果 |
TopHitsBuilder thb二 AggregationBuilders.topHits("top hit result"); |
嵌套的聚合 |
NestedBuilder nb= AggregationBuilders.nested("negsted quests").path("quests"); |
反轉(zhuǎn)嵌套 |
AggregationBuilders.reverseNested("res negsted").path("kps "); |
具體用法見以下代碼:
//測試桶
public String searchBybucket() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
queryBuilder.withSourceFilter(new FetchSourceFilterfnew String[]{""}, null));
//指定索引的類型,只先從各分片中查詢匹配的文檔,再重新排序和排名,取前size個文檔
queryBuilder.withSearchType(SearchType.QUERY_THEN_FETCH);
//指定要查詢的索引庫的名稱和類型,其實就是文檔?Document中設(shè)置的indedName和type
queryBuilder.withlndices("goods").withTypes("goods");
//添加一個新的聚合,聚合類型為terms,聚合名稱為brands,聚合字段為brand
queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));
//查詢,需要把結(jié)果強轉(zhuǎn)為AggregatedPage類型,AggregatedPage:聚合查詢的結(jié)果類。它是Page<T> 的子接口
AggregatedPage<Goods> aggPage = (AggregatedPage<Goods>)
goodsRepository.search(queryBuilder.build());
//從結(jié)果中取出名為brands的聚合解析
//強轉(zhuǎn)為St「ingTerm類型
StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
//獲取桶
List<StringTerms.Bucket> buckets = agg.getBuckets();
//遍歷
for (StringTerms.Bucket bucket: buckets) {
//獲取桶中的key
System.out.println(bucket.getKeyAsString());
//獲取桶中的文檔數(shù)量
System.out.println(bucket.getDocCount());
}
return buckets;
}
還可以嵌套聚合,在聚合AggregationBuilders中使用subAggregation,用法見以下代碼:
queryBuilder.addAggregation(
AggregationBuilders.terms("brands").field("brand")
.subAggregation(AggregationBuilders.avg("price_avg").field("price")) //在品牌聚合桶內(nèi)進(jìn)行嵌套聚合);
這里一定要注意Spring Boot和Elasticsearch的版本是否對應(yīng)。
13.5?實例57:實現(xiàn)產(chǎn)品搜索引擎
????????本實例通過實現(xiàn)一個產(chǎn)品信息的搜索引擎來幫助讀者理解本章所講的知識點及具體使用。創(chuàng)建?實體和接口的方法在前面小節(jié)中已經(jīng)講解過,本節(jié)只講解創(chuàng)建控制器實現(xiàn)搜索API和搜索的視圖展?示方法。
本實例的源代碼可以在,713/ElasticsearchProductSearch”目錄下找到。
(1 )創(chuàng)建搜索控制器,用于構(gòu)建搜索框架,見以下代碼:
〃省略
?Controller
public class Searchcontroller {
@Autowired
private ProductRepository productRepository;
@GetMapping("search")
public ModelAndView searchByPageAndSort(lnteger start,String key) (
//分頁:
if (start == null) {
start = 0;
}
int size=2;〃每頁文檔數(shù)
//構(gòu)建查詢條件
NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
//nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.matchQuery("name", key));
nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(key,"name",
"body"));
/搜索,獲取結(jié)果
nativeSearchQueryBuilderQueryBuilder.withSort(SortBuilders.fieldSort("id")
.order(SortOrder.DESC));
nativeSearchQueryBuilderQueryBuilder.withPageable(PageRequest.of(start, size));
Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
//總條數(shù)
for (Product product: products) {
System.out.println(product);
}
ModelAndView mav = new ModelAndView("search");
mav.addObject("page", products);
mav.addObject("keys", key);
return mav;
}
}
(2)創(chuàng)建顯示視圖。
創(chuàng)建用于展示數(shù)據(jù)的前端頁面,具體見以下代碼:
<!—省略...一〉
<body>
<div class ="contain?!敢?fluid"〉
<div><span >搜索詞:</span><span th:text="$(keys)">mav</span></div>
<div class="row-fluid">
<div class="span12">
<div th:each="item : $(page.content}">
<span th:text=,,$(item.id}">id</span>
<span th:text="$(item.name}">name</span> <span th:text="$(item.body}">body</span></div>
</div>
<div>
<a th:href=,,@{/search(key=${keys},start=O,)}n>[MZS]</a>
<a th:if="$(not page.isFirst())" th:href="@(/search(key=${keys},start=${page.number-1 })}">[± 頁]<7a>
<a th:if="$(not page.isLast())" th:href="@(/search(key=${keys),start=${page.number+1})}">[T 頁]</a>
<a th:href="@(/search(key=${keys},start=$(page.totalPages-1})}">[^^]</h>
</div>
</div>
</div>
</body>
</html>
13.6 Solr—搜索應(yīng)用服務(wù)器
13.6.1?了解?Solr
Solr是一個獨立的企業(yè)級搜索應(yīng)用服務(wù)器,對外提供API接口。用戶可以通過HTTP請求向?搜索引擎服務(wù)器提交一定格式的XML文件,生成索引;也可以通過HTTP GET操作提出查找請求, 并得到XML格式的返回結(jié)果。Solr現(xiàn)在支持多種返回結(jié)果。
13.6.2安裝配置Solr
1、Solr安裝
(1 )訪問鏡像網(wǎng)站,下載Solr壓縮包。
(2)在下載完成后解壓文件,在"cmd”控制臺進(jìn)入"solr/bin”目錄下,輸入“solr start” 命令啟動Sol「o
如果出現(xiàn)以下提示,則表示成功啟動。
INFO - 2019-05-09 10:30:09.043; org.apache.solr.util.configuration.SSLCredentialProviderFactory; Processing SSL Credential Provider chain: env;sysprop Waiting up to 30 to see Solr running on port 8983 Started Solr server on port 8983. Happy searching!
(3)訪問?uhttp://localhost:8983/solr/#/n,就可以看到已經(jīng)啟動了。
常用命令如下。
- 停止:Msolr stop -p 8983 ”?或?“sole stop - oil”。
- 查看運行狀態(tài):sol「status。
2、Solr配置
(1 )進(jìn)入Solr的安裝目錄下的server/solr/,創(chuàng)建一個名字為new_core的文件夾。
(2)將?conf?目錄(在安裝目錄/servei7soli7configsets/sample_techproducts_configs?下) 復(fù)制到new_core目錄下。
(3 )訪問?“http://localhost:8983/sol「/#/”。
單擊導(dǎo)航欄的“Core Admin”,在彈出窗口中單擊“Add Core”命令,彈出如圖13-8所示?的對話框,輸入名字和目錄名,再單擊“AddCore”按鈕,完成創(chuàng)建。
13.6.3?整合?Spring Boot?和?Solr
(1)?添加依賴,見以下代碼:
<dependency>
<groupld>org.springframework.boot</groupld>
<artifactld>spring-boot-starter-data-solr</artifactld>
</dependency>
(2)?寫入Solr配置。
在application.properties文件中進(jìn)行Sol「配置,寫入下面配置信息即可。
spring.data.solr.host=http://localhost:8983/solr/new_core
13.7?實例58:在Sping Boot中集成Solr,實現(xiàn)數(shù)據(jù)的增加、刪除、?修改和查詢
本實例在Sol「中實現(xiàn)數(shù)據(jù)的增加、刪除、修改和查詢。
本實例的源代碼可以在“/13/Solr”目錄下找到。
13.7.1?創(chuàng)建?User 類
User類必須繼承可序列化接口,見以下代碼:
@Data
public class User implements Serializable {
@Field("id")
//使用這個注釋,里面的名字是根據(jù)Solr數(shù)據(jù)庫中的配置來決定的
private String id;
@Field("name")
private String name;
}
13.7.2測試增加、刪除、修改和查詢功能
(1)?測試增加功能。
構(gòu)造一篇文檔,實例化一個對象,向Solr中添加數(shù)據(jù),見以下代碼:
@Test
public void addUser() throws lOException, SolrServerException {
User user = new User();
user.setld("8888888");
user.setName("龍知然");
solrClient.addBean(user);
solrClient.commit();
}
(2)?測試增加功能,根據(jù)id查詢剛剛添加的內(nèi)容,見以下代碼:
@Test
public void getByldFromSolr() throws lOException, SolrServerException {
//根據(jù)id查詢內(nèi)容
String id = "8888888";
SolrDocument solrDocument = solrClient.getByld(id);
//獲取 filedName
Collection<String> fieldNames = solrDocument.getFieldNames();
//獲取file名和內(nèi)容
Map<String, Object> fieldValueMap = solrDocument.getFieldValueMap();
List<SolrDocument> childDocuments = solrDocument.getChildDocuments();
String results = solrDocument.toString();
System.out.println(results);
}
運行測試,控制臺輸出如下結(jié)果:
SolrDocument{id=8888888, name=龍知然,version = 1633023077954617344}
(3)?測試修改功能,根據(jù)id修改內(nèi)容,見以下代碼
@RequestMapping("/updateUser")
public void updateUser() throws lOException, SolrServerException {
User user = new User();
user.setld("8888888");
user.setName("知然");
solrClient.addBean(user);
soIrClient.commit();
}
修改之后的值如下:
Solr Document{id=8888888, name=知然,_version_=1633023690698391552}
可以看到,內(nèi)容已經(jīng)變化,所謂Sol「的更新操作,就是對相同id的文檔重新添加一次。修改之?后,Version變得不一樣了。
(4)?測試刪除功能,根據(jù)id刪除內(nèi)容,見以下代碼:
@Test
public void delByld() throws lOException, SolrServerException {
//根據(jù)id刪除信息
UpdateResponse updateResponse = solrClient.deleteByld("8888888");
//執(zhí)行的時間
long elapsedTime = updateResponse.getElapsedTime();
int qTime = updateResponse.getQTime();
//請求地址
String requestUrl = updateResponse.getRequestUrl();
//請求的結(jié)果{responseHeader=(status=0,QTime=2)}
NamedList<Object> response = updateResponse.getResponse();
//請求結(jié)果的頭(status=0,QTime=2)
NamedList responseHeader = updateResponse.getResponseHeader();
//請求的狀態(tài)0
solrClient.commit();
int status = updateResponse.getStatus();
//成功,則返回0,如果沒有文檔被刪除,也會返回0,代表根本沒有
}
刪除全部可以用以下代碼:
solrClient.deleteByQuery("*:*");
(5)?實現(xiàn)文檔高亮顯示,見以下代碼:
@Test
public void queryAII() throws lOException, SolrServerException {
SoIrQuery soIrQuery = new SoIrQuery();
〃設(shè)置默認(rèn)搜索域
solrQuery.setQuery("*:*");
soIrQuery.set("q", "知然");
solrQuery.add("q", "name:然");
〃設(shè)置返回結(jié)果的排序規(guī)則
soIrQuery.setSort("id", SolrQuery.ORDER.asc);
〃設(shè)置查詢的條數(shù)
solrQuery.setRows(50);
〃設(shè)置查詢的開始
solrQuery.setStart(0);
//設(shè)置分頁參數(shù)
solrQuery.setStart(0);
solrQuery.setRows(20);
〃設(shè)置高亮
solrQuery.setHighlight(true);
〃設(shè)置高亮的字段
solrQuery.addHighlightField("name");
〃設(shè)置高亮的樣式
solrQuery-setHighlightSimplePre("<font color='red'>");
solrQuery.setHighlightSimplePost("</font>");
System.out.println(solrQuery);
QueryResponse response = solrClient.query(solrQuery);
//返回高亮顯示結(jié)果
Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();
//response.getResults();查詢返回的結(jié)果
SoIrDocumentList documentList = response.getResults();
long numFound = documentList.getNumFound();
System.out.println("共查詢到的文檔數(shù)量:"+ numFound);
for (SoIrDocument soIrDocument: documentList) {
System.out.println(solrDocument);
System.out.println(solrDocument.get("name"));
}
System.out.println(highlighting);
}
運行上面代碼,在控制臺中輸出如下結(jié)果:
q=知然&q=name:然&sort=id+asc&rows=20&start=0&hl=true&hl.fl=name&hl.simple.pre=<font+color='red,> &hl.simple.post=</font> 總共查詢到的文檔數(shù)量:3 SolrDocument(id=ld3cbb8a541 b45759b 1 a59a86ddd0f9b, name=龍知然 4, _version_=l 633022994022400000) 蘢知然4 SolrDocument {id=3 ddb0995b0c04fc0be3 c34612c33992c, name=龍知然 4, _version_=1633022357082734594} 蘢知然4 SolrDocument{id=bb37d6fI96ad43bc8654f29f2e9f389f, name=龍知然 4, sex=男,address=武漢 4, _version_=1626411498284777473) 蘢知然j (1 d3cbb8a541 b45759b 1 a59a86ddd0f9b= {name=[^.<fbnt coloi^'red'>^p</fbntxfbnt colorTedA 然 </fbnt>4]), 3ddb0995b0c04fc0be3c34612c33992c= {name=[^.<fbnt coloi=Yed^^p^fbntXfbnt coloi='red^^^/fbnW]), bb37d6f!96ad43bc8654f29f2e9f389仁{name=[龍 vfbnt coloi^'red^^p^fontxfbnt color='red' >然 </fbnt>4]}}
從〈font color='red‘>然</font>中可以看出,查詢結(jié)果已經(jīng)高亮化了,對查詢關(guān)鍵詞輸岀紅色?字體。
13.8?對比?Elasticsearch?和?Solr
1. Elasticsearch和Solr的市場關(guān)注度
2. Elasticsearch?和?Solr?的優(yōu)缺點
(1 ) Solr的優(yōu)點。
Solr有一個更大' 更成熟的用戶' 開發(fā)和貢獻(xiàn)者社區(qū)。
- 支持添加多種格式的索引,如:HTML. PDF、微軟Office系列軟件格式,以及JSON、?XML、CSV等純文本格式。
- 比較成熟、穩(wěn)定。
- 搜索速度更快(不建索引時)。
- Soli?利用Zookeeper進(jìn)行分布式管理,而Elasticsearch自身帶有分布式協(xié)調(diào)管理功能。?如果項目本身使用了?Zookeeper,那Solr可能是最好選擇。有時缺點在特點場景下可能會?變成優(yōu)點。
- 如果項目后期升級,要朝著Hadoop這塊發(fā)展,當(dāng)數(shù)據(jù)量較大時,用Hadoop處理數(shù)據(jù),?Solr可以很方便地與Hadoop結(jié)合。
(2)?Elasticsearch 的優(yōu)點。
- Elasticsearch本身是分布式、分發(fā)實時的,不需要其他組件。
- Elasticsearch完全支持Apache Lucene的接近實時的搜索。
- 它處理多用戶不需要特殊配置,而Solr則需要更多的高級設(shè)置。
- Elasticsearch采用Gateway的概念,備份更加簡單。各節(jié)點組成對等的網(wǎng)絡(luò)結(jié)構(gòu),某節(jié)?點出現(xiàn)故障會自動分配其他節(jié)點代替其進(jìn)行工作。
(3)?Solr的缺點。?
- 建立索引時,搜索效率下降,實時索引搜索效率不高。
- 實時搜索應(yīng)用效率明顯低于Elasticsearch。
(4 ) Elasticsearch 的缺點。文章來源:http://www.zghlxwxcb.cn/news/detail-407408.html
- 沒有Solr的生態(tài)系統(tǒng)發(fā)達(dá)。
- 僅支持JSON文件格式。
- 本身更注重核心功能,高級功能多由第三方插件提供。
總結(jié):Solr是傳統(tǒng)搜索應(yīng)用的有力解決方案,但Elasticsearch更適用于新興的實時搜索應(yīng)用。文章來源地址http://www.zghlxwxcb.cn/news/detail-407408.html
到了這里,關(guān)于《Spring Boot 實戰(zhàn)派》--13.集成NoSQL數(shù)據(jù)庫,實現(xiàn)Elasticsearch和Solr搜索引擎的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!