ElasticSearch詳細筆記
什麼是ElasticSearch
Elasticsearch(簡稱ES)是一個基於Apache Lucene(TM)的開源搜尋引擎,無論在開源還是專有領域,Lucene 可以被認為是迄今為止最先進、效能最好的、功能最全的搜尋引擎庫。注意,Lucene 只是一個庫。想要發揮其強大的作用,你需使用 Java 並要將其整合到你的應用中。
重要特性:
- 分散式的實時檔案儲存,每個欄位都被索引並可被搜尋
- 實時分析的分散式搜尋引擎
- 可以擴充套件到上百臺伺服器,處理PB級結構化或非結構化資料
基本概念&倒排索引
需要了解ElasticSearch中的一些基本概念。
- 索引(indices)
-- Databases 資料庫
- 型別(type)
-- Table 資料表
- 文件(Document)
-- Row 行
- 欄位(Field)
-- Columns 列
ElasticSearch中的倒排索引
ElasticSearch在插入資料的同時還會為這些資料維護了一張倒排索引表,通過這個倒排索引可以大大的提高搜尋的效能
倒排索引(Inverted Index)也叫反向索引,有反向索引必有正向索引。簡單來講,正向索引是通過key找value,反向索引則是通過value找key。
舉個例子:
-
comment
表有id
、content
兩個欄位,現在向comment
表插入如下一條資料:id:1 content:今天天氣很好
ElasticSearch
會把content
的內容進行分詞,可以分成三個詞:今天
、天氣
、很好
。倒排索引表就如下:今天 [1] 天氣 [1] 很好 [1]
表示
"今天"、"天氣"、"很好"
這三個詞在1
號記錄中存在。 -
再向
comment
表中插入一條資料:id: 2 content: 今天天氣好冷
繼續將
content
的內容進行分詞,得到:今天
、天氣
、好冷
。將這三個詞新增到倒排索引表中今天 [1,2] 天氣 [1,2] 很好 [1] 好冷 [2]
"今天"
、"天氣"
這兩個詞在1號
、2號
記錄中都存在。"很好"
在1號
記錄存在"好冷"
在2號
記錄存在 -
現在查詢記錄,檢索條件:今天好冷
通過
"今天好冷"
這個字串進行檢索記錄,這種就屬於通過value
查詢key
。ElasticSearch
首先將"今天好冷"
進行分詞為:"今天"
、"好冷"
兩個詞。然後在倒排索引表中查詢,發現
"今天"
這個詞命中了1號
和2號
記錄,再看"好冷"
這個詞命中了2號
記錄。這裡有個評分機制,
2號
記錄經過對比發現命中2次
,1號
記錄命中1次
。因此2號
記錄的評分就比1號
記錄高。查詢出來的結果順序就是:id: 2 content: 今天天氣好冷 id: 1 content: 今天天氣很好
這就是倒排索引的基本邏輯,通過 value
查詢 key
。實際上,ElasticSearch引擎建立的倒排索引比這個複雜得多。
安裝ElasticSearch&Kibana
Docker安裝ElasticSearch
-
下載映象
docker pull elasticsearch:7.4.2 #儲存和檢索資料
-
建立例項需要掛載目錄
mkdir -p /mydata/elasticsearch/config mkdir -p /mydata/elasticsearch/data echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml chmod -R 777 /mydata/elasticsearch/
-
建立執行例項
docker run --name es -p 9200:9200 -p 9300:9300 \ -e "discovery.type=single-node" \ -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \ -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \ -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ -d elasticsearch:7.4.2
-e "discovery.type=single-node"
:單例項模式-e ES_JAVA_OPTS="-Xms64m -Xmx512m"
:設定執行的初始記憶體和最大記憶體-v
:將本地檔案對映到容器中對應檔案 -
瀏覽器訪問主機
9200
埠
Docker安裝Kibana
Kibana 是一個基於 Node.js 的 Elasticsearch 索引庫資料統計工具,可以利用 Elasticsearch 的聚合功能,生成各種圖表,如柱形圖,線狀圖,餅圖等。
而且還提供了操作 Elasticsearch 索引資料的控制檯,並且提供了一定的API提示,非常有利於我們學習 Elasticsearch 的語法。
安裝步驟:
-
下載映象
docker pull kibana:7.4.2
-
檢視
ElasticSearch
例項地址docker inspect es
es
:執行的elasticsearch
容器例項名 -
建立執行例項
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://172.17.0.3:9200 -p 5601:5601 -d kibana:7.4.2
172.17.0.3
:地址填寫上一步查詢到的地址 -
瀏覽器訪問主機
5601
埠注意:kibana啟動可能有點慢,需要等待一會
ES基本操作
_cat
elasticsearch
提供 _cat
API 來檢視ElasticSearch
狀態
#0. 檢視_cat支援的命令
GET /_cat
#1. 檢視所有節點
GET /_cat/nodes
#2. 檢視es健康狀態
GET /_cat/health
#3. 檢視主節點
GET /_cat/master
#4. 檢視所有索引
GET /_cat/indices
例子:
http://192.168.23.6:9200/_cat/indices
新增資料
elasticsearch
中儲存的都是 json
格式的資料
現在向 es
新增一條資料 { "msg": "Hello ElasticSearch" }
-
訪問
kibana
,選擇Dev Tools
-
在這個介面操作
es
-
PUT 方式
PUT /news/comment/1 { "msg":"Hello ElasticSearch" }
執行結果:
-
POST 方式
POST /news/comment/1 { "name":"Hello ElasticSearch" }
執行結果:
可以理解為向
news
資料庫的comment
表中新增了一條記錄,不過這裡叫做索引和型別
分析結果:
_index: 索引,對應就是資料庫名
_type: 型別,對應就是資料表
_id: 資料的id
_version: 版本號,通過運算元據版本號會不斷增加
_result: created表示建立了一條資料,如果重新put一條資料,則該狀態會變為updated,並且版本號也會發生變化。
_shards: 分片資訊
_seq_no: 序列號
_primary_term:
- PUT可以新增也可以修改。PUT必須指定id;
- POST新增資料的時候不指定id,會自動的生成id,並且型別是新增
- 由於PUT需要指定id,我們一般用來做修改操作,不指定id會報錯。
查詢資料
檢視使用GET請求方式檢索資料
GET /news/comment/1
可以理解為向
news
資料庫的comment
表中查詢一條id
為1
的記錄
_source
:儲存的資料
更新資料
-
POST 方式
POST /news/comment/1/_update { "doc": { "msg": "Hello ES" } }
POST /customer/external/1 { "msg": "Hello ES" }
使用
_update
需要加doc
區別:
- 使用
_update
修改資料,版本號不會增加
- 使用
-
PUT 方式
PUT /news/comment/1 { "msg": "Hello ES" }
刪除資料
刪除一條資料
DELETE /news/comment/1
刪除一個索引
DELETE /customer
bulk批量API
bulk相當於資料庫裡的bash操作, 其支援的操作型別包括:index, create, update, delete
-
bulk 語法:
{ action: { metadata } } { requstbody }
批量新增 index
POST /news/comment/_bulk
{ "index": {"_id": 2} }
{ "msg": "zhangsan" }
{ "index": {"_id": 3} }
{ "msg": "lisi" }
{ "index": {"_id": 4} }
{ "msg": "wangwu" }
執行結果:
引數解析:
{ "index": {"_id": 2} }
{ "msg": "zhangsan" }
這兩行為一次操作,第一行指定了資料的id(還可以指定
index
、type
;以下劃線開頭)第二行是儲存的資料體
結果解析:
"took": 31
:請求執行時間(毫秒)
"error": false
:請求是否出錯,返回flase表示沒有出錯
"items"
:操作過的文件的具體資訊
"static"
:響應狀態碼
批量新增 create
POST /news/comment/_bulk
{ "create": {"_id": 2} }
{ "msg": "zhangsan" }
{ "create": {"_id": 3} }
{ "msg": "lisi" }
{ "create": {"_id": 4} }
{ "msg": "wangwu" }
執行結果:
新增失敗了,因為
id
重複問題
create
方式新增,如果id
已存在了就會報錯index
方式新增,如果id
存在不會報錯,並且version
增加
批量更新 update
POST /news/comment/_bulk
{ "update": {"_id": 2} }
{ "doc":{"msg": "zhangsan.cn"} }
{ "update": {"_id": 3} }
{ "doc":{"msg": "lisi.cn"} }
{ "update": {"_id": 4} }
{ "doc":{"msg": "wangwu.cn"} }
執行結果:
更新操作需要多加一層
doc
。
{ "update": {"_id": 2} }
{ "doc":{"msg": "zhangsan.cn"} }
第一行
update
為更新操作,並指定了更新資料的id 第二行
doc
裡面是更新的新資料。
批量刪除 delete
批量刪除不需要請求體(資料體)
POST /news/comment/_bulk
{ "delete": {"_id": 2} }
{ "delete": {"_id": 3} }
{ "delete": {"_id": 4} }
執行結果:
進階檢索
學習之前先為es新增一些測試資料,這裡使用官方提供的測試資料 https://gitee.com/depthch/elasticsearch/blob/master/doc/test/resourses/accounts.json
-
開啟上面連結將裡面的資料複製
-
在 kibana 中使用 bulk 批量新增資料
在索引為
bank
,型別account
中批量插入了資料
ES支援兩種基本方式檢索
- 通過REST request uri 傳送搜尋引數 (uri +檢索引數)
- 通過REST request body 來傳送它們(uri+請求體)
_search
請求方式:uri + 檢索引數
1、檢索 bank
下所有資訊
GET /bank/_search
響應結果解析:
- took:Elasticsearch執行搜尋的時間(毫秒)
- time_out:告訴我們搜尋是否超時
- _shards:告訴我們多少個分片被搜尋了,以及統計了成功/失敗的搜尋分片
- hits:搜尋結果
- hits.total:搜尋結果數量
- hits.hits:實際的搜尋結果陣列(預設為前10的文件)
- sort:結果的排序key (鍵) (沒有則按score排序)
- score和max score:相關性得分和最高得分(全文檢索用)
2、檢索 bank
下所有資訊,並按照 account_number
升序
GET /bank/_search?q=*&sort=account_number:asc
q=*
:* 是萬用字元,表示查詢所有的資料sort=account_number:asc
:按照 account_number 排序,asc 是升序
queryDSL
請求方式:uri + 請求體
基本語法
QUERY_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
1、檢索 bank
下所有資訊,並按照 account_number
降序
GET /bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": {
"order": "desc"
}
}
]
}
查詢引數解析:
- match_all查詢型別【代表查詢所有的所有】,es中可以在query中組合非常多的查詢型別完成複雜查詢;
- 除了query引數之外,我們可也傳遞其他的引數以改變查詢結果,如sort,size;
- from+size限定,完成分頁功能;
- sort排序,多欄位排序,會在前序欄位相等時後續欄位內部排序,否則以前序為準;
_source
_source:返回指定的部分欄位
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"sort": [
{
"account_number": {
"order": "desc"
}
}
],
"_source": ["balance","firstname"]
}
_source:指定返回的部分欄位
match匹配查詢
-
基本型別(非字串),精確控制
GET bank/_search { "query": { "match": { "account_number": "20" } } }
-
字串,全文檢索
GET bank/_search { "query": { "match": { "address": "kings" } } }
match_phrase
match_phrase: 短句匹配,將需要匹配的值當成一整個單詞(不分詞)進行檢索
GET bank/_search
{
"query": {
"match_phrase": {
"address": "mill road"
}
}
}
查出
address
中包含mill road
的所有記錄,並給出相關性得分
match_phrase
和match
的區別:
- match:匹配時會分詞,如:mill road,會拆分成:mill、road。然後檢索出包含這兩個詞的記錄(包含其中一個詞也滿足條件)
- match_phrase:匹配時不會分詞,如:mill road,會被當成一個整體來檢索記錄,必須包含整個整體的記錄才會被檢索出來
match.keyword
keyword
是精確匹配,就是說某條記錄必須完全滿足匹配條件才會被檢索出來
GET bank/_search
{
"query": {
"match": {
"address.keyword": "990 Mill Road"
}
}
}
multi_math
multi_math:多欄位匹配可以在多個欄位中去匹配條件
GET bank/_search
{
"query": {
"multi_match": {
"query": "mill",
"fields": [
"state",
"address"
]
}
}
}
state
或者address
中包含mill
,並且在查詢過程中,會對於查詢條件進行分詞。
bool
bool:用來做複合查詢,複合語句可以合併,任何其他查詢語句,包括符合語句。這也就意味著,複合語句之間可以互相巢狀,可以表達非常複雜的邏輯。
must
must:必須達到must所列舉的所有條件
GET bank/_search
{
"query":{
"bool":{
"must":[
{"match":{"address":"mill"}},
{"match":{"gender":"M"}}
]
}
}
}
匹配
gender
為M
並且address
包含mill
的文件
must_not
must_not,必須不匹配must_not所列舉的所有條件。
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "mill"
}
}
],
"must_not": [
{
"match": {
"age": "38"
}
}
]
}
}
匹配
gender
為M
並且address
包含mill
的文件,但是age
不等於38
的資料
should
should:應該達到should列舉的條件,如果到達會增加相關文件的評分,並不會改變查詢的結果。
如果query中只有should且只有一種匹配規則,那麼should的條件就會被作為預設匹配條件二區改變查詢結果。
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "mill"
}
}
],
"must_not": [
{
"match": {
"age": "18"
}
}
],
"should": [
{
"match": {
"lastname": "Wallace"
}
}
]
}
}
}
should
是"應該包含"
的意思,不是必須包含,也就是除去其他匹配條件即使lastName
不包含Wallace
也能匹配成功。但是如果有資料的lastName
包含Wallace
,那麼這條資料的相關性得分會更高,即優先匹配。
Filter
並不是所有的查詢都需要產生分數,特別是哪些僅用於filtering過濾的文件。為了不計算分數,elasticsearch會自動檢查場景並且優化查詢的執行。
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
}
],
"filter": {
"range": {
"balance": {
"gte": "10000",
"lte": "20000"
}
}
}
}
}
}
這裡先是查詢所有匹配
address
包含mill
的文件,然後再根據10000<=balance<=20000
進行過濾查詢結果
filter
在使用過程中,並不會計算相關性得分,即 "_score" : 0.0
term
和match一樣
。匹配某個屬性的值。
-
全文檢索欄位用
match
-
非text
欄位匹配用term
。
GET bank/_search
{
"query": {
"term": {
"address": "mill Road"
}
}
}
使用
term
匹配text
型別資料是匹配不到任何資料的。
Aggregation
聚合提供了從資料中分組和提取資料的能力。最簡單的聚合方法大致等於SQL Group by和SQL聚合函式。在elasticsearch中,執行搜尋返回this(命中結果),並且同時返回聚合結果,把以響應中的所有hits(命中結果)分隔開的能力。這是非常強大且有效的,你可以執行查詢和多個聚合,並且在一次使用中得到各自的(任何一個的)返回結果,使用一次簡潔和簡化的API啦避免網路往返。
聚合語法:
"aggs":{
"aggs_name這次聚合的名字,方便展示在結果集中":{
"AGG_TYPE聚合的型別(avg,terms....)":{}
}
}
常用聚合型別:
- avg:求平均值
- max:求最大值
- min:求最小值
- sum:求和
- filter:過濾聚合。基於一個條件,來對當前的文件進行過濾的聚合。
- terms:詞聚合。基於某個field,該 field 內的每一個【唯一詞元】為一個桶,並計算每個桶內文件個數。預設返回順序是按照文件個數多少排序。
搜尋address中包含mill的所有人的年齡分佈以及平均年齡
GET bank/_search
{
"query": {
"match": {
"address": "Mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": {
"field": "age"
}
}
}
}
查出所有年齡分佈,並且求這些年齡段的這些人的平均薪資
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"ageAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
聚合是可以巢狀聚合的。
查出所有年齡分佈,並且這些年齡段中M的平均薪資和F的平均薪資以及這個年齡段的總體平均薪資
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"ageBalanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
先按年齡聚合查出了所有分佈情況,再巢狀聚合 按性別聚合查出分佈情況,最後再巢狀聚合 按薪資聚合查出平均薪資。
ageBalanceAvg
:根據年齡分佈計算出平均工資,這個聚合跟性別無關。
mapping
maping是用來定義一個文件(document),以及它所包含的屬性(field)是如何儲存和索引的。比如:使用maping來定義:
- 哪些字串屬性應該被看做全文字屬性(full text fields);
- 哪些屬性包含數字,日期或地理位置;
- 文件中的所有屬性是否都能被索引(all 配置);
- 日期的格式;
- 自定義對映規則來執行動態新增屬性;
檢視 bank 索引的 mapping
對映資訊
GET bank/_mapping
執行結果:
在
properties
中可以看到每個field
的欄位型別
新版本的改變
ElasticSearch7 去掉了type(表)概念
- 關係型資料庫中兩個資料表示是獨立的,即使他們裡面有相同名稱的列也不影響使用,但ES中不是這樣的。elasticsearch是基於Lucene開發的搜尋引擎,而ES中不同type下名稱相同的filed最終在Lucene中的處理方式是一樣的。
- 兩個不同type(表)下的兩個名稱 user_name(欄位),在ES同一個索引下其實被認為是同一個filed,你必須在兩個不同的type(表)中定義相同的 filed 對映。否則,不同 type(表)中的相同欄位名稱就會在處理中出現衝突的情況,導致Lucene處理效率下降。
- 去掉type(表)就是為了提高ES處理資料的效率。
- Elasticsearch 7.x URL中的type引數為可選。比如,索引一個文件不再要求提供文件型別。
- Elasticsearch 8.x 不再支援URL中的type引數。
建立對映
PUT /student
{
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"email": {
"type": "keyword"
},
"name": {
"type": "text"
}
}
}
}
執行結果:
建立
student
索引並指定了索引的mapping
對映資訊。
properties
:指定對映的欄位和欄位型別
檢視對映
GET /student
執行結果:
新增新的欄位對映
為 student
索引新增一個新的欄位對映
PUT /student/_mapping
{
"properties": {
"id": {
"type": "keyword",
"index": false
}
}
}
執行結果:
再次檢視對映:
"index": false
:表明新增的欄位不能被檢索,只是一個冗餘欄位。
資料遷移
由於ElasticSearch
是不支援修改對映欄位的,只能新增對映欄位。如果必須修改就需要資料遷移。
需求:將 bank 索引的所有資料遷移到 newbank 索引下,並將 age
欄位型別改為 integer
。修改 city、email、employer、gender
等欄位型別改為 keyword
具體步驟:
-
檢視 bank 的對映資訊
GET /bank
-
建立一個跟
bank
索引欄位相同mapping
對映的索引,並且改變欄位型別PUT /newbank { "mappings": { "properties": { "account_number": { "type": "long" }, "address": { "type": "text" }, "age": { "type": "integer" }, "balance": { "type": "long" }, "city": { "type": "keyword" }, "email": { "type": "keyword" }, "employer": { "type": "keyword" }, "firstname": { "type": "text" }, "gender": { "type": "keyword" }, "lastname": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "state": { "type": "keyword" } } } }
在指定對映資訊時改變了欄位的型別。
-
可以檢視到
newbank
索引的對映資訊GET /newbank
執行結果:
欄位型別都已經改變。
-
將
bank
索引中的資料遷移到newbank
索引中POST _reindex { "source": { "index": "bank", "type": "account" }, "dest": { "index": "newbank" } }
執行結果:
source
:指定舊索引資訊index
:指定舊的索引名type
:指定 type,如果沒有 type 可以不指定。
dest
:指定新索引資訊index
:指定新的索引名
-
檢視
newbank
中的資料GET /newbank/_search
執行結果:
檢索了1000條資料,資料遷移成功。發現
type
:_doc
,我們在建立對映關係時並沒有設定type
,這是因為ElasticSearch7
去掉了type
(表)概念,但是有個預設的type
就是_doc
。
分詞
一個 tokenizer(分詞器)
接收一個字元流,將之分割為獨立的 tokens(詞元,通常是獨立的單詞)
,然後輸出tokens
流。例如:hello world
遇到空白字元時分割文字。它會將文字 "hello world"
分割為 [hello, world]
。
tokenizer(分詞器
)還負責記錄各個terms(詞條)
的順序或position
位置(用於phrase短語和word proximity詞近鄰查詢),以及term(詞條)
所代表的原始word(單詞)
的start(起始)
和 end(結束)
的 character offsets(字串偏移量)
(用於高亮顯示搜尋的內容)
。
elasticsearch
提供了很多內建的分詞器,可以用來構建 custom analyzers(自定義分詞器)
。
使用分詞器
POST _analyze
{
"analyzer": "standard",
"text": "Nothing is impossible!"
}
執行結果:
standard
:ElasticSearch預設的分詞器,預設就是按空格進行分詞的
安裝 ik 分詞器
所有的語言分詞,預設使用的都是“Standard Analyzer”,但是這些分詞器針對於中文的分詞,並不友好。為此需要安裝中文的分詞器。
具體步驟:
-
檢視 ElasticSearch 的版本
訪問es主機的 9200 埠檢視es版本
-
下載對應版本的 ik 分詞器
在前面安裝的elasticsearch時,我們已經將elasticsearch容器的“/usr/share/elasticsearch/plugins”目錄,對映到本地主機的“ /mydata/elasticsearch/plugins”目錄下.
2.1)進入
/mydata/elasticsearch/plugins
目錄cd /mydata/elasticsearch/plugins
2.2)下載 ik 分詞器
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
注意
7.4.2
是自己es對應的版本。 -
解壓下載好的檔案
unzip elasticsearch-analysis-ik-7.4.2.zip -d ik
-
重啟es
docker restart es
-
檢視安裝好的ik
GET _cat/plugins
執行結果:
可以看到我們的ik分詞器已經配置成功了
使用ik分詞器
ik分詞器有兩種分詞模式:ik_max_word
和 ik_smart
模式。
-
ik_max_word
會將文字做最細粒度的拆分,比如會將“中華人民共和國人民大會堂”拆分為“中華人民共和國、中華人民、中華、華人、人民共和國、人民、共和國、大會堂、大會、會堂等詞語。
-
ik_smart
會做最粗粒度的拆分,比如會將“中華人民共和國人民大會堂”拆分為中華人民共和國、人民大會堂。
先來看看預設的分詞器:
GET _analyze
{
"text":"每天都要努力"
}
執行結果:
ik 分詞器:
GET _analyze
{
"analyzer": "ik_smart",
"text":"每天都要努力"
}
執行結果:
可以看到使用 ik 分詞器可以把一些常用的中文詞分出來了。
自定義詞庫
雖然使用 ik 分詞器預設的詞庫已經可以實現常用的中文分詞了,但是如果我們要分的詞不常用,如:張明想學Java
GET _analyze
{
"analyzer": "ik_smart",
"text":"張明想學Java"
}
執行結果:
可以看到這裡把
"張明"
拆成了"張"
和"明"
,這並我是預想的效果,"張明"
應該拆成整體。
使用自定義詞庫,因為要使用 "遠端擴充套件字典"
,因此就需要一個遠端的字典檔案。這裡可以使用 nginx
來配置遠端擴充套件字典檔案。文件最後有nginx安裝和配置步驟
具體步驟:
-
配置好 nginx 後,建立詞庫檔案
vi /mydata/nginx/html/fenci.txt
新增如下內容並儲存:
張明 學java
-
修改es-plugins配置檔案
vi /mydata/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 擴充套件配置</comment> <!--使用者可以在這裡配置自己的擴充套件字典 --> <entry key="ext_dict"></entry> <!--使用者可以在這裡配置自己的擴充套件停止詞字典--> <entry key="ext_stopwords"></entry> <!--使用者可以在這裡配置遠端擴充套件字典 --> <entry key="remote_ext_dict">http://192.168.16.6/fenci.txt</entry> <!--使用者可以在這裡配置遠端擴充套件停止詞字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> --> </properties>
注意:修改第10行需要改成
nginx
服務的地址(這行預設是註釋的)。 -
修改了配置檔案需要重啟 es
docker restart es
-
再次使用 ik 分詞器
GET /_analyze { "analyzer": "ik_max_word", "text":"張明想學Java" }
//TODO 執行結果
......
SpringBoot 整合 ElasticSearch
SpringBoot
可以通過 9200
和9300
埠來呼叫 ElasticSearch
,它們之間的區別:
-
9300:TCP
SpringBoot
提供了spring-data-elasticsearch:transport-api.jar;
來對ES
呼叫。這種方式有些缺陷:- springboot版本不同,transport-api.jar不同,不能適配es版本
- es7.x已經不建議使用,es8以後就要廢棄
-
9200:HTTP
jestClient
:非官方,更新慢;RestTemplate
:模擬HTTP請求,ES很多操作需要自己封裝,麻煩;HttpClient
:模擬HTTP請求,ES很多操作需要自己封裝,麻煩;
Elasticsearch-Rest-Client
:官方RestClient,封裝了ES操作,API層次分明,上手簡單;
根據上面分析,我們最終選擇 Elasticsearch-Rest-Client
來進行呼叫es
。
具體步驟:
-
新增依賴
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.4.2</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.4.2</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.4.2</version> </dependency>
-
建立一個
ElasticSearch
的配置類@Configuration public class ElasticSearchConfig { public static final RequestOptions COMMON_OPTIONS; static { RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); COMMON_OPTIONS = builder.build(); } @Bean public RestHighLevelClient esRestClient(){ RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(new HttpHost("192.168.16.6", 9200, "http"))); return client; } }
HttpHost("192.168.16.6", 9200, "http")
- 192.168.16.6:es服務的地址
- 9200:es服務9200埠
- http:使用http協議
配置類中建立了一個
JavaBen
,之後通過這個JavaBean
來呼叫ElasticSearch
的相關API
儲存資料
@RunWith(SpringRunner.class)
@SpringBootTest
public class MallSearchApplicationTests {
@Autowired
RestHighLevelClient client;
/**
* 測試儲存資料
*/
@Test
public void contextLoads() throws IOException {
IndexRequest request = new IndexRequest("users"); //建立索引物件
request.id("10"); //設定id
//source方法可以直接傳入多個鍵值對值儲存
//request.source("name", "lisi", "age", 24, "gender", "男");
User user = new User(); //建立一個實體類user
user.setName("java");
user.setAge(24);
user.setGender("男");
String jsonString = JSON.toJSONString(user); //解析實體轉成json字串
request.source(jsonString, XContentType.JSON); //傳入json格式字串儲存
IndexResponse response = client.index(request, ElasticSearchConfig.COMMON_OPTIONS);
System.out.println(response); //列印結果
}
/**
* 定義 user 實體類
*/
@Data
class User{
private String name;
private int age;
private String gender;
}
}
執行結果:
IndexResponse[index=users,type=_doc,id=10,version=2,result=updated,seqNo=3,primaryTerm=6,shards={"total":2,"successful":1,"failed":0}]
kibana檢視:
資料測試新增成功。
檢索資料
搜尋address中包含mill的所有人的年齡分佈以及平均年齡
QueryDSL
實現:
GET bank/_search
{
"query": {
"match": {
"address": "Mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": {
"field": "age"
}
}
}
}
執行結果:
java
程式碼實現:
首先需要生成實體類,因為從es獲取的資料在java中最終都會儲存為java物件。
需要根據 _source 中的欄位生成 java 類 account
測試類程式碼:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MallSearchApplicationTests {
@Autowired
RestHighLevelClient client;
/**
* 按照 bank 索引裡的 _source 資料欄位建立對應的實體類
*/
@Data
@ToString
static class Account{
private int account_number;
private int balance;
private String firstname;
private String lastname;
private int age;
private String gender;
private String address;
private String employer;
private String email;
private String city;
private String state;
}
/**
* 檢索資料
*/
@Test
public void searchData() throws IOException {
//1、建立檢索物件
SearchRequest searchRequest = new SearchRequest();
//指定索引
searchRequest.indices("bank");
//指定DSL&檢索條件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//構建檢索條件
//sourceBuilder.query();
//sourceBuilder.from();
//sourceBuilder.size();
//sourceBuilder.aggregation();
sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
//構建聚合條件: 按照年齡值分佈進行聚合
TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
sourceBuilder.aggregation(ageAgg);
//構建聚合條件: 計算平均工資
AvgAggregationBuilder ageAvgAgg = AggregationBuilders.avg("ageAvgAgg").field("age");
sourceBuilder.aggregation(ageAvgAgg);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
//結果分析
SearchHits hits = searchResponse.getHits();
SearchHit[] hitsHits = hits.getHits();
for(SearchHit hit : hitsHits){
String sourceAsString = hit.getSourceAsString();
//將結果轉成 javeBean
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println("account:" + account);
}
//獲取檢索到的聚合資訊
Aggregations aggregations = searchResponse.getAggregations();
Terms ageAgg1 = aggregations.get("ageAgg");
// 列印聚合結果
for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
System.out.println("年齡:" + keyAsString + "===> " + bucket.getDocCount());
}
Avg balanceAgg1 = aggregations.get("ageAvgAgg");
System.out.println("平均年齡:" + balanceAgg1.getValueAsString());
}
}
執行結果:
這裡執行結果跟queryDSL查詢是相同的。
附:Docker 安裝 Nginx
-
隨便啟動一個nginx例項,只是為了複製出配置
docker run -p 80:80 --name nginx -d nginx:1.10
-
建立目錄並且將容器內部配置檔案拷貝到外部
mkdir -p /mydata/nginx/html mkdir -p /mydata/nginx/logs mkdir -p /mydata/nginx/conf docker container cp nginx:/etc/nginx/* /mydata/nginx/conf/ #由於拷貝完成後會在conf中存在一個nginx資料夾,所以需要將它的內容移動到conf中 mv /mydata/nginx/conf/nginx/* /mydata/nginx/conf/ rm -rf /mydata/nginx/conf/nginx
-
終止容器&刪除原來的容器
docker stop nginx docker rm nginx
-
建立新的 nginx 容器
docker run -p 80:80 --name nginx \ -v /mydata/nginx/html:/usr/share/nginx/html \ -v /mydata/nginx/logs:/var/log/nginx \ -v /mydata/nginx/conf:/etc/nginx \ -d nginx:1.10