全文檢索Elasticsearch研究
基于虛擬機服務器自主部署ELK服務
學習目標
了解Elasticsearch的應用場景 學習基于服務器部署ELK服務 掌握索引維護的方法 掌握索引維護的方法 掌味訓本的搜索API的使用方法
約束
需要提前掌握Lucene的索引方法、搜索方法
ELK的介紹和安裝
1.簡介
? Elasticsearch是一個基于Lucene的搜索服務器,它提供了一個分布式多用戶能力的全文搜索引擎,基于Restful web介面,Elasticsearch是用java開發的,是當前流行的企業級搜索引擎,能到達到實時搜索,穩定、可靠、快速、安裝使用方便,
? 我們建立一個網站或應用程式,并要添加搜索功能,如果搜索的數量非常多,而且分類繁雜,如果使用傳統的資料庫想要完成搜索作業的創建失非常困難的,我們希望搜索解決方案要運行速度快,我們希望有一個零配置和完全免費的搜索模式,能夠簡單的使用JSON通過HTTP來索引資料,而搜索服務器始終可用,并且服務器可以自如擴展,我們一般都會使用全文檢索技術,如solr、Elasticsearch等,
2.突出優點
擴展性好,可部署上百臺服務器集群,處理PB級資料 近實時的去索引資料、搜索資料
3.原理與應用
3.1 索引結構
下圖是ElasticSearch的索引結構,下邊==黑色部分是物理結構==,上邊==黃色部分是邏輯結構==,邏輯結構可以更好的描述ElasticSearch的作業原理及去使用物理結構中的索引檔案,

邏輯結構部分是一個倒排索引表:
將要搜索的檔案內容分詞,所有不重復的詞做成分詞串列, 將搜索的檔案最終以Document方式存盤起來, 每個詞和document都有關聯,
3.2 RESTFUL應用方法
ElasticSearch提供RESTFUL Api介面進行索引、搜索、并且支持多種客戶端,
下圖是ElasticSearch在專案中的應用方式:

用戶在前端搜索關鍵字 專案前端通過http方式請求專案服務端 專案服務端通過http RESTful方式請求ES集群進行搜索 ES集群從索引庫檢索資料
4.ElasticaSearc安裝
4.1 安裝配置
安裝ElasticaSearc7.9.0 該版本要求至少jdk1.8以上 解壓elasticsearch-7.9.0-linux-x86_64.tar.gz bin:腳本目錄,包括:啟動、停止等可執行腳本 config:組態檔目錄 data:索引目錄,存放索引檔案的地方 modules:模板目錄,包括了es的功能模塊 plugins:插件目錄,es支持插件機制
4.2 組態檔
ES組態檔的地址根據安裝形式的不同而不同:
使用zip、tar安裝,組態檔的地址在安裝目錄的config下 使用RPM安裝,組態檔在/etc/elasticsearch下 使用MSI安裝,組態檔的地址在安裝目錄的config下,并且會自動將config目錄地址寫入環境變數ES_PATH_CONF
4.3 安裝
為了模擬真實場景,我們將在linux系統下安裝Elasticsearch
4.3.1 新建一個用戶gavin
處于安全考慮,Elasticsearch默認不允許以root賬號運行
創建用戶:
useradd gavin
設定密碼:
passwd gavin
切換用戶:
su gavin
洗掉用戶:
userdel -r gavin
普通用戶增加sudo命令的權限:
vim /etc/sudoers
gavin ALL=(ALL) ALL
改變目錄及其目錄下所有檔案的所有者為當前普通用戶:
chown -R yourname dirname

4.3.3 解壓縮
tar -zxvf elasticsearch-7.9.0-linux-x86_64.tar.gz
4.3.4 目錄重命名
mv elasticsearch-7.9.0 elasticsearch
4.3.5 修改組態檔
進入config目錄,修改elasticsearch.yml和jvm.options
jvm.options
默認配置:
-Xms1g
-Xmx1g
記憶體占用太多,設定為不超過物理記憶體的一半:
-Xms512m
-Xmx512m
elasticsearch.yml
修改資料和日志目錄
# 資料目錄位置
path.data: /home/gavin/elasticsearch/data
# 日志目錄位置
path.logs: /home/gavin/elasticsearch/logs
elasticsearch的安裝目錄默認只有logs目錄,沒有data目錄,需要手動創建:
mkdir data

修改系結的ip:
# 系結0.0.0.0 允許任何ip來訪問,默認只允許本機訪問
network.host: 0.0.0.0
目前我們是學習單機安裝,如果要做集群,只需要在這個組態檔中添加節點資訊即可,
| 屬性名 | 說明 |
|---|---|
| cluster.name | 配置elasticsearch的集群名稱,默認是elasticsearch,建議修改成一個有意義的名稱, |
| node.name | 節點名,es會默認隨機指定一個名字,建議指定一個有意義的名稱,方便管理 |
| path.conf | 設定組態檔的存盤路徑,tar或zip包安裝默認在es根目錄下的config檔案夾,rpm安裝默認在/etc/ elasticsearch |
| path.data | 設定索引資料的存盤路徑,默認是es根目錄下的data檔案夾,可以設定多個存盤路徑,用逗號隔開 |
| path.logs | 設定日志檔案的存盤路徑,默認是es根目錄下的logs檔案夾 |
| path.plugins | 設定插件的存放路徑,默認是es根目錄下的plugins檔案夾 |
| bootstrap.memory_lock | 設定為true可以鎖住ES使用的記憶體,避免記憶體進行swap |
| network.host | 設定bind_host和publish_host,設定為0.0.0.0允許外網訪問 |
| http.port | 設定對外服務的http埠,默認為9200, |
| transport.tcp.port | 集群結點之間通信埠 |
| discovery.zen.ping.timeout | 設定ES自動發現節點連接超時的時間,默認為3秒,如果網路延遲高可設定大些 |
| discovery.zen.minimum_master_nodes | 主結點數量的最少值 ,此值的公式為:(master_eligible_nodes / 2) + 1 ,比如:有3個符合要求的主結點,那么這里要設定為2 |
4.4 運行
進入elasticsearch/bin*目錄下,可以看到如下的可執行檔案:

然后輸入運行命令:
./elasticsearch
4.5 啟動報錯
4.5.1 JDK版本過低 并且 不支持 root用戶啟動

解決方案:
1.因為elasticsearch7.9.X內置了jdk,默認是jdk11,但是向下兼容,所以可以不用處理
2.切換到普通用戶進行啟動,此時需要修改檔案目錄下所有檔案的所有者為當前用戶,
chown -R gavin /home/gavin/elasticsearch
4.5.2 集群節點導致啟動報錯

解決:
vim elasticsearch.yml
ip替換host1等,多節點請添加多個ip地址,單節點可寫按默認來
#配置以下三者,最少其一
#[discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes]
cluster.initial_master_nodes: ["node-1"] #這里的node-1為node-name配置的值
啟動成功:

可以看到系結了兩個埠:
9200:客戶端訪問埠 9300:集群節點之間通訊埠
我們在瀏覽器中訪問:http://192.168.15.100:9200/

5.安裝kibana
kibana是一個基于Node.js的Elasticsearch索引庫資料統計工具,可以利用Elasticsearch的聚合功能,生成各種圖表,如柱狀圖、線狀圖、餅圖等,
而且還提供了操作Elasticsearch索引資料的控制臺,并且提供了一定的API提示,非常有利于學習Elasticsearch的語法,
5.1 安裝
因為Kibana是依賴于node
查看是否服務器是否安裝nodejs
[root@centos logs]# node -v
v9.3.0
如果沒有安裝,則安裝步驟如下:
1.可以在下載頁面https://nodejs.org/en/download/中找到下載地址,然后執行指令
wget https://nodejs.org/dist/v9.3.0/node-v9.3.0-linux-x64.tar.xz
2.解壓縮
xz -d node-v9.3.0-linux-x64.tar.xz
tar -xf node-v9.3.0-linux-x64.tar
3.部署bin檔案
根據自己nodejs的實際路徑,依次執行下面命令,建立軟連接:
ln -s /usr/local/software/node/bin/node /usr/bin/node
ln -s /usr/local/software/node/bin/npm /usr/bin/npm
ln -s /usr/local/software/node/bin/npx /usr/bin/npx
4.測驗
node -v
npm -v
npx -v
5.1.1 解壓縮kibana
tar -zxvf kibana-7.9.0-linux-x86_64.tar.gz
5.1.2 重命名安裝包
mv kibana-7.9.0-linux-x86_64 kibana
5.1.3 修改配置
vim /home/gavin/kibana/config/kibana.yml
elasticsearch.hosts: ["http://192.168.15.100:9200"]
5.2 啟動
cd /home/gavin/kibana/bin
./kibana
6.安裝ik分析器
Lucene的IK分詞器早在2012年就已經沒有維護了,現在我們要使用的是在其基礎上維護升級的版本,并且開發為ElasticSearch的繼承插件了,與ElasticSearch一起維護升級了,版本也保持一致,
6.1 解壓縮
unzip elasticsearch-analysis-ik-7.9.0.zip -d /home/gavin/kibana/plugins/ik-analyzer

6.2 重啟elasticsearch

加載IK分詞器插件、
6.3 測驗
在Dev Tools --> console 中輸入下面請求:
POST _analyze
{
"analyzer": "ik_max_word",
"text": "我是中國人"
}

API
Elasticsearch提供了Rest風格的API,即http請求介面,而且也提供了各種語言的客戶端API
檔案地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
1.客戶端API
Elasticsearch支持的客戶端非常多,如:https://www.elastic.co/guide/en/elasticsearch/client/index.html

點開Java Rest Client后,會有兩個:

Java Low Level REST Client:是低級別封裝,提供一些基礎功能,但更靈活 Java High Level REST Client:是在Low Level Rest Client基礎上進行的高級別封裝,功能更豐富和完善,而且API會變得簡單

2.如何學習
2.1 操作索引
Elasticsearch是基于Lucene的全文檢索庫,本質也是存盤資料,很多概念與mysql類似
對比關系:
索引(indices) --------------------------------------------- Databases 資料庫
? 型別(type) ------------------------------------------ Table 資料表
? 檔案(Document) ---------------------------Row 行
? 欄位(field) -----------------------------Columns 列
說明:
| 概念 | 說明 |
|---|---|
| 索引庫(indices) | indices是index的復數,代表許多的索引 |
| 型別(type) | 型別是模擬mysql中的table概念,一個索引庫下可以有不同型別的索引,比如商品索引、訂單索引,其資料格式不同,不過這會導致索引庫混亂,因此未來版本中會移除這個概念 |
| 檔案(document) | 存入索引庫原始的資料,比如每一條商品資訊,就是一個檔案 |
| 欄位(field) | 檔案中的屬性 |
| 映射配置(mappings) | 欄位的資料型別,屬性、是否索引、是否存盤等特性 |
特別說明:
Elasticsearch本身就是分布式的,因此即便你只有一個節點,Elasticsearch默認也會對你的資料進行分片和副本操作,當你向集群添加新資料時,資料也會在新加入的節點中進行平衡
2.2語法
Elasticsearch采用Rest風格API,因此其API就是一次http請求,可以使用任何工具進行發起http請求
2.1.1 索引庫設定
創建索引索引庫設定:
請求方式:PUT
請求路徑:/索引名
請求引數:json格式:
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}settings:索引庫的設定
number_of_shards:分片數量
number_of_replicas:副本數量
測驗
使用postman進行創建索引并對索引庫進行設定測驗

? 2. 使用kibana進行創建索引并對索引庫進行設定測驗

查看索引庫設定
語法:
GET ceshi

或者,使用*來查詢所有索引配置:

洗掉索引庫設定
語法:
DELETE /索引庫名

再次查看

2.1.2 映射配置
索引庫創建好之后就是添加資料,再添加資料之前必須定義映射
映射:
定義檔案的程序,檔案包含哪些欄位,這些欄位是否保存、是否索引、是否分詞等
創建映射欄位
語法:
請求方式是PUT,型別名稱和_mapping可以互換位置
PUT /索引庫名稱/_doc/型別名稱
{
"properties": {
"type": "型別",
"index": true,
"store": true,
"analyzer": "分詞器"
}
}型別:就是前面提過的type的概念,類似于資料庫中的不同表
欄位名:任意填寫,可以指定很多屬性,如:
type:型別,可以是text、long、short、date、integer、object等 index:是否索引,默認為true store:是否存盤,默認是false analyzer:分詞器,這里的 ik_max_word即表示使用ik分詞器
示例:
? 請求:
PUT ceshi/_doc/goods
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"images": {
"type": "keyword",
"index": false
},
"price": {
"type": "float"
}
}
}
特別說明:
傳統ES6創建映射的時候是把上面的**_doc換成_mapping**
ES7這個**_mapping已經移除了,使用_doc**代替
否則會報如下錯誤:

? 回應結果:
{
"_index" : "ceshi",
"_type" : "_doc",
"_id" : "goods",
"_version" : 3,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
查看映射關系
語法:
GET 索引庫名/_mapping示例:
GET ceshi/_mapping結果:

欄位屬性詳解
3.1 type
Elasticsearch中支持的資料型別非常豐富

常用的說明下:
String型別,分為兩種: text:可分詞,不可參與聚合 keyword:不可分詞,資料會作為完整欄位進行匹配,可以參與聚合
Numerical:數值型別,分兩類 基本資料型別:long、integer、short、byte、double、float、half_float 浮點數的高精度型別:scaled_float 需要指定一個精度因子,比如10或100,Elasticsearch會把真實值乘以這個因子以后存盤,取出時再還原,
Date:日期型別 Elasticsearch可以對日期格式化為字串存盤,但是建議我們存盤為毫秒值,存盤為long,節省空間,
3.2 index
index影響欄位的索引情況
true:欄位會被索引,則可以用來進行搜索,默認值是 truefalse:欄位不會被索引,不能用來搜索
特別說明:
index的默認值就是
true,也就是說你不進行任何配置,所有欄位都會被索引,但是有些欄位我們不希望索引的,就需要手動設定index為false
3.3 store
是否將資料額外存盤
在lucene和solr中,我們設定store欄位為false,那么這個欄位在檔案串列中就不會有這個欄位的值,用戶搜索結果中就不會顯示出來,
但在Elasticsearch中,即便設定為false,也可以搜索結果,
原因是Elasticsearch在創建檔案索引庫時,會將檔案中的原始資料備份,保存到一個叫_source的屬性中,而且我們可以通過過濾_source來選擇哪些要顯示,哪些不顯示,
而如果設定store為true,就會在_source以外額外存盤一份資料,多余,因此我們一般都會講store設定為false,事實上,store的默認是就是false,
2.1.3 新增資料
1.隨機生成id
通過POST請求,可以向一個已經存在的索引庫中添加資料
語法:
POST /索引庫名/_doc/型別名
{
"key": "value"
}
示例:
POST /ceshi/_doc/goods
{
"title": "小米手機",
"images": "http://image.leyou.com/12479122.jpg",
"price": 2688.01
}
運行結果:

查看新增資料結果
GET /ceshi/_search
{
"query":{
"match_all":{}
}
}

_source:源檔案資訊,所有資料都在里面 _id:這條檔案的唯一標識,與檔案自己的id沒有關聯
2.自定義id
如果我們在新增資料的時候指定id,可以按如下操作:
語法:
POST /索引庫名/_doc/id
{
"key": "value"
}
示例:
POST /ceshi/_doc/2
{
"title": "小米手機",
"images": "http://image.leyou.com/12479122.jpg",
"price": 2988.02
}

2.1.4 智能判斷
在學習solr時,我們在新增資料時,智能使用提前批配置好映射屬性的欄位,否則就會報錯
不過在Elasticsearch中,可以不需要給索引庫設定任何的映射屬性的欄位,它也可以根據輸入的資料來判斷型別,動態添加資料映射
示例:
POST /ceshi/_doc/3
{
"title":"超米手機",
"images":"http://image.leyou.com/12479122.jpg",
"price":2899.00,
"stock": 200,
"saleable":true
}

我們發現,ceshi索引庫額外增加了stock庫存和saleable是否上架兩個欄位,
查看此時的索引庫映射關系
GET ceshi/_mapping

2.1.5 修改資料
把剛才新增請求的方式改為PUT,就是修改了,不過修改必須指定id
id對應檔案存在,則修改 id對應檔案不存在,則新增
比如,我們把id為3的資料進行修改:
PUT /ceshi/_doc/3
{
"title":"超大米手機",
"images":"http://image.leyou.com/12479122.jpg",
"price":3899.00,
"stock": 100,
"saleable":true
}

2.1.6 洗掉資料
洗掉資料使用DELETE請求方式,同樣,根據id進行洗掉
語法:
DELETE /索引庫名/_doc/id
示例:
DELETE /ceshi/_doc/1
結果:洗掉id為1的索引庫資料
2.1.7 查詢資料
基本查詢 _source過濾 結果過濾 高級查詢 排序
1.基本查詢
基本語法
GET /索引庫名/_search
{
"query": {
"查詢型別": {
"查詢條件": "查詢條件值"
}
}
}
query:表示一個查詢物件,里面可以有不同的查詢屬性
查詢型別:如,
mastch_all、match、term、range等查詢條件:
查詢條件會根據型別的不同,寫法也有差異,后面詳細講解
2.查詢所有(match_all)
GET /ceshi/_search
{
"query": {
"match_all": {}
}
}
query:代表查詢物件 match_all:代表查詢所有

took:查詢花費時間,單位是毫秒 time_out:是否超時 _shards:分片資訊 hits:搜索結果總覽物件
total:搜索到的總條數 max_score:所有結果中檔案得分的最高分 hits:搜索結果的檔案物件陣列,每個元素是一條搜索到的檔案資訊
_index:索引庫 _type:檔案型別 _id:檔案id _score:檔案得分 _source:檔案的源資料
3.匹配查詢(match)
先增加一條資料,便于測驗
PUT /ceshi/_doc/1
{
"title":"小米電視4A",
"images":"http://image.leyou.com/12479122.jpg",
"price":1899.00
}
特別說明:增加資料使用
POST和PUT的區別
PUT:需要指定id,否則會報錯,冪等操作 POST:指定的id存在,則更新資料,不存在要么自定義id,要么隨機生成,非冪等操作
從結果中看到,索引庫中有2部手機,1臺電視

or關系
match型別查詢,會把查詢條件進行分詞,然后進行查詢,多個詞條之間是or的關系
GET /ceshi/_search
{
"query": {
"match": {
"title": "小米電視"
}
}
}

默認情況下,是會通過分詞,使多個詞之間是or的關系,
and關系
某些時候需要精確查找,需要將多個詞關系設定為and,
GET /ceshi/_search
{
"query": {
"match": {
"title": {
"query": "小米電視",
"operator": "and"
}
}
}
}

本例中,只有同時包含
小米和電視的詞條才會被搜索到
or and and 之間
場景:如果用戶給定的條件分詞后有5個查詢詞項,想查找只包含其中4個詞的檔案,該如何處理?將operator運算子設定成and只會將此檔案排除,
有時候這正是我們期望的,但在全文搜索的大多數應用場景下,我們既想包含那些可能相關的檔案,同時又排除那些不太相關的,換句話說,我們想要處于中間某種結果,
match查詢支持minimum_should_match最小匹配引數,這讓我們可以指定匹配的詞項數用來表示一個檔案是否相關,我們可以將其設定為某個具體數字,更常用的做法是將其設定為一個百分數,因為我們無法控制用戶搜索時輸入的單詞數量,
示例:
GET /ceshi/_search
{
"query": {
"match": {
"title": {
"query": "小米曲面電視",
"minimum_should_match": "75%"
}
}
}
}

多欄位查詢(multi_match)
match和multi_match類似,不同的是multi_match可以在多個欄位中查詢
示例:
GET /ceshi/_search
{
"query":{
"multi_match": {
"query": "小",
"fields": [ "title", "subTitle" ]
}
}
}
詞條匹配(term)
term查詢被用于精確值匹配,這些精確值可能是數字、時間、布爾或者那些未分詞的字串
示例:
GET /ceshi/_search
{
"query": {
"term": {
"price": "1899"
}
}
}

多詞條精確匹配(terms)
terms查詢和term查詢一樣,但它允許你指定多值進行匹配,如果這個欄位中包含了指定值中的任何一個值,那么這個檔案滿足條件:

結果過濾
默認情況下,elasticsearch在搜索結果中,會把檔案中保存在
_source的所有欄位回傳,但是,如果我們只想要獲取其中的部分欄位,我們可以添加_source的過濾,
直接指定欄位
示例:
GET /ceshi/_search
{
"_source": ["title","price"],
"query": {
"term": {
"price": 1899
}
}
}
2. 指定includes和excludes我們也可以通過“
includes:來指定想要顯示的欄位 excludes:來指定不想要顯示的欄位
示例:
GET /ceshi/_search
{
"_source": {
"includes": ["title","images"]
},
"query": {
"term": {
"price": 1899
}
}
}
GET /ceshi/_search
{
"_source": {
"excludes": ["title","images"]
},
"query": {
"term": {
"price": 1899
}
}
}
2.18 高級查詢
1.布爾組合(bool)
bool把各種其他查詢通過must(與)、must_not(非)、shoud(或)的當時組合
示例
# 查詢title可能包含“大米”,但一定包含“手機”的資料
GET /ceshi/_search
{
"query": {
"bool": {
"must": {"match":{"title":"大米"}},
"must_not":{"match":{"title":"電視"}},
"should":{"match":{"title":"手機"}}
}
}
}

2.范圍查詢(range)
range查詢找出那些落在指定區間內的數字或時間
示例
# 查詢price在1000-2900范圍內的資料
GET /ceshi/_search
{
"query": {
"range": {
"price": {
"gte": 1000,
"lte": 2900
}
}
}
}

range查詢允許以下字符:
| 運算子 | 說明 |
|---|---|
| gt | 大于 |
| gte | 大于等于 |
| lt | 小于 |
| lte | 小于等于 |
3.模糊查詢(fuzzy)
新增一條資料
POST /ceshi/_doc/4
{
"title":"apple手機",
"images":"http://image.leyou.com/12479122.jpg",
"price":6899.00
}
fuzzy查詢時term查詢的模糊等價,它允許用戶搜索詞條與實際詞條的拼寫出現偏差,但是偏差的編輯距離不得超過2
POST /ceshi/_doc/4
{
"title":"apple手機",
"images":"http://image.leyou.com/12479122.jpg",
"price":6899.00
}
上面查詢也是可以查到apple手機資料的
我們可以通過fuzziness屬性來指定允許的偏差距離:
GET /ceshi/_search
{
"query": {
"fuzzy": {
"title": {
"value": "appaa",
"fuzziness": 3
}
}
}
}
也是可以查到資料
注意:
fuzzinexx值越大,偏差距離也越大,模糊查詢的范圍也越大,反之,
4.過濾(filter)
條件查詢找那個進行過濾
所有的查詢都會影響到檔案的評分及排名,如果我們需要在查詢結果中進行過濾,并且不希望過濾條件影響評分,那么就不要吧過濾條件作為查詢條件來用,而是使用filter方式:
GET /ceshi/_search
{
"query": {
"bool": {
"must":{"match":{"title":"手機"}},
"filter": {
"range": {
"price": {
"gte": 1000,
"lte": 5000
}
}
}
}
}
}

注意:
filet中還可以再次進行bool組合條件過濾
無查詢條件,直接過濾
如果一個查詢只有過濾,沒有查詢條件,不希望進行評分,我們可以使用constant_score取代只有filter陳述句的bool查詢,在性能上時完全相同的,但對于提高查詢簡潔性和清晰度有很大幫助,
示例:
GET /ceshi/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"price": {
"gte": 1000,
"lte": 4000
}
}
}
}
}
}

5.排序
需求:想要將查詢條件title和price范圍過濾出來結果,進行首先按照價格排序,然后按照得分排序:
GET /ceshi/_search
{
"query": {
"bool": {
"must":{"match":{"title":"手機"}},
"filter":{
"range": {
"price": {
"gte": 1000,
"lte": 7000
}
}
}
}
},
"sort": [
{"price": {"order": "desc"}},
{"_score": {"order": "desc"}}
]
}

2.3聚合aggregations
聚合可以讓我們及其方便的實作對資料的統計、分析,例如:
什么品牌的手機最受歡迎 這些手機的平均價格、最高價格、最低價格 這些手機每月的銷售情況如何
實作這些統計功能要比資料庫的sql方便的多,而且查詢速度非常快,可以實作實時搜索效果,
2.3.1 基本概念
Elasticsearch中的聚合,包含多種型別,最常用的兩種:
桶 度量
1.桶(bucket)
桶的作用,是按照某種方式對資料進行分組,每一組資料在ES中稱為一個桶,例如,我們根據國籍對人劃分,可以得到中國桶、英國桶、美國桶,,,
Elasticsearch中提供的劃分桶的方式很多:
Date Histogram Aggregation:根據日期階梯分組,例如給定階梯為周,會自動每周分一組 Histogram Aggregation:根據數值階梯分組,與日期類似 Terms Aggregation:根據詞條內容分組,詞條內容完全匹配分為一組 Range Aggregation:數值和日期的范圍分組,指定開始和結束,然后分段分組 ,,,
綜上所述,我們發現bucket aggregations只負責對資料進行分組,并不進行計算,因此bucket中往往會嵌套另一種聚合:metrics aggregations 即度量,
2.度量(metrics)
分組完成以后,我們一般會對組中的資料進行聚合運算,例如求平均值、最大、最小、求和等操作,這些在ES中稱為度量,
比較常用的一些度量聚合方式:
Avg Aggregation:求平均值 Max Aggregation:求最大值 MIn Aggregation:求最小值 Percentiles Aggregation:求百分比 Stats Aggregation:同時回傳avg、max、min、sum、count等 Sum Aggregation:求和 Top hits Aggregation:求前幾 Value Count Aggregation:求總數 ,,,
開始測驗
為了方便測驗,我們首先批量匯入測驗資料
2.3.2 創建索引庫
PUT /cars
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties":{
"color":{"type":"keyword"},
"make":{"type":"keyword"}
}
}
}
查看索引庫映射關系:GET /cars/_mapping
注意:在ES中,需要進行聚合、排序、過濾的欄位其處理方式比較特殊,因此不能被分詞,這里我們將color和make這兩個欄位型別設定為keyword型別,這個型別不會被分詞,將來就可以參與聚合
匯入資料:
POST /cars/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
查看cars索引庫中的資料:
GET /cars/_search
{
"query": {
"match_all": {}
}
}
2.3.3 聚合為桶
1.按照cars中的color欄位來劃分桶
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
size:查詢條數,這里設定為0,因為我們不關心搜索到的資料,只關心聚合結果,提高效率 aggs:宣告這是一個聚合查詢,是aggregations的縮寫
popular_color:給這次聚合起一個名字,任意,
terms:劃分桶的方式,這里是根據詞潭訓分
field:劃分桶的欄位

hits:查詢結果為空,因為我們設定了size為0 aggregations:聚合的結果
popular_clor:我們定義的聚合名稱
buckets:查找到的桶,每個不同的color欄位值都會形成一個桶
key:這個桶對應的color欄位的值 doc_count:這個桶中的檔案數量 總結:通過聚合的結果我們發現,目前紅色的小車比較暢銷
2.3.4 桶內度量
前面的例子告訴我們每個桶里面的檔案數量,但通常,我們的應用需要提供更為復雜的檔案度量,例如,每種顏色騎車的平均價格是多少?
因此,我們需要告訴Elasticsearch使用哪個欄位,使用何種度量方式進行運算,這些資訊要嵌套在==桶內==,度量的運算會基于==桶內==的檔案進行,
示例:
需求:按照cars中的color欄位劃分桶,并求相應每個桶中的平均價格
GET /cars/_search
{
"size": 0,
"aggs": {
"popular_color": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
arrgs:我們在aggs(popular_color)中添加新的aggs,可見==度量也是一個聚合,度量是在桶中的聚合==, avg_price:度量聚合的名稱,任意 avg:度量的型別,這里是求平均值 field:度量運算的欄位
結果:

我們可以看到每個桶中都有自己的avg_price欄位,這就是度量聚合的結果
2.3.5 桶內嵌套桶
上面示例是桶內嵌套度量運算,事實上桶內不僅可以嵌套運算,還可以嵌套其他桶,也就是說在每個分組中,可以再分更多桶,
示例:
需求:我們想要統計每種顏色的汽車中,分別屬于哪個制造商,按照make欄位在進行分桶
GET /cars/_search
{
"size": 0,
"aggs": {
"popular_color": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"maker":{
"terms": {
"field": "make"
}
}
}
}
}
}
結果:
{
"took" : 14,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 8,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"popular_color" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "red",
"doc_count" : 4,
"maker" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "honda",
"doc_count" : 3
},
{
"key" : "bmw",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 32500.0
}
},
{
"key" : "blue",
"doc_count" : 2,
"maker" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "ford",
"doc_count" : 1
},
{
"key" : "toyota",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 20000.0
}
},
{
"key" : "green",
"doc_count" : 2,
"maker" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "ford",
"doc_count" : 1
},
{
"key" : "toyota",
"doc_count" : 1
}
]
},
"avg_price" : {
"value" : 21000.0
}
}
]
}
}
}
我們可以看到,新的聚合maker被嵌套在原來每一個color的桶中, 每個顏色下面都根據make欄位進行了分組 我們從結果中讀到的資訊:
紅色車共有4輛 紅色車的平均售價32500 其中3輛是Honda本田制造,1輛是BMW寶馬制造
2.3.6 階梯分桶(Histogram)
histogram是把數值型別的欄位,按照一定的階梯大小進行分組,需要指定一個階梯值(interval)來劃分階梯大小
示例
需求:比如你有價格欄位,如果你設定interval的值為200.那么階梯就會是這樣的:
0,200,400,600,,,,
上面列出的是每個階梯的key,也是區間的起點
如果一件商品的價格是450,會落在哪個階梯區間呢?計算公式如下:
bucket_key = Math.floor((value-offset)/interval)*interval+offset
value:就是當前資料的值,本例中是450 offset:起始偏移值,默認為0 interval:階梯間隔,比如200 因此得到的key=Math.floor((450-0)/200)*200+0=400
我們對汽車的價格進行分組,指定間隔interval為5000:
# 階梯分桶,對汽車的價格分組,指定間隔interval為5000,并約束桶內的檔案最小值為1
GET /cars/_search
{
"size": 0,
"aggs": {
"price": {
"histogram": {
"field": "price",
"interval": 5000,
"min_doc_count": 1
}
}
}
}

2.3.7 范圍分桶(range)
范圍分桶和階梯分桶類似,也是把數字按照階段進行分組,只不過range方式需要你自己指定每一組的起始和結束大小
2.4 Spring Data Elaticsearch
Elasticsearch提供的java客戶端有一些不太方便的地方:
很多地方需要在java中拼接json字串 需要自己把物件序列化為json存盤 查詢到結果也需要自己反序列化為物件 因此,我們可以學習Spring提供的套件:Spring Data Elaticsearch
2.4.1 簡介
Spring Data Elaticsearch是Spring Data專案下的一個子模塊
Spring Data官網:http://projects.spring.io/spring-data/
Spring Data的使命是給各種資料訪問提供統一的編程介面,不管是關系型資料庫(mysql),還是非關系型資料庫(redis),或者類似Elaticsearch索引資料庫,

Spring Data Elaticsearch的頁面:https://projects.spring.io/spring-data-elasticsearch/
特征:
支持Spring的基于 @configuration的java配置方式,或者XML配置方式提供了用于操作ES的便捷工具類 ElaticsearchTemplate,包括實作檔案到POJO之間的自動智能映射利用Spring的資料轉換服務實作的功能豐富的物件映射 基于注解的元資料映射方式,而且可擴展以支持更多不同的資料格式 根據持久層介面自動生成物件實作方法,無需人工撰寫基本操作代碼(類似mybatis,根據介面自動得到實作,也支持人工定制查詢)
2.4.2 專案實戰
1.創建一個專案,匯入如下pom依賴:
<!-- high client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${es.version}</version>
</dependency>
<!-- rest-high-level-client 依賴如下兩個jar -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${es.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>${es.version}</version>
</dependency>
2.application.yml配置
es:
host: 192.168.15.100
port: 9200
scheme: http
3.es配置類
@Configuration
public class ElaticsearchConfig {
@Value("${es.host}")
public String host;
@Value("${es.port}")
public int port;
@Value("${es.scheme}")
public String scheme;
@Bean(destroyMethod = "close")
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(RestClient.builder(
new HttpHost(host, port, scheme)));
}
}
4.單元測驗
創建索引庫
@Test
public void createIndexTest() throws IOException {
CreateIndexRequest indexRequest = new CreateIndexRequest(index);
CreateIndexResponse response = client.indices().create(indexRequest, RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged());
}
判斷索引庫是否存在
@Test
public void indexExistsTest() throws IOException {
GetIndexRequest request = new GetIndexRequest(index);
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
添加檔案
@Test
public void addDocTest() throws IOException {
IndexRequest request = new IndexRequest(index);
String source = JSONObject.toJSONString(new Users(1000, "瑟曦", 30));
request.source(source, XContentType.JSON);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
System.out.println(response.getResult()); //CREATED
}
批量添加檔案
@Test
public void batchAddDocTest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
List<IndexRequest> indexRequests = generateRequests();
indexRequests.forEach(x -> {
bulkRequest.add(x);
});
BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(response.hasFailures());
}
public List<IndexRequest> generateRequests() {
List<IndexRequest> requests = new ArrayList<>();
requests.add(generateNewRequests(new Users(1, "雪諾", 25)));
requests.add(generateNewRequests(new Users(2, "艾麗婭", 20)));
requests.add(generateNewRequests(new Users(3, "珊莎", 23)));
return requests;
}
public IndexRequest generateNewRequests(Users users) {
IndexRequest indexRequest = new IndexRequest(index);
indexRequest.source(JSONObject.toJSONString(users), XContentType.JSON);
return indexRequest;
}
根據條件搜索檔案
@Test
public void serachTest() throws IOException {
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(new RangeQueryBuilder("age").from(20).to(30))
.mustNot(new TermQueryBuilder("id", 1000));
builder.query(boolQueryBuilder);
request.source(builder);
System.out.println("搜索陳述句為:" + request.source().toString());
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println("搜索結果:" + response);
SearchHits hits = response.getHits();
SearchHit[] hitsArr = hits.getHits();
for (SearchHit documentFields : hitsArr) {
System.out.println(documentFields.getSourceAsString());
}
}
修改檔案
@Test
public void modifyDocTest() throws IOException {
UpdateRequest request = new UpdateRequest(index, "nqHRknUBJcoqc7s-i-Az");
Map<String, Object> params = new HashMap<>();
params.put("id", 4);
request.doc(params);
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
System.out.println(response.getResult());
}
洗掉指定ID的檔案
@Test
public void deleteDocTest() throws IOException {
DeleteRequest request = new DeleteRequest(index, "nqHRknUBJcoqc7s-i-Az");
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
System.out.println(response.getResult());
}
洗掉索引庫
@Test
public void deleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest(index);
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged());
}
完結
與springboot集成的話也可直接使用ElasticsearchRestTemplate,也是基于RestHighLevelClient的模板封裝,后續有需要可以研究下,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/202767.html
標籤:其他
