ES學習第二篇
一、與elastic search的交互
我是Java工程師,相應的我就說我們是怎麼交互的。
1.1 Java API
在代碼中你可以使用 Elasticsearch 內置的兩個客戶端:
1.1.1 節點客戶端
節點客戶端作爲一個非數據節點加入到本地集羣中。換句話說,它本身不保存任何數據,但是它知道數據在集羣中的哪個節點中,並且可以把請求轉發到正確的節點。
1.1.2 傳輸客戶端
輕量級的傳輸客戶端可以將請求發送到遠程集羣。它本身不加入集羣,但是它可以將請求轉發到集羣中的一個節點上。
1.1.3 小總結
兩個 Java 客戶端都是通過 9300 端口並使用 Elasticsearch 的原生 傳輸 協議和集羣交互。集羣中的節點通過端口 9300 彼此通信。如果這個端口沒有打開,節點將無法形成一個集羣。
注意:Java 客戶端作爲節點必須和 Elasticsearch 有相同的 主要 版本;否則,它們之間將無法互相連接。
1.2 RESTful API with JSON over HTTP
所有其他語言可以使用 RESTful API 通過端口 9200 和 Elasticsearch 進行通信,你可以用你最喜愛的 web 客戶端訪問 Elasticsearch 。事實上,正如你所看到的,你甚至可以使用 curl
命令來和 Elasticsearch 交互。
一個 Elasticsearch 請求和任何 HTTP 請求一樣由若干相同的部件組成:
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
被 < >
標記的部件:
VERB |
適當的 HTTP 方法 或 謂詞 : GET 、 POST 、 PUT 、 HEAD 或者 DELETE 。 |
---|---|
PROTOCOL |
http 或者 https (如果你在 Elasticsearch 前面有一個 https 代理) |
HOST |
Elasticsearch 集羣中任意節點的主機名,或者用 localhost 代表本地機器上的節點。 |
PORT |
運行 Elasticsearch HTTP 服務的端口號,默認是 9200 。 |
PATH |
API 的終端路徑(例如 _count 將返回集羣中文檔數量)。Path 可能包含多個組件,例如:_cluster/stats 和 _nodes/stats/jvm 。 |
QUERY_STRING |
任意可選的查詢字符串參數 (例如 ?pretty 將格式化地輸出 JSON 返回值,使其更容易閱讀) |
BODY |
一個 JSON 格式的請求體 (如果請求需要的話) |
例如,計算集羣中文檔的數量,我們可以用這個:
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}
'
Elasticsearch 返回一個 HTTP 狀態碼(例如:200 OK
)和(除HEAD
請求)一個 JSON 格式的返回值。前面的 curl
請求將返回一個像下面一樣的 JSON 體:
{
"count" : 0,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
}
}
縮寫格式就是省略請求中所有相同的部分,例如主機名、端口號以及 curl
命令本身。
curl -XGET 'localhost:9200/_count?pretty' -d '
等價於:
GET /_count
二、文獻檢索
2.1 檢索文檔
目前我們已經在 Elasticsearch 中存儲了一些數據, 接下來就能專注於實現應用的業務需求了。第一個需求是可以檢索到單個僱員的數據。
這在 Elasticsearch 中很簡單。簡單地執行 一個 HTTP GET
請求並指定文檔的地址——索引庫、類型和ID。 使用這三個信息可以返回原始的 JSON 文檔:
GET /megacorp/employee/1
返回結果包含了文檔的一些元數據,以及 _source
屬性,內容是 John Smith 僱員的原始 JSON 文檔:
{
"_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" ]
}
}
2.2 輕量搜索
一個 GET
是相當簡單的,可以直接得到指定的文檔。 現在嘗試點兒稍微高級的功能,比如一個簡單的搜索!
第一個嘗試的幾乎是最簡單的搜索了。我們使用下列請求來搜索所有僱員:
GET /megacorp/employee/_search
可以看到,我們仍然使用索引庫 megacorp
以及類型 employee
,但與指定一個文檔 ID 不同,這次使用 _search
。返回結果包括了所有三個文檔,放在數組 hits
中。一個搜索默認返回十條結果。
{
"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",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_score": 1,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
注意:返回結果不僅告知匹配了哪些文檔,還包含了整個文檔本身:顯示搜索結果給最終用戶所需的全部信息。
接下來,嘗試下搜索姓氏爲 Smith
的僱員。爲此,我們將使用一個 高亮 搜索,很容易通過命令行完成。這個方法一般涉及到一個 查詢字符串 (query-string) 搜索,因爲我們通過一個URL參數來傳遞查詢信息給搜索接口:
GET /megacorp/employee/_search?q=last_name:Smith
我們仍然在請求路徑中使用 _search
端點,並將查詢本身賦值給參數 q=
。返回結果給出了所有的 Smith:
{
...
"hits": {
"total": 2,
"max_score": 0.30685282,
"hits": [
{
...
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
...
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
2.3 使用表達式搜索
Elasticsearch 提供一個豐富靈活的查詢語言叫做 查詢表達式 , 它支持構建更加複雜和健壯的查詢。這比輕量搜索更加方便。
領域特定語言 (DSL), 使用 JSON 構造了一個請求。我們可以像這樣重寫之前的查詢所有名爲 Smith 的搜索 :
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"last_name" : "Smith"
}
}
}
返回結果與之前的查詢一樣,但還是可以看到有一些變化。其中之一是,不再使用 query-string 參數,而是一個請求體替代。這個請求使用 JSON 構造,並使用了一個 match
查詢(屬於查詢類型之一,後面將繼續介紹)。
2.4 更復雜的搜索
現在嘗試下更復雜的搜索。 同樣搜索姓氏爲 Smith 的員工,但這次我們只需要年齡大於 30 的。查詢需要稍作調整,使用過濾器 filter ,它支持高效地執行一個結構化查詢。
GET /megacorp/employee/_search
{
"query" : {
"bool": {
"must": {
"match" : {
//這部分與我們之前使用的 match 查詢 一樣。
"last_name" : "smith"
}
},
"filter": {
"range" : {
//這部分是一個 range 過濾器 , 它能找到年齡大於 30 的文檔,其中 gt 表示_大於(_great than)。
"age" : { "gt" : 30 }
}
}
}
}
}
2.5 全文搜索
截止目前的搜索相對都很簡單:單個姓名,通過年齡過濾。現在嘗試下稍微高級點兒的全文搜索——一項 傳統數據庫確實很難搞定的任務。
搜索下所有喜歡攀巖(rock climbing)的員工:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"about" : "rock climbing"
}
}
}
顯然我們依舊使用之前的 match
查詢在about
屬性上搜索 “rock climbing” 。得到兩個匹配的文檔:
{
...
"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" ]
}
}
]
}
}
其中_source
爲相關性得分
2.6 短語檢索
找出一個屬性中的獨立單詞是沒有問題的,但有時候想要精確匹配一系列單詞或者短語 。 比如, 我們想執行這樣一個查詢,僅匹配同時包含 “rock” 和 “climbing” ,並且 二者以短語 “rock climbing” 的形式緊挨着的僱員記錄。
爲此對 match
查詢稍作調整,使用一個叫做 match_phrase
的查詢:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}
2.7 高亮搜索
許多應用都傾向於在每個搜索結果中 高亮 部分文本片段,以便讓用戶知道爲何該文檔符合查詢條件。在 Elasticsearch 中檢索出高亮片段也很容易。
再次執行前面的查詢,並增加一個新的 highlight
參數:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
當執行該查詢時,返回結果與之前一樣,與此同時結果中還多了一個叫做 highlight
的部分。這個部分包含了 about
屬性匹配的文本片段,並以 HTML 標籤 <em></em>
封裝:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
},
"highlight": {
"about": [
"I love to go <em>rock</em> <em>climbing</em>"
]
}
}
]
}
}
三、分析
終於到了最後一個業務需求:支持管理者對員工目錄做分析。 Elasticsearch 有一個功能叫聚合(aggregations),允許我們基於數據生成一些精細的分析結果。聚合與 SQL 中的 GROUP BY
類似但更強大。
舉個例子,挖掘出員工中最受歡迎的興趣愛好:
GET /megacorp/employee/_search
{
"aggs": {
"all_interests": {
"terms": { "field": "interests" }
}
}
}
暫時忽略掉語法,直接看看結果:
{
...
"hits": { ... },
"aggregations": {
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
可以看到,兩位員工對音樂感興趣,一位對林業感興趣,一位對運動感興趣。這些聚合的結果數據並非預先統計,而是根據匹配當前查詢的文檔即時生成的。如果想知道叫 Smith 的員工中最受歡迎的興趣愛好,可以直接構造一個組合查詢:
GET /megacorp/employee/_search
{
"query": {
"match": {
"last_name": "smith"
}
},
"aggs": {
"all_interests": {
"terms": {
"field": "interests"
}
}
}
}
all_interests
聚合已經變爲只包含匹配查詢的文檔:
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "sports",
"doc_count": 1
}
]
}
聚合還支持分級彙總 。比如,查詢特定興趣愛好員工的平均年齡:
GET /megacorp/employee/_search
{
"aggs" : {
"all_interests" : {
"terms" : { "field" : "interests" },
"aggs" : {
"avg_age" : {
"avg" : { "field" : "age" }
}
}
}
}
}
得到的聚合結果有點兒複雜,但理解起來還是很簡單的:
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2,
"avg_age": {
"value": 28.5
}
},
{
"key": "forestry",
"doc_count": 1,
"avg_age": {
"value": 35
}
},
{
"key": "sports",
"doc_count": 1,
"avg_age": {
"value": 25
}
}
]
}
輸出基本是第一次聚合的加強版。依然有一個興趣及數量的列表,只不過每個興趣都有了一個附加的 avg_age
屬性,代表有這個興趣愛好的所有員工的平均年齡。