概要
本篇主要介紹一下搜索模板、映射模板、高亮搜索和地理位置的簡單玩法。
標準搜索模板
搜索模板search
tempalte高級功能之一,可以將我們的一些搜索進行模板化,使用現(xiàn)有模板時傳入指定的參數(shù)就可以了,避免編寫重復代碼。對常用的功能可以利用模板進行封裝,使用時更簡便。
這點類似于我們編程時的接口封裝,將一些細節(jié)處理的東西封裝成接口,供別人調(diào)用,使用者就只需要關(guān)注參數(shù)和響應結(jié)果就行,這樣可以更好地提高代碼復用率。
下面我們來看看最基本的幾種用法
參數(shù)替換
GET /music/children/_search/template { "source": { "query": { "match": {
"{{field}}":"{{value}}" } } }, "params": { "field":"name", "value":"bye-bye" } }
該搜索模板編譯后等同于:
GET /music/children/_search { "query": { "match": { "name":"bye-bye" } } }
使用Json格式的條件查詢
{{#toJson}}塊內(nèi)可以寫稍微復雜一些的條件
GET /music/children/_search/template { "source": "{\"query\":{\"match\":
{{#toJson}}condition{{/toJson}}}}", "params": { "condition": { "name":"bye-bye"
} } }
該搜索模板編譯后等同于如下:
GET /music/children/_search { "query": { "match": { "name":"bye-bye" } } }
join語法
join內(nèi)的參數(shù)names可以寫多個:
GET /music/children/_search/template { "source": { "query": { "match": {
"name": "{{#join delimiter=' '}}names{{/join delimiter=' '}}" } } }, "params":
{ "name":["gymbo","you are my sunshine","bye-bye"] } }
該搜索模板編譯后等同于如下:
GET /music/children/_search { "query": { "match": { "name":"gymbo you are my
sunshine bye-bye" } } }
搜索模板的默認值設置
可以對搜索模板進行一些默認值的設置,如{{^end}}500表示如果end參數(shù)為空,默認值為500
GET /music/children/_search/template { "source":{ "query":{ "range":{
"likes":{ "gte":"{{start}}", "lte":"{{end}}{{^end}}500{{/end}}" } } } },
"params": { "start":1, "end":300 } }
該搜索模板編譯后等同于:
GET /music/children/_search { "query": { "range": { "likes": { "gte": 1,
"lte": 300 } } } }
條件判斷
在Mustache語言中,它沒有if/else這樣的判斷,但是你可以定section來跳過它如果那個變量是false還是沒有被定義
{{#param1}} "This section is skipped if param1 is null or false" {{/param1}}
示例:創(chuàng)建mustache scripts對象
POST _scripts/condition { "script": { "lang": "mustache", "source": """ {
"query": { "bool": { "must": { "match": { "name": "{{name}}" } }, "filter":{
{{#isLike}} "range":{ "likes":{ {{#start}} "gte":"{{start}}" {{#end}},{{/end}}
{{/start}} {{#end}} "lte":"{{end}}" {{/end}} } } {{/isLike}} } } } } """ } }
使用mustache template查詢:
GET _search/template { "id": "condition", "params": { "name":"gymbo",
"isLike":true, "start":1, "end":500 } }
以上是常用的幾種搜索模板介紹,如果在大型項目,并且配置了專門的Elasticsearch工程師,就經(jīng)常會用一些通用的功能進行模板化,開發(fā)業(yè)務系統(tǒng)的童鞋只需要使用模板即可。
定制映射模板
ES有自己的規(guī)則對插入的數(shù)據(jù)進行類型映射,如10,會自動映射成long類型,"10"會自動映射成text,還會自帶一個keyword的內(nèi)置field。方便是很方便,但有時候這些類型不是我們想要的,比如我們的整數(shù)值10,我們期望是這個integer類型,"10"我們希望是keyword類型,這時候我們可以預先定義一個模板,插入數(shù)據(jù)時,相關(guān)的field就按我們預先定義的規(guī)則進行匹配,決定這個field值的類型。
另外要聲明一下,實際工作中編碼規(guī)范一般嚴謹一些,所有的document都是預先定義好類型再執(zhí)行數(shù)據(jù)插入的,哪怕是中途增加的field,也是先執(zhí)行mapping命令,再插入數(shù)據(jù)的。
但自定義動態(tài)映射模板也需要了解一下。
默認的動態(tài)映射效果
試著插入一條數(shù)據(jù):
PUT /test_index/type/1 { "test_string":"hello kitty", "test_number":10 }
查看mapping信息
GET /test_index/_mapping/type
響應如下:
{ "test_index": { "mappings": { "type": { "properties": { "test_number": {
"type": "long" }, "test_string": { "type": "text", "fields": { "keyword": {
"type": "keyword", "ignore_above": 256 } } } } } } } }
默認的動態(tài)映射規(guī)則,可能不是我們想要的。
例如,我們希望數(shù)字類型的默認是integer類型,字符串默認是string類型,但是內(nèi)置的field名字叫raw,不叫keyword,保留128個字符。
動態(tài)映射模板
有兩種方式:
* 根據(jù)新加入的field的默認的數(shù)據(jù)類型,來進行匹配,匹配某個預定義的模板
* 根據(jù)新加入的field的名字,去匹配預定義的名字,或者去匹配一個預定義的通配符,然后匹配上某個預定義的模板
根據(jù)數(shù)據(jù)類型進行匹配
PUT /test_index { "mappings": { "type": { "dynamic_templates": [ { "integers"
: { "match_mapping_type": "long", "mapping": { "type":"integer" } } }, {
"strings" : { "match_mapping_type": "string", "mapping": { "type":"text",
"fields": { "raw": { "type": "keyword", "ignore_above": 128 } } } } } ] } } }
刪除索引,重新插入數(shù)據(jù),查看mapping信息如下:
{ "test_index": { "mappings": { "type": { "dynamic_templates": [ { "integers":
{ "match_mapping_type": "long", "mapping": { "type": "integer" } } }, {
"strings": { "match_mapping_type": "string", "mapping": { "fields": { "raw": {
"ignore_above": 128, "type": "keyword" } }, "type": "text" } } } ],
"properties": { "test_number": { "type": "integer" }, "test_string": { "type":
"text", "fields": { "raw": { "type": "keyword", "ignore_above": 128 } } } } } }
} }
以按預計類型進行映射,符合預期。
* 按field名稱進行映射
* "long_"開頭的field,并且原本是long類型的,轉(zhuǎn)換為integer類型
* "string_"開頭的field,并且原本是string類型的,轉(zhuǎn)換為string.raw類型
"_text"結(jié)尾的field,并且原本是string類型的,保持不變 PUT /test_index { "mappings": { "type": {
"dynamic_templates":[ { "long_as_integer": { "match_mapping_type":"long",
"match": "long_*", "mapping":{ "type":"integer" } } }, { "string_as_raw": {
"match_mapping_type":"string", "match": "string_*", "unmatch":"*_text",
"mapping": { "type":"text", "fields": { "raw": { "type": "keyword",
"ignore_above": 128 } } } } } ] } } }
插入數(shù)據(jù):
PUT /test_index/type/1 { "string_test":"hello kitty", "long_test": 10,
"title_text":"Hello everyone" }
查詢mapping信息
{ "test_index": { "mappings": { "type": { "dynamic_templates": [ {
"long_as_integer": { "match": "long_*", "match_mapping_type": "long",
"mapping": { "type": "integer" } } }, { "string_as_raw": { "match": "string_*",
"unmatch": "*_text", "match_mapping_type": "string", "mapping": { "fields": {
"raw": { "ignore_above": 128, "type": "keyword" } }, "type": "text" } } } ],
"properties": { "long_test": { "type": "integer" }, "string_test": { "type":
"text", "fields": { "raw": { "type": "keyword", "ignore_above": 128 } } },
"title_text": { "type": "text", "fields": { "keyword": { "type": "keyword",
"ignore_above": 256 } } } } } } } }
結(jié)果符合預期。
在某些日志管理的場景中,我們可以定義好type,每天按日期創(chuàng)建一個索引,這種索引的創(chuàng)建就可以用到映射模板,把我們定義的映射關(guān)系全部做進去。
高亮搜索
我們在瀏覽器上搜索文本時,發(fā)現(xiàn)我們輸入的關(guān)鍵字有高亮顯示,查看html源碼就知道,高亮的部分是加了<em>
標簽的,ES也支持高亮搜索這種操作的,并且在返回的文檔中自動加了<em>標簽,兼容html5頁面。
highlight基本語法
我們還是以音樂網(wǎng)站為案例,開始進行高亮搜索:
GET /music/children/_search { "query": { "match": { "content": "love" } },
"highlight": { "fields": { "content": {} } } }
highlight里面的參數(shù)即為高亮搜索的語法,指定高亮的字段為content,我們可以看到命中的Love里面帶了<em>高亮標簽,<em></em>
表現(xiàn)在html上會變成紅色,所以說你的指定的field中,如果包含了那個搜索詞的話,就會在那個field的文本中,對搜索詞進行紅色的高亮顯示。
{ "took": 35, "timed_out": false, "_shards": { "total": 5, "successful": 5,
"skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 0.2876821,
"hits": [ { "_index": "music", "_type": "children", "_id": "5", "_score":
0.2876821, "_source": { "id": "1740e61c-63da-474f-9058-c2ab3c4f0b0a",
"author_first_name": "Jean", "author_last_name": "Ritchie", "author": "Jean
Ritchie", "name": "love somebody", "content": "love somebody, yes I do",
"language": "english", "tags": "love", "length": 38, "likes": 3, "isRelease":
true, "releaseDate": "2019-12-22" }, "highlight": { "content": [ "<em>love</em>
somebody, yes I do" ] } } ] } }
highlight下的字段可以指定多個,這樣就可以在多個字段命中的關(guān)鍵詞進行高亮顯示,例如:
GET /music/children/_search { "query": { "match": { "content": "love" } },
"highlight": { "fields": { "name":{}, "content": {} } } }
三種高亮語法
有三種高亮的語法:
* plain highlight:使用standard Lucene highlighter,對簡單的查詢支持度非常好。
* unified highlight:默認的高亮語法,使用Lucene Unified
Highlighter,將文本切分成句子,并對句子使用BM25計算詞條的score,支持精準查詢和模糊查詢。
* fast vector highlighter:使用Lucene Fast Vector
highlighter,功能很強大,如果在mapping中對field開啟了term_vector,并設置了with_positions_offsets,就會使用該highlighter,對內(nèi)容特別長的文本(大于1MB)有性能上的優(yōu)勢。
例如:
PUT /music { "mappings": { "children": { "properties": { "name": { "type":
"text", "analyzer": "ik_max_word" }, "content": { "type": "text", "analyzer":
"ik_max_word", "term_vector" : "with_positions_offsets" } } } } }
一般情況下,用plain highlight也就足夠了,不需要做其他額外的設置
如果對高亮的性能要求很高,可以嘗試啟用unified highlight
如果field的值特別大,超過了1M,那么可以用fast vector highlight
自定義高亮html標簽
我們知道高亮的默認標簽是<em>,這個標簽可以自己定義的,然后使用自己喜歡的樣式:
GET /music/children/_search { "query": { "match": { "content": "Love" } },
"highlight": { "pre_tags": ["<tag1>"], "post_tags": ["</tag2>"], "fields": {
"content": { "type": "plain" } } } }
高亮片段fragment的設置
針對一些很長的文本,我們不可能在頁面上完整顯示的,我們需要只顯示有關(guān)鍵詞的上下文即可,這里設置fragment就行:
GET /_search { "query" : { "match": { "content": "friend" } }, "highlight" : {
"fields" : { "content" : {"fragment_size" : 150, "number_of_fragments" : 3,
"no_match_size": 150 } } } }
fragment_size: 設置要顯示出來的fragment文本判斷的長度,默認是100。
number_of_fragments:你可能你的高亮的fragment文本片段有多個片段,你可以指定就顯示幾個片段。
地理位置
現(xiàn)在基于地理位置的app層出不窮,支持地理位置的組件也有不少,Elasticsearch也不例外,并且ES可以把地理位置、全文搜索、結(jié)構(gòu)化搜索和分析結(jié)合到一起,我們來看一下。
geo point數(shù)據(jù)類型
Elasticsearch基于地理位置的搜索,有一個專門的對象geo_point存儲地理位置信息(經(jīng)度,緯度),并且提供了一些基本的查詢方法,如geo_bounding_box。
建立geo_point類型的mapping
PUT /location { "mappings": { "hotels": { "properties": { "location": {
"type": "geo_point" }, "content": { "type": "text" } } } } }
插入數(shù)據(jù)
推薦使用如下插入數(shù)據(jù)方式:
#latitude:維度,longitude:經(jīng)度 PUT /location/hotels/1 { "content":"7days hotel",
"location": { "lon": 113.928619, "lat": 22.528091 } }
還有兩種插入數(shù)據(jù)的方式,但特別容易搞混經(jīng)緯度的位置,所以不是很推薦:
# location中括號內(nèi),前一個是經(jīng)度,后一個是緯度 PUT /location/hotels/2 { "content":"7days hotel
", "location": [113.923567,22.523988] } # location中,前一個是緯度,后一個是經(jīng)度 PUT
/location/hotels/3 { "text": "7days hotel Orient Sunseed Hotel", "location":
"22.521184, 113.914578" }
查詢方法
geo_bounding_box查詢,查詢某個矩形的地理位置范圍內(nèi)的坐標點
GET /location/hotels/_search { "query": { "geo_bounding_box": { "location": {
"top_left":{ "lon": 112, "lat": 23 }, "bottom_right":{ "lon": 114, "lat": 21 }
} } } }
常見查詢場景
geo_bounding_box方式
GET /location/hotels/_search { "query": { "bool": { "must": [ {"match_all":
{}} ], "filter": { "geo_bounding_box": { "location": { "top_left":{ "lon": 112,
"lat": 23 }, "bottom_right":{ "lon": 114, "lat": 21 } } } } } } }
geo_polygon方式,三個點組成的多邊形(三角形)區(qū)域
支持多邊形,只是這個過濾器使用代價很大,盡量少用。
GET /location/hotels/_search { "query": { "bool": { "must": [ {"match_all":
{}} ], "filter": { "geo_polygon": { "location": { "points": [ {"lon":
115,"lat": 23}, {"lon": 113,"lat": 25}, {"lon": 112,"lat": 21} ] } } } } } }
geo_distance方式
根據(jù)當前位置的距離進行搜索,非常實用
GET /location/hotels/_search { "query": { "bool": { "must": [ {"match_all":
{}} ], "filter": { "geo_distance": { "distance": 500, "location": { "lon":
113.911231, "lat": 22.523375 } } } } } }
按距離排序
根據(jù)當前位置進行條件搜索,會指定一個距離的上限,2km或5km,并且符合條件查詢的結(jié)果顯示與當前位置的距離(可以指定單位),并且按從近到遠排序,這個是非常常用的場景。
請求示例:
GET /location/hotels/_search { "query": { "bool": { "must": [ {"match_all":
{}} ], "filter": { "geo_distance": { "distance": 2000, "location": { "lon":
113.911231, "lat": 22.523375 } } } } }, "sort": [ { "_geo_distance": {
"location": { "lon": 113.911231, "lat": 22.523375 }, "order": "asc", "unit":
"m", "distance_type": "plane" } } ] }
* filter.geo_distance.distance: 最大的距離,這里是2000m
* _geo_distance: 固定寫法,下面為指定位置的經(jīng)緯度
* order: 排序方式,asc或desc
* unit: 距離的單位,m/km都行
* distance_type: 計算距離的方式,sloppy_arc (默認值), arc (精準的) and plane (最快速的)
響應如下:
"hits": [ { "_index": "location", "_type": "hotels", "_id": "3", "_score":
null, "_source": { "text": "7days hotel Orient Sunseed Hotel", "location":
"22.521184, 113.914578" }, "sort": [ 421.35435857277366 ] }, { "_index":
"location", "_type": "hotels", "_id": "2", "_score": null, "_source": {
"content": "7days hotel", "location": [ 113.923567, 22.523988 ] }, "sort": [
1268.8952707727062 ] }
sort里面的內(nèi)容,就是與當前位置的地面距離,單位是m。
統(tǒng)計我當前位置幾個范圍內(nèi)酒店的數(shù)量
unit表示距離單位,常用的是mi和km。
distance_type表示計算距離的方式,sloppy_arc (默認值), arc (精準的) and plane (最快速的)。
GET /location/hotels/_search { "size": 0, "aggs": { "group_by_distance": {
"geo_distance": { "field": "location", "origin": { "lon": 113.911231, "lat":
22.523375 }, "unit": "mi", "distance_type": "arc", "ranges": [ {"from": 0,"to":
500}, {"from": 500,"to": 1500}, {"from": 150,"to": 2000} ] } } } }
小結(jié)
本篇簡單介紹了一下搜索模板、映射模板、高亮搜索和地理位置的簡單玩法,有些ES相關(guān)的項目做得比較深的,搜索模板和映射模板用處還是很大的。高亮搜索一般體現(xiàn)在瀏覽器搜索引擎上,地理位置的應用挺有意思,也可以參與到基于Location的APP應用當中。
專注Java高并發(fā)、分布式架構(gòu),更多技術(shù)干貨分享與心得,請關(guān)注公眾號:Java架構(gòu)社區(qū)
可以掃左邊二維碼添加好友,邀請你加入Java架構(gòu)社區(qū)微信群共同探討技術(shù)
熱門工具 換一換