es權威指南學習
es功能
- 分散式的實時文件儲存,每個欄位都可以被索引、搜尋
- 分散式實時分析搜尋引擎
- 勝任上百個服務節點的擴充套件,支援PB級的結構化、非結構化資料管理(儲存、索引、分析、搜尋)
es安裝
- ./bin/elastaicsearch 啟動es
- -d 後臺啟動
- ctrl+c 終止服務
- elasticsearch.yml 重要配置
es外掛(head,sense,marvel)
head外掛可以用來快速檢視elasticsearch中的資料概況以及非全量的資料,也支援控制元件化查詢和rest請求,但是體驗都不是很好。一般就用它來看各個索引的資料量以及分片的狀態。
直接訪問下面的地址即可:
sense外掛可以方便的執行rest請求,但是中文輸入的體驗不是很好。
安裝sense只需要在Kibana端安裝外掛即可,外掛會自動安裝到kibana的應用選單中
http://localhost:5601/app/sensemarvel
marvel工具可以幫助使用者監控elasticsearch的執行狀態,不過這個外掛需要license。安裝完license後可以安裝marvel的agent,agent會收集elasticsearch的執行狀態。
然後在Kibana段安裝marvel外掛,這個外掛與sense類似,都整合在kibana的導航列表面
kibana sense安裝
- kibana是es的視覺化及分析平臺
- sense是kibana提供的互動式控制檯
- ./bin/kibana plugin –install elastic/sense 安裝sense
- ./bin/kibana 啟動kibana
與es互動
java API(9300)
es提供了兩種形式的java客戶端(要將es提供的客戶端支援的程式碼引入自己的java工程或者專案中去),這兩種客戶端與es的互動均是9300埠。叢集中的節點本身也是通過9300埠彼此通訊,使用es原生的傳輸協議。
- 節點客戶端(Node client):節點客戶端作為非資料節點加入到本地叢集中,節點客戶端本身不儲存任何資料,但是它知道資料在叢集中的哪個節點,並可以把請求轉發到正確的節點
- 傳輸客戶端(Transport client):傳輸客戶端將請求傳送到遠端叢集,它本身不加入叢集,但是可以將請求轉發到叢集中的一個節點上
RESTful API with JSON over HTTP
所有其他語言可以使用RESTful API 通過埠9200與es進行通訊。如 JavaScript、Python、PHP、Ruby、Perl
es請求格式:
crul -XVERB "protocal://host:port/path?queryStr' -d 'body'
示例:
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query" : { "match_all" : { } }
}'
縮略格式(sense控制檯也使用此種縮略格式):
GET /_count
{"query" : { "match_all" : { } }}
面向文件
應用程式中的物件一般有複雜的資料結構,當儲存到關係型資料庫中時,往往需要對其進行扁平化處理,而在查詢資料時,又要重新構造物件,這其中需要大量的工作進行設計。
而es是直接儲存物件或者文件,並對整個文件進行索引,使之可以被檢索。在es中是對文件進行索引、檢索、排序和過濾,而不是對行列資料。
序列化格式:JSON
索引/型別/文件/屬性 (_index/_type)
一個es叢集可以包含多個索引(庫),每個索引又可以包含多種型別(類/表),每個型別可以包含多個文件(物件/行-一條資料),每個文件可以包含多個屬性(欄位/列)
索引(名稱)/ 索引(動詞)/ 倒排索引
- 索引(名稱):一個索引類似於關係型資料庫中的一個庫,是儲存文件的地方。
- 索引(動詞):儲存一個文件到一個索引庫中,即為索引一個文件。類似於SQL中的insert動作。
- 倒排索引:
關係型資料庫中通過指定一個列,對此列的所有取值建立某種方式的(如B-Tree)索引,以便檢索
es則是通過對每個單詞或字建立倒排索引,來達到相同的目的。預設情況下,es對每個屬性都會建立倒排索引,如果某個屬性沒有建立倒排索引,那麼檢索時是無法檢索到的。
簡單示例
索引一個文件到庫
無需事先進行建立索引庫、建立型別、指定型別的屬性等操作,只需直接執行如下的索引一個文件的操作,es會在後臺使用預設設定完成其它的一切管理任務。
PUT /magecorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
PUT /megacorp/employee/2
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
PUT /megacorp/employee/3
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
路徑 /megacorp/employee/1 包含了三部分的資訊:
megacorp 索引名稱
employee 型別名稱
1 特定僱員的ID
請求體 —— JSON 文件 —— 包含了這位員工的所有詳細資訊,他的名字叫 John Smith ,今年 25 歲,喜歡攀巖。
檢索文件 - GET - 指定索引庫、型別、ID
GET /megacorp/employee/1
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
輕量搜尋 - /{index}/{type}/_search?q=field:value
適用於通過命令列或者互動式客戶端做即席查詢或一次性查詢,比較簡潔,在開發階段非常方便,但也晦澀、易出錯。
相反的在生產環境中更多地使用功能全面的請求體 request body方式的 查詢API,除了能完成以上所有功能,還有一些附加功能。
例:
+name:(jane kitty) join_date:>2014-05-01 -age:<20 +(aggregations geo)
- +字首表示必須與查詢條件匹配,-字首表示必須不匹配,沒有+、-字首的其它條件都是可選的–匹配的越多,文件就越相關
- +(elastic kibana) 表示_all欄位中必須包含elastic 或者 kibana
es會把某文件的所有域的值拼接成一個字串,作為此文件的一個域,此域的名稱為_all,當q中不指定欄位,則預設是對_all欄位進行檢索,如/_search?q=mary
,則是查詢文件中包含“mary”這個詞的所有文件。
GET /megacorp/employee/_search?q=last_name:Smith #檢索某型別下的文件,指定查詢條件
GET /megacorp/employee/_search #檢索某型別下的所有文件,無查詢條件
{
"took": 6, #耗時?
"timed_out": false,
"_shards": { ... },
"hits": { #檢索出的結果的,彙總資訊?
"total": 3,
"max_score": 1, #檢索出的結果中相關度最高分
"hits": [
{
"_index": "megacorp", #索引庫
"_type": "employee", #型別
"_id": "3",
"_score": 1, #相關度得分
"_source": {
"first_name": "Douglas",
"last_name": "Fir",
"age": 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
},
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 1,
"_source": {
"first_name": "John"
...
}
}...
]
}
}
輕量搜尋:
GET /matecorp/employee/_search?q=last_name:Smith
用DSL查詢表示式如下:
GET /matecorp/employee/_search
{
"query":{
"match":{
"last_name" : "Smith"
}
}
}
過濾器 filter
#查詢last_name=Smith,年齡大於30的
GET /magecorp/employee/_search
{
"query":{
"bool":{
"must":{ #必須匹配
"match" : {
"last_name" : "Smith"
}
},
"filter":{ #過濾器
"range" : { #取值範圍,相對於between
"age" : {
"gt" : 30
}
}
}
}
}
}
全文搜尋
#檢索所有喜歡攀巖(rock climbing)的員工
GET /magecorp/employee/_search
{
"query":{
"match":{ #若要完全匹配整個短語,用match_phrase
"about": "rock climbing"
}
}
}
#結果如下:名為John的僱員的相關性得分_score要高於名為Jane的,因為Jane的about為"I like to collect rock albums",只匹配到了單詞rock
{
...
"hits": {
"total": 2,
"max_score": 0.16273327,
"hits": [
{
...
"_score": 0.16273327,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
...
"_score": 0.016878016,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
高亮搜尋 highlight
GET /magecorp/employee/_search
{
"query" : {
"match_phrase" : { #匹配整個短語
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
聚合 - aggs(aggregations)
#挖掘所有興趣愛好的受歡迎度
GET /megacorp/employee/_search
{
"aggs":{ #指定查詢為聚合查詢
"popularity_interests":{ #給查詢結果取個名
"terms":{ #彙總方式:terms:列出所有結果項
"field" : "interests" #依據的是一個屬性,屬性名是interests
}
}
}
}
{
...
"hits": { ... },
"aggregations": {
"popularity_interests": { #名為popularity_interests的查詢的結果
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
#指定查詢條件的聚合:叫 Smith 的興趣的受歡迎程度,直接新增適當的查詢來組合查詢:
GET /magecorp/employee/_search
{
"query" : { "match" : { "first_name" : "Smith" } },
"aggs" : { "popularity_interests" : { "terms" : { "field" : "interests" } } }
}
#分級彙總(巢狀聚合):某興趣愛好的人氣的平均年齡段
GET /magecorp/employee/_search
{
"aggs" : {
"popularity_interests" : {
"terms" : { "field" : "interests" } , #彙總方式:terms:列出所有結果項
"aggs" : { #巢狀聚合彙總
"avg_age" : { #查詢結果名稱
"avg" : { #彙總方式:avg:求平均
"field" : "age"
}
}
}
}
}
}
叢集特性
- 天生分散式,所有節點平等,自動選舉master節點。若只有一個節點,那麼它就會成為主節點。
- 單點故障:只需兩個節點,即可應對單點故障(在elasticsearch.yml中配置)
cluster.name: escluster
node.name: node-0
network.host: 192.168.174.20
http.port: 9200
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["192.168.174.20","192.168.174.21","192.168.174.22"]
- 節點故障:
- 若故障的節點是主節點,則叢集會立刻重新選舉出一個新的主節點。
- 若故障節點上有主分片,則叢集會立即從其它節點上找到這些主分片的副本分片,並將其提升為主分片。(在這些動作做完之前,status為red,當提升副本分片為主分片後,status為yellow)
- 叢集中有節點加入或者退出時,叢集會自動重新平均分佈叢集中的所有資料
節點、主分片、副本分片(node 、priamary_shard、replacation)
node : number_of_nodes , number_of_data_nodes
shard :
- active_primary_shards(正常執行的主分片數。如果有主分片沒能正常執行,則status為red)。
- active_shards(所有正常執行的分片數。所有的主分片都正常執行,但不是所有的副本分片都正常執行。則status為yellow)
- unassigned_shards(沒有分配到任何節點的副本分片數)
當status為yellow時,叢集是正常執行的,能正常對外提供服務,但是有丟失資料的故障風險(因為有副本是故障的,那麼一旦主分片資料丟失,又沒有可用的副本來恢復,整個資料就有丟失的風險了)
索引與分片
- 索引是邏輯名稱空間,一個索引庫會指向一個或者多個物理分片
- 一個分片是一個底層的工作單元,是一個Lucene的例項,是一個完整的搜尋引擎。一個文件最終會被物理地儲存和索引到一個分片內,但應用程式是直接與索引而不是分片進行互動。
- 一個索引的主分片數在索引建立時確定,且不可更改。一個索引預設有5個主分片,可以通過如下定義語句,設定索引庫的主分片數
PUT /myIndex
{
"settings" : {
"number_of_shards" : "3", #3個主分片
"number_of_replicas" : "2", #每個分片有兩個副本,共6個副本分片
}
}
- 索引的副本數可以動態的進行水平擴充,水平擴充套件能提高負載能力,提高檢索和訪問效能,
PUT /my_index/settings
{
"number_of_replicas":5
}
當然,如果只是在相同節點數目的叢集上增加更多的副本分片並不能提高效能,因為每個分片從節點上獲得的資源會變少。 你需要增加更多的硬體資源來提升吞吐量。
但是更多的副本分片數提高了資料冗餘量:按照上面的節點配置,我們可以在失去2個節點的情況下不丟失任何資料
文件和物件
- 通常情況下,es中的術語 物件 和 文件 是可以互換的。
- 但物件可以包含(巢狀)了另外的物件,或者包含在(巢狀在)一個物件中
- 而文件則只能是一個型別中的頂層或者根物件,這個根物件被序列化為JSON文件,儲存到索引庫中,有唯一的ID。
文件元資料(_index 、 _type、_id)
- _index:
索引庫中儲存的應該是因共同的特性被分組到一起的文件集合。
如:所有的產品在索引 products 中,而所有的交易資訊儲存到索引 sales 中。
索引名稱只能以小寫字母開頭。 - _type:
Elasticsearch 公開了一個稱為 types (型別)的特性,它允許您在索引中對資料進行邏輯分割槽。不同 types 的文件可能有不同的欄位,但最好能夠非常相似。
如,所有的產品都放在一個索引中,但是你有許多不同的產品類別,比如 “electronics” 、 “kitchen” 和 “lawn-care”。
type名稱可以以大小寫字母開頭 - _id
id可以是自己指定的(如果文件本身有一個可以作唯一標識的欄位,如學號等),那麼應該在將文件索引到es中時直接提供:
PUT /{index}/{type}/{id}
{ "field1":"value"}
#如果不自己指定id,那麼可以用autogenerating IDS,注意語法改成用POST(儲存文件在這個url名稱空間下),而不是PUT(使用這個URL儲存文件):
POST /{index}/{type}
{ "field1":"value"}
#自動生成的 ID 是 URL-safe、 基於 Base64 編碼且長度為20個字元的 GUID 字串。 這些 GUID 字串由可修改的 FlakeID 模式生成,這種模式允許多個節點並行生成唯一 ID ,且互相之間的衝突概率幾乎為零
取回一個文件
#返回頭部head資訊(-i)
GET /magecorp/employee/1
{
...
"_version": 1,
"found": true,
"_source": {
"first_name": "John",
"last_name": "Smith",
...
}
}
curl -i -XGET http://hadoop01:9200/megacorp/employee/10
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=UTF-8
Content-Length: 65
{"_index":"megacorp","_type":"employee","_id":"10",
"found":false}
#返回文件的一個或者幾個欄位(?_source=欄位名1,欄位名2)
GET /megacorp/employee/1?_source=first_name,age
{
"_index": "megacorp",
...
"found": true,
"_source": {
"first_name": "John",
"age": "25"
}
}
#只想獲得_source欄位,不想獲得元資料,則設定端點為_source
GET /megacorp/employee/1/_source
{
"first_name": "John",
"last_name": "Smith",
...
}
檢查文件是否存在( -i -XHEAD )
#status為200時為存在,status為404時,為不存在
curl -i -XHEAD http://hadoop01:9200/megacorp/employee/10
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
更新與建立文件
#如果直接用 PUT /{index}/{type}/{id} {body}
#那麼可能是更新,也可能是建立新文件(當id已經存在時,更新文件;否則建立新文件)
#如果需要確保建立一個新的文件,有以下三種請求格式
POST /{index}/{type}/ {body}
PUT /{index}/{type}/{id}/_create
PUT /{index}/{type}/{id}?op_type=create
當指定的id已經存在時,會返回status=409,document_already_exists
刪除文件(DELETE)
DELETE /megacorp/employee/1
#如果要刪除的id存在,則status為200,且返回的_version為加1後的
#如果id不存在,則status為404
併發控制(樂觀鎖,?version=1&version_type=external)
Elasticsearch 是分散式的。當文件建立、更新或刪除時, 新版本的文件必須複製到叢集中的其他節點。
Elasticsearch 也是非同步和併發的,這意味著這些複製請求被並行傳送,並且到達目的地時也許 順序是亂的 。 Elasticsearch 需要一種方法確保文件的舊版本不會覆蓋新的版本。
es通過檢查版本號來檢測是否發生了併發衝突(conflict),如果衝突,則返回status:409。
{
"error": {
"root_cause": [
...
],
"type": "version_conflict_engine_exception",
"reason": "[blog][1]: version conflict, current [2], provided [1]",
"index": "website",
"shard": "3"
},
"status": 409
}
- 自動生成的版本號
對於自動生成的版本號,可以通過設定源版本號?version=源版本號
來檢測是否發生了併發的衝突
所謂源版本號是指從庫中獲取到文件時,此文件的版本號,如果更新後需要重新索引到庫中時,庫中的相同ID的文件的版本號已經不是我之前取出的版本號了,那麼說文件在我的取出和重新更新這個時間段內,被別的執行緒做了一次更新。此時即為發生了衝突。
PUT /megacorp/employee/2?version=1
- 外部系統資料自身提供版本號
很常見的是,es中的資料是先儲存到結構化資料庫中,然後再被索引到es中。資料在主庫中的所有更新都要同步到es中,這更加提高了併發衝突的可能性。
如果主資料庫中已經有了版本號,或者有可以作為版本號的欄位,比如timestamp,那麼就可以通過?version=新版本號&version_type=external
來檢測是否發生了衝突。
外部版本號的檢測機制是檢測當前索引庫中的相同ID文件的版本號是否小於請求中給定的新版本號,如果不是的話那麼就發生了衝突。
外部版本號不僅可以在更新、刪除時指定,也可以在建立時指定
#建立一個ID=4的僱員,指定版本號=5
PUT /megacorp/employee/4?version=5&version_type=external
{ "first_name":"hellen",...}
#更新ID=4的僱員資訊,指定版本號=10
PUT /megacorp/employee/4?version=10&version_type=external
{ "first_name":"Helen",...}
文件的部分更新
POST /megacorp/employee/1/_update
{
"doc" : { "interests" : ["swiming" , "pingpang" ] }
}
#或者用groovy指令碼更新,注意要配置一下允許指令碼
POST /megacorp/employee/1/_update
{
"script" : "ctx._source.interests+=newInterest",
"params" :{
"newInterest" : "swiming"
}
}
取回多個文件(/_mget:multi-get)
GET /_mget
{
"docs":[
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1"
},
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "2",
"_source": "interests" #指定取回的欄位
}
]
}
GET /megacorp/employee/_mget
{
"ids" : ["1","2"]
}
批量操作(/_bulk:bulk API)
bulk API提供在一個步驟中進行多個create\index\update\delete的不同action的操作。
批量請求避免了單獨處理每個請求的網路延時和開銷,因此可以提高效能。但同時整個批量請求都要由接收到請求的節點載入到記憶體中,則其它後來的請求所能獲得的記憶體就會相應縮減。因此批量操作並不是越多越好,超過某個量,效能將不再提升,反而可能降低,一般佔用記憶體5M~15M比較合適。最好的辦法是逐漸增加批的大小,進行嘗試。一般以1000-5000條為一個批。
格式如下:
POST /_bulk 或者 POST /{index}/{type}/_bulk
{action1 : {metadata1}}
{request body1}
{action2 : {metadata2}}
{request body2}
其中action必須是以下選項之一:
- create :如果文件不存在,則建立文件(PUT /{index}/{type}/{id}/_create)
- index:建立一個新文件或者對現有文件進行全部更新(PUT /{index}/{type}/{id})
- update:對一個文件進行部分更新(POST /{index}/{type}/{id}/_update)
- delete:刪除一個文件(DELETE /{index}/{type}/{id})
POST /_bulk
{"create":{"_index": "megacorp","_type":"employee","_id": "4"}} #create,不存在則建立,已存在則報錯
{"first_name": "helilio",...} #注意body的兩個花括號{}必須是在同一行
{"index":{"_index": "megacorp","_type":"employee","_id": "1"}} #不存在在建立,一存在則替換整個文件
{"first_name": "John",...}
{"update":{"_index": "megacorp", "_type": "employee", "_id": "2"}} #部分更新
{"doc":{"age": "11"}
{"delete":{"_index": "megacorp", "_type": "employee", "_id": "1"}}
分散式文件儲存
路由一個文件到某個分片
shard = hash(_id) % number_of_primary_shards
因此索引庫一旦建立後,則不能再修改主分片數,否則就無法正確的找到文件在索引庫的哪個分片上
請求的接收方式
- 客戶端將請求傳送到專門的協調節點,協調節點會接收所有請求,並計算請求的資料路由到的分片所在的節點,將請求轉發到分片所在節點,分片所在節點處理完後,向協調節點報告處理成,協調節點響應給客戶端
- 輪詢所有節點:沒有專門的協調節點,而是輪詢地傳送請求到叢集的所有節點,這樣負載能力更強
叢集對於更新操作的處理步驟
- 協調節點接收請求,路由算出資料應在哪個主分片上,並找到主分片所在的節點。然後轉發請求到主分片所在節點
- 主分片所在節點處理完成後,將請求並行傳送到副本分片所在節點上,副本分片處理成功後向主分片節點報告成功,
- 主分片節點向協調節點報告成功,協調節點向客戶端報告成功
搜尋-最基本的工具
- 空搜尋:
GET /_search
查詢所有索引庫下的所有文件,預設返回前10個文件 - 多索引、多型別
/idx1,idx2/_search #索引庫idx1,idx2下的所有文件
/_all/user,tweet/_search #所有索引庫下型別為user,tweet的所有文件
/idx1,idx2/user,tweet/_search
/idx*,db*/_search #所有名稱以idx或者db為字首的索引庫下的所有文件
- 分頁:
/_search?size=5&from=10
一個請求可能跨越多個分片,那麼分頁是如何實現的呢?
假如我們所請求的索引共有5各分片,現在請求第一頁,前10個文件,那麼會先從每個分片上各取相關度前10的文件,然後彙總這5個分片的所有前10共50個文件,再對這50個文件的相關度進行排序,拿排名前10的10個文件作為請求的結果。 - 深度分頁的問題:據上所述(分頁的實現),如果我們是要拿第1000頁的10個文件,即相關度排名為從10000到10010的10個文件。那麼要各從5個分片上拿相關度排名前1000*10+10=10010的10010個文件,然後彙總這5個分片的共10010*5=50050個文件,再從這50050個文件中取出排名從10000到10010的10個文件。
因此對結果排序的成本,隨著分頁的深度成指數級上升,這就是任何web搜尋引擎對任何查詢都不要返回超過1000個結果的原因
對映和分析
精確值 和 全文文字
- es中的資料可以概括的分為兩類:精確值、全文文字
- 查詢精確值的結果:文件要麼匹配、要麼不匹配
- 查詢全文文字的結果:文件的匹配度(相關度)有多大
- 很少對全文型別的域做精確匹配,而是希望在文字型別的域中搜索。
分析(分詞 + 標準化)
對文字分析的過程
1. 將文字分成一個個的詞條
2. 將每個詞條統一化為標準格式
分析器就是執行上面的工作,分三個步驟:
1. 字元過濾器:在分詞前先整理下字串,如將’&’轉成 and
2. 分詞器:字串被分成一個個的詞條。例如空格分詞器、
3. Token過濾器:每個詞條按順序通過Token過濾器,如將詞條轉為小寫,提取詞根,刪除無意義詞條如the 、a、的、了等,增加同義詞如jump則加leap
es自帶的分詞器:
- 標準分析器(預設,最常用)
根據 Unicode 聯盟 定義的 單詞邊界 劃分文字;刪除絕大部分標點;將詞條小寫。
- 簡單分析器
在任何不是字母的地方分隔文字;將詞條小寫。
- 空格分析器
在空格的地方劃分文字。(不進行小寫處理)
- 語言分析器
特定語言分析器有許多可選語言,會考慮指定語言的特定。例如 ‘英語’:刪除英語無意義詞(and the a);提取詞根;將詞條小寫。
測試分析器
GET /_analyze
{
"analyzer":"standard", #指定要測試的分析器
"text":" to test The Standard analyzer" #要分析的文字
}
域型別與域對映
簡單核心域型別:
- 字串:string
- 日期:date
- 整形:byte, short, integer, long
- 浮點型:float, double
- 布林型:boolean
動態對映:
當索引一個文件,此文件中有一個新的域(屬性),即在文件所屬型別的對映中未出現過的域,es會對其進行動態對映(根據一些預設的規則,自動設定新域的對映)
新域的值型別 | 新域對映的型別 |
---|---|
整數:123 | long |
浮點數:123 | double |
字串形式的有效日期:2017-01-01 | date |
普通字串:foo bar | string |
布林型:true 或 false | boolean |
檢視對映:GET /{index}/_mapping/{type}
自定義域對映:
- 設定索引型別
"index" : "analyzed" #全文索引
"index" : "not_analyzed" #精確值索引
"index" : "no" #不索引:永遠檢索不到
- 設定分析器,預設為 standard 分析器, 但你可以指定一個內建的分析器替代它,例如 whitespace 、 simple 和 english:
"analyzer" : "english"
複雜核心域型別:
- 多值域:陣列
- 空域:將不會被索引,有如下三種形式:
"null_value": null,
"empty_array": [],
"array_with_null_value": [ null ]
- 內部域(內部物件):
#如:
{
"age": 23
"name":{
"first" : "li",
"last" : "ling",
"full" : "li ling",
}
}
將被索引為:
{
"age":[23],
"name.first":[li],
"name.last":[ling],
"name.full":[li,ling]
}
}
使用DSL查詢表示式檢索
- 兩種查詢情況:過濾情況(filtering context)與 查詢情況(query context)
過濾情況下,查詢語句被設定成不評分的查詢,只需判斷是否匹配,因此效能較好,並且會被快取。
查詢情況下,查詢語句被設定成評分的查詢,即首先判斷文件是否匹配,還要判斷文件匹配的有多好 葉子語句(leaf clauses) 與 複合語句(bool)
重要葉子語句:- match_all ,相當於空查詢
- match,即可作用於全文文字進行全文查詢,又可作用於精確值域做精確匹配
multi_match:可以作用在多個欄位上執行相同的match查詢
{ "muti_match":{ "query" : "elastic", #共同的要查詢的資訊 "fields":[ "title" , "body"] } }
range:範圍查詢(gt, gte , lt , lte)
{ "range":{ "age" : { "lt" : "30", "gt": 20 } } }
term:精確值查詢
{ "term": { "age" : 25 } } { "term": { "join_date" : "2017-01-01"} }
terms:精確值查詢,允許匹配多個值
{ "terms": { "age" : [25,27,29] } }
exists 和 missing:查詢某個欄位是否有值
{ "exists": { "field " : "title" } }
複合語句bool的可接收的引數:
- must:文件必須匹配才能被包含進來
- must_not:文件必須不匹配才能被包含進來
- should:如果滿足這些查詢中的任意語句,將增加_score,主要用於相關性得分
- filter:必須匹配,但不以評分查詢進行,只用於過濾
- constant_score:用來代替bool
{
"bool":{
"must":{"match":{"skills":["java","python"]}}, #最好會java或者python,會影響相關度得分_score
"must_not":{"range":{"exception_salary":{"gt":25000}}}, #期望薪資最好不超過25K,會影響相關度得分_score
"should" :{"match":{"skills":["hadoop","elastic"]}}, #會hadoop和elastic的優先,會影響相關度得分_score
"filter" :{"range":{"age":["lt","30"]}} # 年齡必須不能超過30歲,過濾掉30歲以上的,不參與相關度評分
}
}
- constant_score:用來代替bool,在bool裡只有filter的情況下。
{
"constant_score":{
"filter" :{"range":{"age":["lt","30"]}} # 年齡必須不能超過30歲,過濾掉30歲以上的,不參與相關度評分
}
}
驗證查詢(/_validate/query?explain)
排序
自定義排序欄位,及多級排序
預設是按照相關性排序,即按_score欄位降序,但是也可以自己指定某個欄位進行排序,也可以指定多個欄位進行多級排序
{
"query":{...},
"sort" : {
"join_date" : { "order" : "desc"}, #先按照入黨日期降序排序
"_score" : { "order" : "desc"} #再按照相關性得分降序排序
}
}
設定的排序欄位是多值欄位
,可以設定"mode" : "min" | "max" | "avg" | "sum"
{
"query":{...},
"sort" : {
"dates" : {
"mode" : "min",
"order" : "desc"
}, #先按照入黨日期降序排序
}
}
字串排序
字串被分析器分析後也是一個多值欄位,但是因為多個值的順序總是不固定的,而且用設定 “` “mode”為 “min” 或者 “max” 也並不能達到我們想要的效果。
例如,我們原本有兩個文件的content分別為“i love marry” 和 “i love lily”,我們想要的效果是按照字母順序,含 lily的文件 應該 排在 含marry的文件前。
這時,我們可以對content的域對映做如下設定:
"content": {
"type": "string",
"analyzer": "english",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed" #設定此欄位不被分析,不被分詞,
}
}
}
{"sort":{"content.raw":{"order":"desc"}}}
相關性得分的計算方式
es的相似度演算法 :檢索詞頻率/反向文件頻率= TF/IDF ,包括以下內容:
- 檢索詞頻率(TF)
檢索詞在該欄位出現的頻率?出現頻率越高,相關性也越高。 欄位中出現過 5 次要比只出現過 1 次的相關性高。 - 反向文件頻率(IDF)
每個檢索詞在索引庫中出現的頻率?頻率越高,相關性越低。檢索詞出現在多數文件中會比出現在少數文件中的權重更低。 - 欄位長度準則
欄位的長度是多少?長度越長,相關性越低。 檢索詞出現在一個短的 title 要比同樣的詞出現在一個長的 content 欄位權重更大。
單個查詢可以聯合使用 TF/IDF 和其他方式,比如短語查詢中檢索詞的距離或模糊查詢裡的檢索詞相似度。
相關性並不只是全文字檢索的專利。也適用於 yes|no 的子句,匹配的子句越多,相關性評分越高。
如果多條查詢子句被合併為一條複合查詢語句 ,比如 bool 查詢,則每個查詢子句計算得出的評分會被合併到總的相關性評分中。
explain 檢視相關性得分_score的分值來源
GET /megacorp/employee/_search?explain&format=yaml
{
"query": {
"match": {
"about": "rock"
}
}
}
"_explanation": {
"description": "weight(tweet:honeymoon in 0)
[PerFieldSimilarity], result of:",
"value": 0.076713204,
"details": [
{
"description": "fieldWeight in 0, product of:",
"value": 0.076713204,
"details": [
{
"description": "tf(freq=1.0), with freq of:",
"value": 1,
"details": [
{
"description": "termFreq=1.0", #檢索詞 `honeymoon` 在這個文件的 `tweet` 欄位中的出現次數。
"value": 1
}
]
},
{
"description": "idf(docFreq=1, maxDocs=1)", #檢索詞 `honeymoon` 在索引上所有文件的 `tweet` 欄位中出現的次數。
"value": 0.30685282
},
{
"description": "fieldNorm(doc=0)",
"value": 0.25, #在這個文件中, `tweet` 欄位內容的長度 -- 內容越長,值越小。
}
]
}
]
}
分散式檢索:Query and Fetch(查詢 然後 取回)
分頁查詢:
- 客戶端傳送檢索請求到某節點,此節點成為本次請求的協調節點,協調節點向所有相關分片所在節點發送檢索請求,
- 這些節點經過檢索後,返回給協調節點一個_id 和_score的排序列表
取回:
- 協調節點從所有其它節點返回的資料中挑選出需要被取回的_id,再向相關節點發出_mget請求,
- 相關節點根據協調節點的_mget請求,獲取豐富文件,返回給協調節點
- 協調節點返回文件給客戶端
注意:深度分頁本身並不符合人的行為,一般很少人翻到第50頁,除非是爬蟲spider或者機器人。因此最好直接做一個全域性設定,比如_score小於某個值或者是資料量少於1000等等。
搜尋引數
- preference(偏好):指定特定的節點處理檢索請求,即設定檢索偏好用哪些節點。
為了防止出現bouncing results(每次查詢結果的順序都不一樣),因為分散式、非同步的原因,主分片和每個副本分片上資料的timestamp都是不盡相同的,那如果恰好檢索是按照timestamp排序的話,而兩條資料的時間戳又是相同的,那如果兩次查詢落在了不同的分片上,就有可能出現查詢的結果的順序不一致的情況。
因此設定preference就很有必要,可以設定 _primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, 和 _shards:2,3 。
建議用隨機字串,