ElasticSearch學習檔案
第一章 ElasticSearch入門篇
第一節 ElasticSearch概述
1.1 ElasticSearch是一個基於Lucene的搜尋伺服器。
它提供了一個分散式多使用者能力的全文搜尋引擎,基於RESTfulweb介面。ElasticSearch是用Java開發的, 並作為Apache許可條款下的開放原始碼釋出,是當前流行的企業級搜尋引擎。設計用於雲端計算中,能夠達到實時搜尋,穩定,可靠,快速,安裝使用方便。構建在全 文檢索開源軟體Lucene之上的Elasticsearch,不僅能對海量規模的資料完成分散式索引與檢索,還能提供資料聚合分析。據國際權威的 資料庫產品評測機構DBEngines的統計,在2016年1月,Elasticsearch已超過Solr等,成為排名第一的搜尋引擎類應用 概括:基於Restful標準的高擴充套件高可用的實時資料分析的全文搜尋工具
1.2 ElasticSearch的基本概念
Index
類似於mysql資料庫中的database
Type
類似於mysql資料庫中的table表,es中可以在Index中建立type(table),通過mapping進行對映。
Document
由於es儲存的資料是檔案型的,一條資料對應一篇檔案即相當於mysql資料庫中的一行資料row, 一個檔案中可以有多個欄位也就是mysql資料庫一行可以有多列。
Field
es中一個檔案中對應的多個列與mysql資料庫中每一列對應
Mapping
可以理解為mysql或者solr中對應的schema,只不過有些時候es中的mapping增加了動態識別功能,感覺很強大的樣子, 其實實際生產環境上不建議使用,最好還是開始制定好了對應的schema為主。
indexed
就是名義上的建立索引。mysql中一般會對經常使用的列增加相應的索引用於提高查詢速度,而在es中預設都是會加 上索引的,除非你特殊制定不建立索引只是進行儲存用於展示,這個需要看你具體的需求和業務進行設定了。
Query DSL
類似於mysql的sql語句,只不過在es中是使用的json格式的查詢語句,專業術語就叫:QueryDSL
GET/PUT/POST/DELETE
分別類似與mysql中的select/update/delete......
1.3 Elasticsearch的架構
Gateway層
es用來儲存索引檔案的一個檔案系統且它支援很多型別,例如:本地磁碟、共享儲存(做snapshot的時候需要用到)、hadoop 的hdfs分散式儲存、亞馬遜的S3。它的主要職責是用來對資料進行長持久化以及整個叢集重啟之後可以通過gateway重新恢復資料。
Distributed Lucene Directory
Gateway上層就是一個lucene的分散式框架,lucene是做檢索的,但是它是一個單機的搜尋引擎,像這種es分散式搜尋引擎系 統,雖然底層用lucene,但是需要在每個節點上都執行lucene進行相應的索引、查詢以及更新,所以需要做成一個分散式的運 行框架來滿足業務的需要。
四大模組元件
districted lucene directory之上就是一些es的模組
1.Index Module是索引模組,就是對資料建立索引也就是通常所說的建立一些倒排索引等;
2.Search Module是搜尋模組,就是對資料進行查詢搜尋;
3.Mapping模組是資料對映與解析模組,就是你的資料的每個欄位可以根據你建立的表結構 通過mapping進行對映解析,如果你沒有建立表結構,es就會根據你的資料型別推測你 的資料結構之後自己生成一個mapping,然後都是根據這個mapping進行解析你的資料;
4.River模組在es2.0之後應該是被取消了,它的意思表示是第三方外掛,例如可以通過一 些自定義的指令碼將傳統的資料庫(mysql)等資料來源通過格式化轉換後直接同步到es叢集裡, 這個river大部分是自己寫的,寫出來的東西質量參差不齊,將這些東西整合到es中會引發 很多內部bug,嚴重影響了es的正常應用,所以在es2.0之後考慮將其去掉。
Discovery、Script
es4大模組元件之上有 Discovery模組:es是一個叢集包含很多節點,很多節點需要互相發現對方,然後組成一個叢集包括選 主的,這些es都是用的discovery模組,預設使用的是Zen,也可是使用EC2;es查詢還可以支撐多種script即指令碼語言,包括 mvel、js、python等等。
Transport協議層
再上一層就是es的通訊介面Transport,支援的也比較多:Thrift、Memcached以及Http,預設的是http,JMX就是java的一個 遠端監控管理框架,因為es是通過java實現的。
RESTful介面層
最上層就是es暴露給我們的訪問介面,官方推薦的方案就是這種Restful介面,直接傳送http請求,方便後續使用nginx做代理、 分發包括可能後續會做許可權的管理,通過http很容易做這方面的管理。如果使用java客戶端它是直接呼叫api,在做負載均衡以 及許可權管理還是不太好做。
1.4 RESTful API
一種軟體架構風格、設計風格,而不是標準,只是提供了一組設計原則和約束條件。它主要用於客戶端和伺服器互動類的軟體。 基於這個風格設計的軟體可以更簡潔,更有層次,更易於實現快取等機制。在目前主流的三種Web服務互動方案中,REST相比於S OAP(Simple Object Access protocol,簡單物件訪問協議)以及XML-RPC更加簡單明瞭
(Representational State Transfer 意思是:表述性狀態傳遞)
它使用典型的HTTP方法,諸如GET,POST.DELETE,PUT來實現資源的獲取,新增,修改,刪除等操作。即通過HTTP動詞來實現資源的狀態扭轉
複製程式碼
GET 用來獲取資源
POST 用來新建資源(也可以用於更新資源)
PUT 用來更新資源
DELETE 用來刪除資源
複製程式碼
1.5 CRUL命令
以命令的方式執行HTTP協議的請求
GET/POST/PUT/DELETE
示例:
訪問一個網頁
curl www.baidu.com
curl -o tt.html www.baidu.com
顯示響應的頭資訊
curl -i www.baidu.com
顯示一次HTTP請求的通訊過程
curl -v www.baidu.com
執行GET/POST/PUT/DELETE操作
curl -X GET/POST/PUT/DELETE url
複製程式碼
1.6 CentOS7下安裝ElasticSearch6.2.4
配置JDK環境
配置環境變數
複製程式碼
export JAVA_HOME="/opt/jdk1.8.0_144"
export PATH="$JAVA_HOME/bin:$PATH"
export CLASSPATH=".:$JAVA_HOME/lib"
複製程式碼
安裝ElasticSearch6.2.4
下載地址:www.elastic.co/cn/download…
啟動報錯:
解決方式: bin/elasticsearch -Des.insecure.allow.root=true
或者修改bin/elasticsearch,加上ES_JAVA_OPTS屬性: ES_JAVA_OPTS="-Des.insecure.allow.root=true"
再次啟動:
這是出於系統安全考慮設定的條件。由於ElasticSearch可以接收使用者輸入的指令碼並且執行,為了系統安全考 慮,建議建立一個單獨的使用者用來執行ElasticSearch。
建立使用者組和使用者:
groupadd esgroup
useradd esuser -g esgroup -p espassword
更改elasticsearch資料夾及內部檔案的所屬使用者及組:
cd /opt
chown -R esuser:esgroup elasticsearch-6.2.4
切換使用者並執行:
su esuser
./bin/elasticsearch
再次啟動顯示已殺死:
需要調整JVM的記憶體大小:
vi bin/elasticsearch
ES_JAVA_OPTS="-Xms512m -Xmx512m"
再次啟動:啟動成功
如果顯示如下類似資訊:
[INFO ][o.e.c.r.a.DiskThresholdMonitor] [ZAds5FP] low disk watermark [85%] exceeded on [ZAds5FPeTY-ZUKjXd7HJKA][ZAds5FP][/opt/elasticsearch-6.2.4/data/nodes/0] free: 1.2gb[14.2%],replicas will not be assigned to this node
複製程式碼
需要清理磁碟空間。
後臺執行:./bin/elasticsearch -d
測試連線:curl 127.0.0.1:9200
會看到一下JSON資料:
[root@localhost ~]# curl 127.0.0.1:9200
{
"name" : "rBrMTNx","cluster_name" : "elasticsearch","cluster_uuid" : "-noR5DxFRsyvAFvAzxl07g","version" : {
"number" : "5.1.1","build_hash" : "5395e21","build_date" : "2016-12-06T12:36:15.409Z","build_snapshot" : false,"lucene_version" : "6.3.0"
},"tagline" : "You Know,for Search"
}
複製程式碼
實現遠端訪問: 需要對config/elasticsearch.yml進行 配置: network.host: 192.168.25.131
再次啟動報錯:
處理第一個錯誤:
vim /etc/security/limits.conf //檔案最後加入
esuser soft nofile 65536
esuser hard nofile 65536
esuser soft nproc 4096
esuser hard nproc 4096
處理第二個錯誤:
進入limits.d目錄下修改配置檔案。
vim /etc/security/limits.d/20-nproc.conf 修改為 esuser soft nproc 4096
處理第三個錯誤:
vim /etc/sysctl.conf
vm.max_map_count=655360
執行以下命令生效: sysctl -p
關閉防火牆:systemctl stop firewalld.service
再次啟動成功!
1.7安裝Head外掛
Head是elasticsearch的叢集管理工具,可以用於資料的瀏覽和查詢
(1)elasticsearch-head是一款開源軟體,被託管在github上面,所以如果我們要使用它,必須先安裝git,通過git獲取elasticsearch-head
(2)執行elasticsearch-head會用到grunt,而grunt需要npm包管理器,所以nodejs是必須要安裝的
(3)elasticsearch5.0之後,elasticsearch-head不做為外掛放在其plugins目錄下了。 使用git拷貝elasticsearch-head到本地
cd /usr/local/
git clone git://github.com/mobz/elasticsearch-head.git
(4)安裝elasticsearch-head依賴包
[root@localhost local]# npm install -g grunt-cli
[root@localhost _site]# cd /usr/local/elasticsearch-head/
[root@localhost elasticsearch-head]# cnpm install
(5)修改Gruntfile.js
[root@localhost _site]# cd /usr/local/elasticsearch-head/
[root@localhost elasticsearch-head]# vi Gruntfile.js
在connect-->server-->options下面新增:hostname:’*’,允許所有IP可以訪問
(6)修改elasticsearch-head預設連線地址 [root@localhost elasticsearch-head]# cd /usr/local/elasticsearch-head/_site/
[root@localhost _site]# vi app.js
將this.base_uri = this.config.base_uri || this.prefs.get("app-base_uri") || "http://localhost:9200";中的localhost修改成你es的伺服器地址
(7)配置elasticsearch允許跨域訪問
開啟elasticsearch的配置檔案elasticsearch.yml,在檔案末尾追加下面兩行程式碼即可:
http.cors.enabled: true
http.cors.allow-origin: "*"
(8)開啟9100埠
[root@localhost elasticsearch-head]# firewall-cmd --zone=public --add-port=9100/tcp --permanent
重啟防火牆
[root@localhost elasticsearch-head]# firewall-cmd --reload
(9)啟動elasticsearch
(10)啟動elasticsearch-head
[root@localhost _site]# cd /usr/local/elasticsearch-head/
[root@localhost elasticsearch-head]# node_modules/grunt/bin/grunt server
(11)訪問elasticsearch-head
關閉防火牆:systemctl stop firewalld.service
瀏覽器輸入網址:http://192.168.25.131:9100/
1.8安裝Kibana
Kibana是一個針對Elasticsearch的開源分析及視覺化平臺,使用Kibana可以查詢、檢視並與儲存在ES索引的資料進行互動操作,使用Kibana能執行高階的資料分析,並能以圖表、表格和地圖的形式檢視資料
(1)下載Kibana www.elastic.co/downloads/k…
(2)把下載好的壓縮包拷貝到/soft目錄下
(3)解壓縮,並把解壓後的目錄移動到/user/local/kibana
(4)編輯kibana配置檔案
[root@localhost /]# vi /usr/local/kibana/config/kibana.yml
將server.host,elasticsearch.url修改成所在伺服器的ip地址
(5)開啟5601埠
Kibana的預設埠是5601
開啟防火牆:systemctl start firewalld.service
開啟5601埠:firewall-cmd --permanent --zone=public --add-port=5601/tcp
重啟防火牆:firewall-cmd –reload
(6)啟動Kibana
[root@localhost /]# /usr/local/kibana/bin/kibana
瀏覽器訪問:http://192.168.25.131:5601
1.9安裝中文分詞器
(1)下載中文分詞器 github.com/medcl/elast…
下載elasticsearch-analysis-ik-master.zip
複製程式碼
(2)解壓elasticsearch-analysis-ik-master.zip
unzip elasticsearch-analysis-ik-master.zip
(3)進入elasticsearch-analysis-ik-master,編譯原始碼
mvn clean install -Dmaven.test.skip=true
(4)在es的plugins資料夾下建立目錄ik
(5)將編譯後生成的elasticsearch-analysis-ik-版本.zip移動到ik下,並解壓
(6)解壓後的內容移動到ik目錄下
第二節 ElasticSearch基本操作
2.1倒排索引
Elasticsearch 使用一種稱為 倒排索引 的結構,它適用於快速的全文搜尋。一個倒排索引由檔案中所有不重複詞的列表構成,對於其中每個詞,有一個包含它的檔案列表。
示例:
(1):假設檔案集合包含五個檔案,每個檔案內容如圖所示,在圖中最左端一欄是每個檔案對應的檔案編號。我們的任務就是對這個檔案集合建立倒排索引。
(2):中文和英文等語言不同,單詞之間沒有明確分隔符號,所以首先要用分詞系統將檔案自動切分成單詞序列。這樣每個檔案就轉換為由單詞序列構成的資料流,為了系統後續處理方便,需要對每個不同的單詞賦予唯一的單詞編號,同時記錄下哪些檔案包含這個單詞,在如此處理結束後,我們可以得到最簡單的倒排索引
“單詞ID”一欄記錄了每個單詞的單詞編號,第二欄是對應的單詞,第三欄即每個單詞對應的倒排列表(3):索引系統還可以記錄除此之外的更多資訊,下圖還記載了單詞頻率資訊(TF)即這個單詞在某個檔案中的出現次數,之所以要記錄這個資訊,是因為詞頻資訊在搜尋結果排序時,計算查詢和檔案相似度是很重要的一個計算因子,所以將其記錄在倒排列表中,以方便後續排序時進行分值計算。
(4):倒排列表中還可以記錄單詞在某個檔案出現的位置資訊
(1,<11>,1),(2,<7>,(3,<3,9>,2)
有了這個索引系統,搜尋引擎可以很方便地響應使用者的查詢,比如使用者輸入查詢詞“Facebook”,搜尋系統查詢倒排索引,從中可以讀出包含這個單詞的檔案,這些檔案就是提供給使用者的搜尋結果,而利用單詞頻率資訊、檔案頻率資訊即可以對這些候選搜尋結果進行排序,計算檔案和查詢的相似性,按照相似性得分由高到低排序輸出,此即為搜尋系統的部分內部流程。
2.1.2 倒排索引原理
1.The quick brown fox jumped over the lazy dog
2.Quick brown foxes leap over lazy dogs in summer
倒排索引:
Term | Doc_1 | Doc_2 |
---|---|---|
Quick | X | |
The | X | |
brown | X | X |
dog | X | |
dogs | X | |
fox | X | |
foxes | X | |
in | X | |
jumped | X | |
lazy | X | X |
leap | X | |
over | X | X |
quick | X | |
summer | X | |
the | X |
搜尋quick brown :
Term | Doc_1 | Doc_2 |
---|---|---|
brown | X | X |
quick | X | |
Total | 2 | 1 |
計算相關度分數時,檔案1的匹配度高,分數會比檔案2高
問題:
Quick 和 quick 以獨立的詞條出現,然而使用者可能認為它們是相同的詞。
fox 和 foxes 非常相似,就像 dog 和 dogs ;他們有相同的詞根。
jumped 和 leap,儘管沒有相同的詞根,但他們的意思很相近。他們是同義詞。
搜尋含有 Quick fox的檔案是搜尋不到的
使用標準化規則(normalization): 建立倒排索引的時候,會對拆分出的各個單詞進行相應的處理,以提升後面搜尋的時候能夠搜尋到相關聯的檔案的概率
Term | Doc_1 | Doc_2 |
---|---|---|
brown | X | X |
dog | X | X |
fox | X | X |
in | X | |
jump | X | X |
lazy | X | X |
over | X | X |
quick | X | X |
summer | X | |
the | X | X |
2.1.3 分詞器介紹及內建分詞器
分詞器:從一串文字中切分出一個一個的詞條,並對每個詞條進行標準化
包括三部分:
character filter:分詞之前的預處理,過濾掉HTML標籤,特殊符號轉換等
tokenizer:分詞
token filter:標準化
內建分詞器:
standard 分詞器:(預設的)他會將詞彙單元轉換成小寫形式,並去除停用詞和標點符號,支援中文采用的方法為單字切分
simple 分詞器:首先會通過非字母字元來分割文字資訊,然後將詞彙單元統一為小寫形式。該分析器會去掉數字型別的字元。
Whitespace 分詞器:僅僅是去除空格,對字元沒有lowcase化,不支援中文; 並且不對生成的詞彙單元進行其他的標準化處理。
language 分詞器:特定語言的分詞器,不支援中文
2.2 使用ElasticSearch API 實現CRUD
新增索引:
PUT /lib/
{
"settings":{
"index":{
"number_of_shards": 5,"number_of_replicas": 1
}
}
}
PUT lib
複製程式碼
檢視索引資訊:
GET /lib/_settings
GET _all/_settings
複製程式碼
新增檔案:
PUT /lib/user/1
{
"first_name" : "Jane","last_name" : "Smith","age" : 32,"about" : "I like to collect rock albums","interests": [ "music" ]
}
POST /lib/user/
{
"first_name" : "Douglas","last_name" : "Fir","age" : 23,"about": "I like to build cabinets","interests": [ "forestry" ]
}
複製程式碼
檢視檔案:
GET /lib/user/1
GET /lib/user/
GET /lib/user/1?_source=age,interests
複製程式碼
更新檔案:
PUT /lib/user/1
{
"first_name" : "Jane","age" : 36,"interests": [ "music" ]
}
POST /lib/user/1/_update
{
"doc":{
"age":33
}
}
複製程式碼
刪除一個檔案
DELETE /lib/user/1
複製程式碼
刪除一個索引
DELETE /lib
複製程式碼
2.3 批量獲取檔案
使用es提供的Multi Get API:
使用Multi Get API可以通過索引名、型別名、檔案id一次得到一個檔案集合,檔案可以來自同一個索引庫,也可以來自不同索引庫
使用curl命令:
curl 'http://192.168.25.131:9200/_mget' -d '{
"docs":[
{
"_index": "lib","_type": "user","_id": 1
},{
"_index": "lib","_id": 2
}
]
}'
複製程式碼
在客戶端工具中:
GET /_mget
{
"docs":[
{
"_index": "lib","_type": "user","_id": 1
},{
"_index": "lib","_id": 2
},"_id": 3
}
]
}
複製程式碼
可以指定具體的欄位:
GET /_mget
{
"docs":[
{
"_index": "lib","_id": 1,"_source": "interests"
},"_id": 2,"_source": ["age","interests"]
}
]
}
複製程式碼
獲取同索引同型別下的不同檔案:
GET /lib/user/_mget
{
"docs":[
{
"_id": 1
},{
"_type": "user",}
]
}
GET /lib/user/_mget
{
"ids": ["1","2"]
}
複製程式碼
2.4 使用Bulk API 實現批量操作
bulk的格式:
{action:{metadata}}\n
{requstbody}\n
action:(行為)
create:檔案不存在時建立
update:更新檔案
index:建立新檔案或替換已有檔案
delete:刪除一個檔案
metadata:_index,_type,_id
複製程式碼
create 和index的區別
如果資料存在,使用create操作失敗,會提示檔案已經存在,使用index則可以成功執行。
示例:
{"delete":{"_index":"lib","_type":"user","_id":"1"}}
複製程式碼
批量新增:
POST /lib2/books/_bulk
{"index":{"_id":1}}
{"title":"Java","price":55}
{"index":{"_id":2}}
{"title":"Html5","price":45}
{"index":{"_id":3}}
{"title":"Php","price":35}
{"index":{"_id":4}}
{"title":"Python","price":50}
複製程式碼
批量獲取:
GET /lib2/books/_mget
{
"ids": ["1","2","3","4"]
}
複製程式碼
刪除:沒有請求體
POST /lib2/books/_bulk
{"delete":{"_index":"lib2","_type":"books","_id":4}}
{"create":{"_index":"tt","_type":"ttt","_id":"100"}}
{"name":"lisi"}
{"index":{"_index":"tt","_type":"ttt"}}
{"name":"zhaosi"}
{"update":{"_index":"lib2","_id":"4"}}
{"doc":{"price":58}}
複製程式碼
bulk一次最大處理多少資料量:
bulk會把將要處理的資料載入記憶體中,所以資料量是有限制的,最佳的資料量不是一個確定的數值,它取決於你的硬體,你的檔案大小以及複雜性,你的索引以及搜尋的負載。
一般建議是1000-5000個檔案,大小建議是5-15MB,預設不能超過100M,可以在es的配置檔案(即$ES_HOME下的config下的elasticsearch.yml)中。
2.5 版本控制
ElasticSearch採用了樂觀鎖來保證資料的一致性,也就是說,當使用者對document進行操作時,並不需要對該document作加鎖和解鎖的操作,只需要指定要操作的版本即可。當版本號一致時,ElasticSearch會允許該操作順利執行,而當版本號存在衝突時,ElasticSearch會提示衝突並丟擲異常(VersionConflictEngineException異常)。
ElasticSearch的版本號的取值範圍為1到2^63-1。
內部版本控制:使用的是_version
外部版本控制:elasticsearch在處理外部版本號時會與對內部版本號的處理有些不同。它不再是檢查_version是否與請求中指定的數值_相同_,而是檢查當前的_version是否比指定的數值小。如果請求成功,那麼外部的版本號就會被儲存到檔案中的_version中。
為了保持_version與外部版本控制的資料一致 使用version_type=external
2.6 什麼是Mapping
PUT /myindex/article/1
{
"post_date": "2018-05-10","title": "Java","content": "java is the best language","author_id": 119
}
PUT /myindex/article/2
{
"post_date": "2018-05-12","title": "html","content": "I like html","author_id": 120
}
PUT /myindex/article/3
{
"post_date": "2018-05-16","title": "es","content": "Es is distributed document store","author_id": 110
}
GET /myindex/article/_search?q=2018-05
GET /myindex/article/_search?q=2018-05-10
GET /myindex/article/_search?q=html
GET /myindex/article/_search?q=java
#檢視es自動建立的mapping
GET /myindex/article/_mapping
複製程式碼
es自動建立了index,type,以及type對應的mapping(dynamic mapping)
什麼是對映:mapping定義了type中的每個欄位的資料型別以及這些欄位如何分詞等相關屬性
{
"myindex": {
"mappings": {
"article": {
"properties": {
"author_id": {
"type": "long"
},"content": {
"type": "text","fields": {
"keyword": {
"type": "keyword","ignore_above": 256
}
}
},"post_date": {
"type": "date"
},"title": {
"type": "text","ignore_above": 256
}
}
}
}
}
}
}
}
複製程式碼
建立索引的時候,可以預先定義欄位的型別以及相關屬性,這樣就能夠把日期欄位處理成日期,把數字欄位處理成數字,把字串欄位處理字串值等
支援的資料型別:
(1)核心資料型別(Core datatypes)
字元型:string,string型別包括
text 和 keyword
text型別被用來索引長文字,在建立索引前會將這些文字進行分詞,轉化為詞的組合,建立索引。允許es來檢索這些詞語。text型別不能用來排序和聚合。
Keyword型別不需要進行分詞,可以被用來檢索過濾、排序和聚合。keyword 型別欄位只能用本身來進行檢索
數字型:long,integer,short,byte,double,float
日期型:date
布林型:boolean
二進位制型:binary
複製程式碼
(2)複雜資料型別(Complex datatypes)
陣列型別(Array datatype):陣列型別不需要專門指定陣列元素的type,例如:
字元型陣列: [ "one","two" ]
整型陣列:[ 1,2 ]
陣列型陣列:[ 1,[ 2,3 ]] 等價於[ 1,2,3 ]
物件陣列:[ { "name": "Mary","age": 12 },{ "name": "John","age": 10 }]
物件型別(Object datatype):_ object _ 用於單個JSON物件;
巢狀型別(Nested datatype):_ nested _ 用於JSON陣列;
複製程式碼
(3)地理位置型別(Geo datatypes)
地理座標型別(Geo-point datatype):_ geo_point _ 用於經緯度座標;
地理形狀型別(Geo-Shape datatype):_ geo_shape _ 用於類似於多邊形的複雜形狀;
複製程式碼
(4)特定型別(Specialised datatypes)
IPv4 型別(IPv4 datatype):_ ip _ 用於IPv4 地址;
Completion 型別(Completion datatype):_ completion _提供自動補全建議;
Token count 型別(Token count datatype):_ token_count _ 用於統計做了標記的欄位的index數目,該值會一直增加,不會因為過濾條件而減少。
mapper-murmur3
型別:通過外掛,可以通過 _ murmur3 _ 來計算 index 的 hash 值;
附加型別(Attachment datatype):採用 mapper-attachments
外掛,可支援_ attachments _ 索引,例如 Microsoft Office 格式,Open Document 格式,ePub,HTML 等。
複製程式碼
支援的屬性:
"store":false//是否單獨設定此欄位的是否儲存而從_source欄位中分離,預設是false,只能搜尋,不能獲取值
"index": true//分詞,不分詞是:false ,設定成false,欄位將不會被索引
"analyzer":"ik"//指定分詞器,預設分詞器為standard analyzer
"boost":1.23//欄位級別的分數加權,預設值是1.0
"doc_values":false//對not_analyzed欄位,預設都是開啟,分詞欄位不能使用,對排序和聚合能提升較大效能,節約記憶體
"fielddata":{"format":"disabled"}//針對分詞欄位,參與排序或聚合時能提高效能,不分詞欄位統一建議使用doc_value
"fields":{"raw":{"type":"string","index":"not_analyzed"}} //可以對一個欄位提供多種索引模式,同一個欄位的值,一個分詞,一個不分詞
"ignore_above":100 //超過100個字元的文字,將會被忽略,不被索引
"include_in_all":ture//設定是否此欄位包含在_all欄位中,預設是true,除非index設定成no選項
"index_options":"docs"//4個可選引數docs(索引檔案號),freqs(檔案號+詞頻),positions(檔案號+詞頻+位置,通常用來距離查詢),offsets(檔案號+詞頻+位置+偏移量,通常被使用在高亮欄位)分詞欄位預設是position,其他的預設是docs
"norms":{"enable":true,"loading":"lazy"}//分詞欄位預設配置,不分詞欄位:預設{"enable":false},儲存長度因子和索引時boost,建議對需要參與評分欄位使用 ,會額外增加記憶體消耗量
"null_value":"NULL"//設定一些缺失欄位的初始化值,只有string可以使用,分詞欄位的null值也會被分詞
"position_increament_gap":0//影響距離查詢或近似查詢,可以設定在多值欄位的資料上火分詞欄位上,查詢時可指定slop間隔,預設值是100
"search_analyzer":"ik"//設定搜尋時的分詞器,預設跟ananlyzer是一致的,比如index時用standard+ngram,搜尋時用standard用來完成自動提示功能
"similarity":"BM25"//預設是TF/IDF演演算法,指定一個欄位評分策略,僅僅對字串型和分詞型別有效
"term_vector":"no"//預設不儲存向量資訊,支援引數yes(term儲存),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量) 對快速高亮fast vector highlighter能提升效能,但開啟又會加大索引體積,不適合大資料量用
對映的分類:
(1)動態對映:
當ES在檔案中碰到一個以前沒見過的欄位時,它會利用動態對映來決定該欄位的型別,並自動地對該欄位新增對映。
可以通過dynamic設定來控制這一行為,它能夠接受以下的選項:
true:預設值。動態新增欄位
false:忽略新欄位
strict:如果碰到陌生欄位,丟擲異常
複製程式碼
dynamic設定可以適用在根物件上或者object型別的任意欄位上。
POST /lib2
#給索引lib2建立對映型別
{
"settings":{
"number_of_shards" : 3,"number_of_replicas" : 0
},"mappings":{
"books":{
"properties":{
"title":{"type":"text"},"name":{"type":"text","index":false},"publish_date":{"type":"date","price":{"type":"double"},"number":{"type":"integer"}
}
}
}
}
複製程式碼
POST /lib2
#給索引lib2建立對映型別
{
"settings":{
"number_of_shards" : 3,"number":{
"type":"object","dynamic":true
}
}
}
}
}
複製程式碼
2.7 基本查詢(Query查詢)
2.7.1 資料準備
PUT /lib3
{
"settings":{
"number_of_shards" : 3,"number_of_replicas" : 0
},"mappings":{
"user":{
"properties":{
"name": {"type":"text"},"address": {"type":"text"},"age": {"type":"integer"},"interests": {"type":"text"},"birthday": {"type":"date"}
}
}
}
}
GET /lib3/user/_search?q=name:lisi
GET /lib3/user/_search?q=name:zhaoliu&sort=age:desc
複製程式碼
2.7.2 term查詢和terms查詢
term query會去倒排索引中尋找確切的term,它並不知道分詞器的存在。這種查詢適合keyword 、numeric、date。
term:查詢某個欄位裡含有某個關鍵詞的檔案
GET /lib3/user/_search/
{
"query": {
"term": {"interests": "changge"}
}
}
複製程式碼
terms:查詢某個欄位裡含有多個關鍵詞的檔案
GET /lib3/user/_search
{
"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
複製程式碼
2.7.3 控制查詢返回的數量
from:從哪一個檔案開始 size:需要的個數
GET /lib3/user/_search
{
"from":0,"size":2,"query":{
"terms":{
"interests": ["hejiu","changge"]
}
}
}
複製程式碼
2.7.4 返回版本號
GET /lib3/user/_search
{
"version":true,"changge"]
}
}
}
複製程式碼
2.7.5 match查詢
match query知道分詞器的存在,會對filed進行分詞操作,然後再查詢
GET /lib3/user/_search
{
"query":{
"match":{
"name": "zhaoliu"
}
}
}
GET /lib3/user/_search
{
"query":{
"match":{
"age": 20
}
}
}
複製程式碼
match_all:查詢所有檔案
GET /lib3/user/_search
{
"query": {
"match_all": {}
}
}
複製程式碼
multi_match:可以指定多個欄位
GET /lib3/user/_search
{
"query":{
"multi_match": {
"query": "lvyou","fields": ["interests","name"]
}
}
}
複製程式碼
match_phrase:短語匹配查詢
ElasticSearch引擎首先分析(analyze)查詢字串,從分析後的文字中構建短語查詢,這意味著必須匹配短語中的所有分詞,並且保證各個分詞的相對位置不變:
GET lib3/user/_search
{
"query":{
"match_phrase":{
"interests": "duanlian,shuoxiangsheng"
}
}
}
複製程式碼
2.7.6 指定返回的欄位
GET /lib3/user/_search
{
"_source": ["address","name"],"query": {
"match": {
"interests": "changge"
}
}
}
複製程式碼
2.7.7 控制載入的欄位
GET /lib3/user/_search
{
"query": {
"match_all": {}
},"_source": {
"includes": ["name","address"],"excludes": ["age","birthday"]
}
}
複製程式碼
使用萬用字元*
GET /lib3/user/_search
{
"_source": {
"includes": "addr*","excludes": ["name","bir*"]
},"query": {
"match_all": {}
}
}
複製程式碼
2.7.8 排序
使用sort實現排序: desc:降序,asc升序
GET /lib3/user/_search
{
"query": {
"match_all": {}
},"sort": [
{
"age": {
"order":"asc"
}
}
]
}
GET /lib3/user/_search
{
"query": {
"match_all": {}
},"sort": [
{
"age": {
"order":"desc"
}
}
]
}
複製程式碼
2.7.9 字首匹配查詢
GET /lib3/user/_search
{
"query": {
"match_phrase_prefix": {
"name": {
"query": "zhao"
}
}
}
}
複製程式碼
2.7.10 範圍查詢
range:實現範圍查詢
引數:from,to,include_lower,include_upper,boost
include_lower:是否包含範圍的左邊界,預設是true
include_upper:是否包含範圍的右邊界,預設是true
GET /lib3/user/_search
{
"query": {
"range": {
"birthday": {
"from": "1990-10-10","to": "2018-05-01"
}
}
}
}
GET /lib3/user/_search
{
"query": {
"range": {
"age": {
"from": 20,"to": 25,"include_lower": true,"include_upper": false
}
}
}
}
複製程式碼
2.7.11 wildcard查詢
允許使用萬用字元* 和 ?來進行查詢
*代表0個或多個字元
?代表任意一個字元
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "zhao*"
}
}
}
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "li?i"
}
}
}
複製程式碼
2.7.12 fuzzy實現模糊查詢
value:查詢的關鍵字
boost:查詢的權值,預設值是1.0
min_similarity:設定匹配的最小相似度,預設值為0.5,對於字串,取值為0-1(包括0和1);對於數值,取值可能大於1;對於日期型取值為1d,1m等,1d就代表1天
prefix_length:指明區分詞項的共同字首長度,預設是0
max_expansions:查詢中的詞項可以擴充套件的數目,預設可以無限大
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": "chagge"
}
}
}
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": {
"value": "chagge"
}
}
}
}
複製程式碼
2.7.13 高亮搜尋結果
GET /lib3/user/_search
{
"query":{
"match":{
"interests": "changge"
}
},"highlight": {
"fields": {
"interests": {}
}
}
}
複製程式碼
2.8 Filter查詢
filter是不計算相關性的,同時可以cache。因此,filter速度要快於query。
POST /lib4/items/_bulk
{"index": {"_id": 1}}
{"price": 40,"itemID": "ID100123"}
{"index": {"_id": 2}}
{"price": 50,"itemID": "ID100124"}
{"index": {"_id": 3}}
{"price": 25,"itemID": "ID100124"}
{"index": {"_id": 4}}
{"price": 30,"itemID": "ID100125"}
{"index": {"_id": 5}}
{"price": null,"itemID": "ID100127"}
複製程式碼
####2.8.1 簡單的過濾查詢
GET /lib4/items/_search
{
"post_filter": {
"term": {
"price": 40
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"terms": {
"price": [25,40]
}
}
}
GET /lib4/items/_search
{
"post_filter": {
"term": {
"itemID": "ID100123"
}
}
}
複製程式碼
檢視分詞器分析的結果:
GET /lib4/_mapping
複製程式碼
不希望商品id欄位被分詞,則重新建立對映
DELETE lib4
PUT /lib4
{
"mappings": {
"items": {
"properties": {
"itemID": {
"type": "text","index": false
}
}
}
}
}
複製程式碼
2.8.2 bool過濾查詢
可以實現組合過濾查詢
格式:
{
"bool": {
"must": [],"should": [],"must_not": []
}
}
複製程式碼
must:必須滿足的條件---and
should:可以滿足也可以不滿足的條件--or
must_not:不需要滿足的條件--not
GET /lib4/items/_search
{
"post_filter": {
"bool": {
"should": [
{"term": {"price":25}},{"term": {"itemID": "id100123"}}
],"must_not": {
"term":{"price": 30}
}
}
}
}
複製程式碼
巢狀使用bool:
GET /lib4/items/_search
{
"post_filter": {
"bool": {
"should": [
{"term": {"itemID": "id100123"}},{
"bool": {
"must": [
{"term": {"itemID": "id100124"}},{"term": {"price": 40}}
]
}
}
]
}
}
}
複製程式碼
2.8.3 範圍過濾
gt: >
lt: <
gte: >=
lte: <=
GET /lib4/items/_search
{
"post_filter": {
"range": {
"price": {
"gt": 25,"lt": 50
}
}
}
}
複製程式碼
2.8.5 過濾非空
GET /lib4/items/_search
{
"query": {
"bool": {
"filter": {
"exists":{
"field":"price"
}
}
}
}
}
GET /lib4/items/_search
{
"query" : {
"constant_score" : {
"filter": {
"exists" : { "field" : "price" }
}
}
}
}
複製程式碼
2.8.6 過濾器快取
ElasticSearch提供了一種特殊的快取,即過濾器快取(filter cache),用來儲存過濾器的結果,被快取的過濾器並不需要消耗過多的記憶體(因為它們只儲存了哪些檔案能與過濾器相匹配的相關資訊),而且可供後續所有與之相關的查詢重複使用,從而極大地提高了查詢效能。
注意:ElasticSearch並不是預設快取所有過濾器, 以下過濾器預設不快取:
numeric_range
script
geo_bbox
geo_distance
geo_distance_range
geo_polygon
geo_shape
and
or
not
複製程式碼
exists,missing,range,term,terms預設是開啟快取的
開啟方式:在filter查詢語句後邊加上 "_catch":true
2.9 聚合查詢
(1)sum
GET /lib4/items/_search
{
"size":0,"aggs": {
"price_of_sum": {
"sum": {
"field": "price"
}
}
}
}
複製程式碼
(2)min
GET /lib4/items/_search
{
"size": 0,"aggs": {
"price_of_min": {
"min": {
"field": "price"
}
}
}
}
複製程式碼
(3)max
GET /lib4/items/_search
{
"size": 0,"aggs": {
"price_of_max": {
"max": {
"field": "price"
}
}
}
}
複製程式碼
(4)avg
GET /lib4/items/_search
{
"size":0,"aggs": {
"price_of_avg": {
"avg": {
"field": "price"
}
}
}
}
複製程式碼
(5)cardinality:求基數
GET /lib4/items/_search
{
"size":0,"aggs": {
"price_of_cardi": {
"cardinality": {
"field": "price"
}
}
}
}
複製程式碼
(6)terms:分組
GET /lib4/items/_search
{
"size":0,"aggs": {
"price_group_by": {
"terms": {
"field": "price"
}
}
}
}
複製程式碼
對那些有唱歌興趣的使用者按年齡分組
GET /lib3/user/_search
{
"query": {
"match": {
"interests": "changge"
}
},"size": 0,"aggs":{
"age_group_by":{
"terms": {
"field": "age","order": {
"avg_of_age": "desc"
}
},"aggs": {
"avg_of_age": {
"avg": {
"field": "age"
}
}
}
}
}
}
複製程式碼
2.10 複合查詢
將多個基本查詢組合成單一查詢的查詢
2.10.1 使用bool查詢
接收以下引數:
must: 檔案 必須匹配這些條件才能被包含進來。
must_not: 檔案 必須不匹配這些條件才能被包含進來。
should: 如果滿足這些語句中的任意語句,將增加 _score,否則,無任何影響。它們主要用於修正每個檔案的相關性得分。
filter: 必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含檔案。
相關性得分是如何組合的。每一個子查詢都獨自地計算檔案的相關性得分。一旦他們的得分被計算出來, bool 查詢就將這些得分進行合併並且返回一個代表整個布林操作的得分。
下面的查詢用於查詢 title 欄位匹配 how to make millions 並且不被標識為 spam 的檔案。那些被標識為 starred 或在2014之後的檔案,將比另外那些檔案擁有更高的排名。如果 兩者 都滿足,那麼它排名將更高:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},"must_not": { "match": { "tag": "spam" }},"should": [
{ "match": { "tag": "starred" }},{ "range": { "date": { "gte": "2014-01-01" }}}
]
}
}
複製程式碼
如果沒有 must 語句,那麼至少需要能夠匹配其中的一條 should 語句。但,如果存在至少一條 must 語句,則對 should 語句的匹配沒有要求。 如果我們不想因為檔案的時間而影響得分,可以用 filter 語句來重寫前面的例子:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},"should": [
{ "match": { "tag": "starred" }}
],"filter": {
"range": { "date": { "gte": "2014-01-01" }}
}
}
}
複製程式碼
通過將 range 查詢移到 filter 語句中,我們將它轉成不評分的查詢,將不再影響檔案的相關性排名。由於它現在是一個不評分的查詢,可以使用各種對 filter 查詢有效的優化手段來提升效能。
bool 查詢本身也可以被用做不評分的查詢。簡單地將它放置到 filter 語句中並在內部構建布林邏輯:
{
"bool": {
"must": { "match": { "title": "how to make millions" }},"filter": {
"bool": {
"must": [
{ "range": { "date": { "gte": "2014-01-01" }}},{ "range": { "price": { "lte": 29.99 }}}
],"must_not": [
{ "term": { "category": "ebooks" }}
]
}
}
}
}
複製程式碼
2.10.2 constant_score查詢
它將一個不變的常量評分應用於所有匹配的檔案。它被經常用於你只需要執行一個 filter 而沒有其它查詢(例如,評分查詢)的情況下。
{
"constant_score": {
"filter": {
"term": { "category": "ebooks" }
}
}
}
複製程式碼
term 查詢被放置在 constant_score 中,轉成不評分的filter。這種方式可以用來取代只有 filter 語句的 bool 查詢。
第三節 ElasticSearch原理
3.1 解析es的分散式架構
3.1.1 分散式架構的透明隱藏特性
ElasticSearch是一個分散式系統,隱藏了複雜的處理機制
分片機制:我們不用關心資料是按照什麼機制分片的、最後放入到哪個分片中
分片的副本:
叢集發現機制(cluster discovery):比如當前我們啟動了一個es程式,當啟動了第二個es程式時,這個程式作為一個node自動就發現了叢集,並且加入了進去
shard負載均衡:比如現在有10shard,叢集中有3個節點,es會進行均衡的進行分配,以保持每個節點均衡的負載請求
請求路由
3.1.2 擴容機制
垂直擴容:購置新的機器,替換已有的機器
水平擴容:直接增加機器
3.1.3 rebalance
增加或減少節點時會自動均衡
3.1.4 master節點
主節點的主要職責是和叢集操作相關的內容,如建立或刪除索引,跟蹤哪些節點是群集的一部分,並決定哪些分片分配給相關的節點。穩定的主節點對叢集的健康是非常重要的。
3.1.5 節點對等
每個節點都能接收請求 每個節點接收到請求後都能把該請求路由到有相關資料的其它節點上 接收原始請求的節點負責採集資料並返回給客戶端
3.2 分片和副本機制
1.index包含多個shard
2.每個shard都是一個最小工作單元,承載部分資料;每個shard都是一個lucene例項,有完整的建立索引和處理請求的能力
3.增減節點時,shard會自動在nodes中負載均衡
4.primary shard和replica shard,每個document肯定只存在於某一個primary shard以及其對應的replica shard中,不可能存在於多個primary shard
5.replica shard是primary shard的副本,負責容錯,以及承擔讀請求負載
6.primary shard的數量在建立索引的時候就固定了,replica shard的數量可以隨時修改
7.primary shard的預設數量是5,replica預設是1,預設有10個shard,5個primary shard,5個replica shard
8.primary shard不能和自己的replica shard放在同一個節點上(否則節點宕機,primary shard和副本都丟失,起不到容錯的作用),但是可以和其他primary shard的replica shard放在同一個節點上
3.3 單節點環境下建立索引分析
PUT /myindex
{
"settings" : {
"number_of_shards" : 3,"number_of_replicas" : 1
}
}
複製程式碼
這個時候,只會將3個primary shard分配到僅有的一個node上去,另外3個replica shard是無法分配的(一個shard的副本replica,他們兩個是不能在同一個節點的)。叢集可以正常工作,但是一旦出現節點宕機,資料全部丟失,而且叢集不可用,無法接收任何請求。
3.4 兩個節點環境下建立索引分析
將3個primary shard分配到一個node上去,另外3個replica shard分配到另一個節點上
primary shard 和replica shard 保持同步
primary shard 和replica shard 都可以處理客戶端的讀請求
3.5 水平擴容的過程
1.擴容後primary shard和replica shard會自動的負載均衡
2.擴容後每個節點上的shard會減少,那麼分配給每個shard的CPU,記憶體,IO資源會更多,效能提高
3.擴容的極限,如果有6個shard,擴容的極限就是6個節點,每個節點上一個shard,如果想超出擴容的極限,比如說擴容到9個節點,那麼可以增加replica shard的個數
4.6個shard,3個節點,最多能承受幾個節點所在的伺服器宕機?(容錯性) 任何一臺伺服器宕機都會丟失部分資料
為了提高容錯性,增加shard的個數: 9個shard,(3個primary shard,6個replicashard),這樣就能容忍最多兩臺伺服器宕機了
總結:擴容是為了提高系統的吞吐量,同時也要考慮容錯性,也就是讓儘可能多的伺服器宕機還能保證資料不丟失
3.6 ElasticSearch的容錯機制
以9個shard,3個節點為例:
1.如果master node 宕機,此時不是所有的primary shard都是Active status,所以此時的叢集狀態是red。
容錯處理的第一步:是選舉一臺伺服器作為master 容錯處理的第二步:新選舉出的master會把掛掉的primary shard的某個replica shard 提升為primary shard,此時叢集的狀態為yellow,因為少了一個replica shard,並不是所有的replica shard都是active status
容錯處理的第三步:重啟故障機,新master會把所有的副本都複製一份到該節點上,(同步一下宕機後發生的修改),此時叢集的狀態為green,因為所有的primary shard和replica shard都是Active status
3.7 檔案的核心元資料
1._index:
說明瞭一個檔案儲存在哪個索引中
同一個索引下存放的是相似的檔案(檔案的field多數是相同的)
索引名必須是小寫的,不能以下劃線開頭,不能包括逗號
2._type:
表示檔案屬於索引中的哪個型別
一個索引下只能有一個type
型別名可以是大寫也可以是小寫的,不能以下劃線開頭,不能包括逗號
3._id:
檔案的唯一標識,和索引,型別組合在一起唯一標識了一個檔案
可以手動指定值,也可以由es來生成這個值
3.8 檔案id生成方式
1.手動指定
put /index/type/66
複製程式碼
通常是把其它系統的已有資料匯入到es時
2.由es生成id值
post /index/type
複製程式碼
es生成的id長度為20個字元,使用的是base64編碼,URL安全,使用的是GUID演演算法,分散式下併發生成id值時不會衝突
3.9 _source元資料分析
其實就是我們在新增檔案時request body中的內容
指定返回的結果中含有哪些欄位:
get /index/type/1?_source=name
複製程式碼
3.10 改變檔案內容原理解析
替換方式:
PUT /lib/user/4
{ "first_name" : "Jane","last_name" : "Lucy","age" : 24,"interests": [ "music" ]
}
複製程式碼
修改方式(partial update):
POST /lib/user/2/_update
{
"doc":{
"age":26
}
}
複製程式碼
刪除檔案:標記為deleted,隨著資料量的增加,es會選擇合適的時間刪除掉
3.11 基於groovy指令碼執行partial update
es有內建的指令碼支援,可以基於groovy指令碼實現複雜的操作
1.修改年齡
POST /lib/user/4/_update
{
"script": "ctx._source.age+=1"
}
複製程式碼
2.修改名字
POST /lib/user/4/_update
{
"script": "ctx._source.last_name+='hehe'"
}
複製程式碼
3.新增愛好
POST /lib/user/4/_update
{
"script": {
"source": "ctx._source.interests.add(params.tag)","params": {
"tag":"picture"
}
}
}
複製程式碼
4.刪除愛好
POST /lib/user/4/_update
{
"script": {
"source": "ctx._source.interests.remove(ctx._source.interests.indexOf(params.tag))","params": {
"tag":"picture"
}
}
}
複製程式碼
5.刪除檔案
POST /lib/user/4/_update
{
"script": {
"source": "ctx.op=ctx._source.age==params.count?'delete':'none'","params": {
"count":29
}
}
}
6.upsert
POST /lib/user/4/_update
{
"script": "ctx._source.age += 1","upsert": {
"first_name" : "Jane","age" : 20,"interests": [ "music" ]
}
}
複製程式碼
3.12 partial update 處理併發衝突
使用的是樂觀鎖:_version
retry_on_conflict:
POST /lib/user/4/_update?retry_on_conflict=3
重新獲取檔案資料和版本資訊進行更新,不斷的操作,最多操作的次數就是retry_on_conflict的值
3.13 檔案資料路由原理解析
1.檔案路由到分片上:
一個索引由多個分片構成,當新增(刪除,修改)一個檔案時,es就需要決定這個檔案儲存在哪個分片上,這個過程就稱為資料路由(routing)
2.路由演演算法:
shard=hash(routing) % number_of_pirmary_shards
複製程式碼
示例:一個索引,3個primary shard
(1)每次增刪改查時,都有一個routing值,預設是檔案的_id的值
(2)對這個routing值使用雜湊函式進行計算
(3)計算出的值再和主分片個數取餘數
餘數肯定在0---(number_of_pirmary_shards-1)之間,檔案就在對應的shard上
routing值預設是檔案的_id的值,也可以手動指定一個值,手動指定對於負載均衡以及提高批量讀取的效能都有幫助
3.primary shard個數一旦確定就不能修改了
3.14 檔案增刪改內部原理
1:傳送增刪改請求時,可以選擇任意一個節點,該節點就成了協調節點(coordinating node)
2.協調節點使用路由演演算法進行路由,然後將請求轉到primary shard所在節點,該節點處理請求,並把資料同步到它的replica shard
3.協調節點對客戶端做出響應
3.15 寫一致性原理和quorum機制
1.任何一個增刪改操作都可以跟上一個引數 consistency
可以給該引數指定的值:
one: (primary shard)只要有一個primary shard是活躍的就可以執行
all: (all shard)所有的primary shard和replica shard都是活躍的才能執行
quorum: (default) 預設值,大部分shard是活躍的才能執行 (例如共有6個shard,至少有3個shard是活躍的才能執行寫操作)
2.quorum機制:多數shard都是可用的,
int((primary+number_of_replica)/2)+1
例如:3個primary shard,1個replica
int((3+1)/2)+1=3
至少3個shard是活躍的
注意:可能出現shard不能分配齊全的情況
比如:1個primary shard,1個replica int((1+1)/2)+1=2 但是如果只有一個節點,因為primary shard和replica shard不能在同一個節點上,所以仍然不能執行寫操作
再舉例:1個primary shard,3個replica,2個節點
int((1+3)/2)+1=3
最後:當活躍的shard的個數沒有達到要求時, es預設會等待一分鐘,如果在等待的期間活躍的shard的個數沒有增加,則顯示timeout
put /index/type/id?timeout=60s
3.16 檔案查詢內部原理
第一步:查詢請求發給任意一個節點,該節點就成了coordinating node,該節點使用路由演演算法算出檔案所在的primary shard
第二步:協調節點把請求轉發給primary shard也可以轉發給replica shard(使用輪詢排程演演算法(Round-Robin Scheduling,把請求平均分配至primary shard 和replica shard)
第三步:處理請求的節點把結果返回給協調節點,協調節點再返回給應用程式
特殊情況:請求的檔案還在建立索引的過程中,primary shard上存在,但replica shar上不存在,但是請求被轉發到了replica shard上,這時就會提示找不到檔案
3.17 bulk批量操作的json格式解析
bulk的格式:
{action:{metadata}}\n
{requstbody}\n
複製程式碼
為什麼不使用如下格式:
[{
"action": {
},"data": {
}
}]
複製程式碼
這種方式可讀性好,但是內部處理就麻煩了:
1.將json陣列解析為JSONArray物件,在記憶體中就需要有一份json文字的拷貝,另外還有一個JSONArray物件。
2.解析json陣列裡的每個json,對每個請求中的document進行路由
3.為路由到同一個shard上的多個請求,建立一個請求陣列
4.將這個請求陣列序列化
5.將序列化後的請求陣列傳送到對應的節點上去
耗費更多記憶體,增加java虛擬機器器開銷
1.不用將其轉換為json物件,直接按照換行符切割json,記憶體中不需要json文字的拷貝
2.對每兩個一組的json,讀取meta,進行document路由
3.直接將對應的json傳送到node上去
3.18 查詢結果分析
{
"took": 419,"timed_out": false,"_shards": {
"total": 3,"successful": 3,"skipped": 0,"failed": 0
},"hits": {
"total": 3,"max_score": 0.6931472,"hits": [
{
"_index": "lib3","_id": "3","_score": 0.6931472,"_source": {
"address": "bei jing hai dian qu qing he zhen","name": "lisi"
}
},{
"_index": "lib3","_id": "2","_score": 0.47000363,"name": "zhaoming"
}
}
複製程式碼
took:查詢耗費的時間,單位是毫秒
_shards:共請求了多少個shard
total:查詢出的檔案總個數
max_score: 本次查詢中,相關度分數的最大值,檔案和此次查詢的匹配度越高,_score的值越大,排位越靠前
hits:預設查詢前10個檔案
timed_out:
GET /lib3/user/_search?timeout=10ms
{
"_source": ["address","query": {
"match": {
"interests": "changge"
}
}
}
複製程式碼
3.19 多index,多type查詢模式
GET _search
GET /lib/_search
GET /lib,lib3/_search
GET /*3,*4/_search
GET /lib/user/_search
GET /lib,lib4/user,items/_search
GET /_all/_search
GET /_all/user,items/_search
複製程式碼
3.20 分頁查詢中的deep paging問題
GET /lib3/user/_search
{
"from":0,"changge"]
}
}
}
GET /_search?from=0&size=3
複製程式碼
deep paging:查詢的很深,比如一個索引有三個primary shard,分別儲存了6000條資料,我們要得到第100頁的資料(每頁10條),類似這種情況就叫deep paging
如何得到第100頁的10條資料?
在每個shard中搜索990到999這10條資料,然後用這30條資料排序,排序之後取10條資料就是要搜尋的資料,這種做法是錯的,因為3個shard中的資料的_score分數不一樣,可能這某一個shard中第一條資料的_score分數比另一個shard中第1000條都要高,所以在每個shard中搜索990到999這10條資料然後排序的做法是不正確的。
正確的做法是每個shard把0到999條資料全部搜尋出來(按排序順序),然後全部返回給coordinate node,由coordinate node按_score分數排序後,取出第100頁的10條資料,然後返回給客戶端。
deep paging效能問題
1.耗費網路頻寬,因為搜尋過深的話,各shard要把資料傳送給coordinate node,這個過程是有大量資料傳遞的,消耗網路,
2.消耗記憶體,各shard要把資料傳送給coordinate node,這個傳遞回來的資料,是被coordinate node儲存在記憶體中的,這樣會大量消耗記憶體。
3.消耗cpu coordinate node要把傳回來的資料進行排序,這個排序過程很消耗cpu.
鑑於deep paging的效能問題,所以應儘量減少使用。
3.21 query string查詢及copy_to解析
GET /lib3/user/_search?q=interests:changge
GET /lib3/user/_search?q=+interests:changge
GET /lib3/user/_search?q=-interests:changge
複製程式碼
copy_to欄位是把其它欄位中的值,以空格為分隔符組成一個大字串,然後被分析和索引,但是不儲存,也就是說它能被查詢,但不能被取回顯示。
注意:copy_to指向的欄位欄位型別要為:text
當沒有指定field時,就會從copy_to欄位中查詢
GET /lib3/user/_search?q=changge
複製程式碼
3.22 字串排序問題
對一個字串型別的欄位進行排序通常不準確,因為已經被分詞成多個詞條了
解決方式:對欄位索引兩次,一次索引分詞(用於搜尋),一次索引不分詞(用於排序)
GET /lib3/_search
GET /lib3/user/_search
{
"query": {
"match_all": {}
},"sort": [
{
"interests": {
"order": "desc"
}
}
]
}
GET /lib3/user/_search
{
"query": {
"match_all": {}
},"sort": [
{
"interests.raw": {
"order": "asc"
}
}
]
}
DELETE lib3
PUT /lib3
{
"settings":{
"number_of_shards" : 3,"number_of_replicas" : 0
},"birthday": {"type":"date"},"interests": {
"type":"text","fields": {
"raw":{
"type": "keyword"
}
},"fielddata": true
}
}
}
}
}
複製程式碼
3.23 如何計算相關度分數
使用的是TF/IDF演演算法(Term Frequency&Inverse Document Frequency)
1.Term Frequency:我們查詢的文字中的詞條在document本中出現了多少次,出現次數越多,相關度越高
搜尋內容: hello world
Hello,I love china.
Hello world,how are you!
複製程式碼
2.Inverse Document Frequency:我們查詢的文字中的詞條在索引的所有檔案中出現了多少次,出現的次數越多,相關度越低
搜尋內容:hello world
hello,what are you doing?
I like the world.
hello 在索引的所有檔案中出現了500次,world出現了100次
複製程式碼
3.Field-length(欄位長度歸約) norm:field越長,相關度越低
搜尋內容:hello world
{"title":"hello,what's your name?","content":{"owieurowieuolsdjflk"}}
{"title":"hi,good morning","content":{"lkjkljkj.......world"}}
複製程式碼
檢視分數是如何計算的:
GET /lib3/user/_search?explain=true
{
"query":{
"match":{
"interests": "duanlian,changge"
}
}
}
複製程式碼
檢視一個檔案能否匹配上某個查詢:
GET /lib3/user/2/_explain
{
"query":{
"match":{
"interests": "duanlian,changge"
}
}
}
複製程式碼
3.24 Doc Values 解析
DocValues其實是Lucene在構建倒排索引時,會額外建立一個有序的正排索引(基於document => field value的對映列表)
{"birthday":"1985-11-11",age:23}
{"birthday":"1989-11-11",age:29}
document age birthday
doc1 23 1985-11-11
doc2 29 1989-11-11
複製程式碼
儲存在磁碟上,節省記憶體
對排序,分組和一些聚合操作能夠大大提升效能
注意:預設對不分詞的欄位是開啟的,對分詞欄位無效(需要把fielddata設定為true)
PUT /lib3
{
"settings":{
"number_of_shards" : 3,"age": {
"type":"integer","doc_values":false
},"birthday": {"type":"date"}
}
}
}
}
複製程式碼
3.25 基於scroll技術滾動搜尋大量資料
如果一次性要查出來比如10萬條資料,那麼效能會很差,此時一般會採取用scoll滾動查詢,一批一批的查,直到所有資料都查詢完為止。
1.scoll搜尋會在第一次搜尋的時候,儲存一個當時的檢視快照,之後只會基於該舊的檢視快照提供資料搜尋,如果這個期間資料變更,是不會讓使用者看到的
2.採用基於_doc(不使用_score)進行排序的方式,效能較高
3.每次傳送scroll請求,我們還需要指定一個scoll引數,指定一個時間視窗,每次搜尋請求只要在這個時間視窗內能完成就可以了
GET /lib3/user/_search?scroll=1m
{
"query": {
"match_all": {}
},"sort":["_doc"],"size":3
}
GET /_search/scroll
{
"scroll": "1m","scroll_id": "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTlU3Z1FCT1lWVHdadlhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVlR3WnZYTmFXZw=="
}
複製程式碼
3.26 dynamic mapping策略
dynamic:
1.true:遇到陌生欄位就 dynamic mapping
2.false:遇到陌生欄位就忽略
3.strict:約到陌生欄位就報錯
PUT /lib8
{
"settings":{
"number_of_shards" : 3,"mappings":{
"user":{
"dynamic":strict,"properties":{
"name": {"type":"text"},"address":{
"type":"object","dynamic":true
},}
}
}
}
#會報錯
PUT /lib8/user/1
{
"name":"lisi","age":20,"address":{
"province":"beijing","city":"beijing"
}
}
複製程式碼
date_detection:預設會按照一定格式識別date,比如yyyy-MM-dd
可以手動關閉某個type的date_detection
PUT /lib8
{
"settings":{
"number_of_shards" : 3,"mappings":{
"user":{
"date_detection": false,}
}
}
複製程式碼
定製 dynamic mapping template(type)
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{
"en": {
"match": "*_en","match_mapping_type": "string","mapping": {
"type": "text","analyzer": "english"
}
}
}
]
}
}
}
#使用了模板
PUT /my_index/my_type/3
{
"title_en": "this is my dog"
}
#沒有使用模板
PUT /my_index/my_type/5
{
"title": "this is my cat"
}
GET my_index/my_type/_search
{
"query": {
"match": {
"title": "is"
}
}
}
複製程式碼
3.27 重建索引
一個field的設定是不能修改的,如果要修改一個field,那麼應該重新按照新的mapping,建立一個index,然後將資料批量查詢出來,重新用bulk api寫入到index中。
批量查詢的時候,建議採用scroll api,並且採用多執行緒併發的方式來reindex資料,每次scroll就查詢指定日期的一段資料,交給一個執行緒即可。
PUT /index1/type1/4
{
"content":"1990-12-12"
}
GET /index1/type1/_search
GET /index1/type1/_mapping
#報錯
PUT /index1/type1/4
{
"content":"I am very happy."
}
複製程式碼
#修改content的型別為string型別,報錯,不允許修改
PUT /index1/_mapping/type1
{
"properties": {
"content":{
"type": "text"
}
}
}
複製程式碼
#建立一個新的索引,把index1索引中的資料查詢出來匯入到新的索引中 #但是應用程式使用的是之前的索引,為了不用重啟應用程式,給index1這個索引起個#別名
PUT /index1/_alias/index2
複製程式碼
#建立新的索引,把content的型別改為字串
PUT /newindex
{
"mappings": {
"type1":{
"properties": {
"content":{
"type": "text"
}
}
}
}
}
複製程式碼
#使用scroll批量查詢
GET /index1/type1/_search?scroll=1m
{
"query": {
"match_all": {}
},"sort": ["_doc"],"size": 2
}
複製程式碼
#使用bulk批量寫入新的索引
POST /_bulk
{"index":{"_index":"newindex","_type":"type1","_id":1}}
{"content":"1982-12-12"}
複製程式碼
#將別名index2和新的索引關聯,應用程式不用重啟
POST /_aliases
{
"actions": [
{"remove": {"index":"index1","alias":"index2"}},{"add": {"index": "newindex","alias": "index2"}}
]
}
GET index2/type1/_search
複製程式碼
3.28 索引不可變的原因
倒排索引包括:
檔案的列表,檔案的數量,詞條在每個檔案中出現的次數,出現的位置,每個檔案的長度,所有檔案的平均長度
索引不變的原因:
不需要鎖,提升了併發效能
可以一直儲存在快取中(filter)
節省cpu和io開銷
第四節 在Java應用中訪問ElasticSearch
4.1 在Java應用中實現查詢檔案
pom中加入ElasticSearch6.2.4的依賴:
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.2.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java編譯外掛 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
複製程式碼
4.2 在Java應用中實現新增檔案
"{" +
"\"id\":\"1\"," +
"\"title\":\"Java設計模式之裝飾模式\"," +
"\"content\":\"在不必改變原類檔案和使用繼承的情況下,動態地擴充套件一個物件的功能。\"," +
"\"postdate\":\"2018-05-20 14:38:00\"," +
"\"url\":\"csdn.net/79239072\"" +
"}"
XContentBuilder doc1 = XContentFactory.jsonBuilder()
.startObject()
.field("id","3")
.field("title","Java設計模式之單例模式")
.field("content","列舉單例模式可以防反射攻擊。")
.field("postdate","2018-02-03")
.field("url","csdn.net/79247746")
.endObject();
IndexResponse response = client.prepareIndex("index1","blog",null)
.setSource(doc1)
.get();
System.out.println(response.status());
複製程式碼
4.3 在Java應用中實現刪除檔案
DeleteResponse response=client.prepareDelete("index1","SzYJjWMBjSAutsuLRP_P").get();
//刪除成功返回OK,否則返回NOT_FOUND
System.out.println(response.status());
複製程式碼
4.4 在Java應用中實現更新檔案
UpdateRequest request=new UpdateRequest();
request.index("index1")
.type("blog")
.id("2")
.doc(
XContentFactory.jsonBuilder().startObject()
.field("title","單例模式解讀")
.endObject()
);
UpdateResponse response=client.update(request).get();
//更新成功返回OK,否則返回NOT_FOUND
System.out.println(response.status());
upsert方式:
IndexRequest request1 =new IndexRequest("index1","3")
.source(
XContentFactory.jsonBuilder().startObject()
.field("id","3")
.field("title","裝飾模式")
.field("content","動態地擴充套件一個物件的功能")
.field("postdate","2018-05-23")
.field("url","csdn.net/79239072")
.endObject()
);
UpdateRequest request2=new UpdateRequest("index1","3")
.doc(
XContentFactory.jsonBuilder().startObject()
.field("title","裝飾模式解讀")
.endObject()
).upsert(request1);
UpdateResponse response=client.update(request2).get();
//upsert操作成功返回OK,否則返回NOT_FOUND
System.out.println(response.status());
複製程式碼
4.5 在Java應用中實現批量操作
MultiGetResponse mgResponse = client.prepareMultiGet()
.add("index1","2")
.add("lib3","user","1","3")
.get();
for(MultiGetItemResponse response:mgResponse){
GetResponse rp=response.getResponse();
if(rp!=null && rp.isExists()){
System.out.println(rp.getSourceAsString());
}
}
bulk:
BulkRequestBuilder bulkRequest = client.prepareBulk();
bulkRequest.add(client.prepareIndex("lib2","books","4")
.setSource(XContentFactory.jsonBuilder()
.startObject()
.field("title","python")
.field("price",68)
.endObject()
)
);
bulkRequest.add(client.prepareIndex("lib2","5")
.setSource(XContentFactory.jsonBuilder()
.startObject()
.field("title","VR")
.field("price",38)
.endObject()
)
);
//批量執行
BulkResponse bulkResponse = bulkRequest.get();
System.out.println(bulkResponse.status());
if (bulkResponse.hasFailures()) {
System.out.println("存在失敗操作");
}
複製程式碼