概要
本篇主要介紹一下搜索模板、映射模板、高亮搜索和地理位置的簡單玩法。
標准搜索模板
搜索模板search tempalte高級功能之一,可以將我們的一些搜索進行模板化,使用現有模板時傳入指定的參數就可以了,避免編寫重復代碼。對常用的功能可以利用模板進行封裝,使用時更簡便。
這點類似於我們編程時的接口封裝,將一些細節處理的東西封裝成接口,供別人調用,使用者就只需要關注參數和響應結果就行,這樣可以更好地提高代碼復用率。
下面我們來看看最基本的幾種用法
參數替換
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}}塊內可以寫稍微復雜一些的條件
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內的參數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參數為空,默認值為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}}
示例:創建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工程師,就經常會用一些通用的功能進行模板化,開發業務系統的童鞋只需要使用模板即可。
定制映射模板
ES有自己的規則對插入的數據進行類型映射,如10,會自動映射成long類型,"10"會自動映射成text,還會自帶一個keyword的內置field。方便是很方便,但有時候這些類型不是我們想要的,比如我們的整數值10,我們期望是這個integer類型,"10"我們希望是keyword類型,這時候我們可以預先定義一個模板,插入數據時,相關的field就按我們預先定義的規則進行匹配,決定這個field值的類型。
另外要聲明一下,實際工作中編碼規范一般嚴謹一些,所有的document都是預先定義好類型再執行數據插入的,哪怕是中途增加的field,也是先執行mapping命令,再插入數據的。
但自定義動態映射模板也需要了解一下。
默認的動態映射效果
試着插入一條數據:
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
}
}
}
}
}
}
}
}
默認的動態映射規則,可能不是我們想要的。
例如,我們希望數字類型的默認是integer類型,字符串默認是string類型,但是內置的field名字叫raw,不叫keyword,保留128個字符。
動態映射模板
有兩種方式:
- 根據新加入的field的默認的數據類型,來進行匹配,匹配某個預定義的模板
- 根據新加入的field的名字,去匹配預定義的名字,或者去匹配一個預定義的通配符,然后匹配上某個預定義的模板
根據數據類型進行匹配
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
}
}
}
}
}
]
}
}
}
刪除索引,重新插入數據,查看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類型的,轉換為integer類型
- "string_"開頭的field,並且原本是string類型的,轉換為string.raw類型
"_text"結尾的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
}
}
}
}
}
]
}
}
}
插入數據:
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
}
}
}
}
}
}
}
}
結果符合預期。
在某些日志管理的場景中,我們可以定義好type,每天按日期創建一個索引,這種索引的創建就可以用到映射模板,把我們定義的映射關系全部做進去。
高亮搜索
我們在瀏覽器上搜索文本時,發現我們輸入的關鍵字有高亮顯示,查看html源碼就知道,高亮的部分是加了<em>
標簽的,ES也支持高亮搜索這種操作的,並且在返回的文檔中自動加了<em>
標簽,兼容html5頁面。
highlight基本語法
我們還是以音樂網站為案例,開始進行高亮搜索:
GET /music/children/_search
{
"query": {
"match": {
"content": "love"
}
},
"highlight": {
"fields": {
"content": {}
}
}
}
highlight里面的參數即為高亮搜索的語法,指定高亮的字段為content,我們可以看到命中的Love里面帶了<em>
高亮標簽,<em></em>
表現在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下的字段可以指定多個,這樣就可以在多個字段命中的關鍵詞進行高亮顯示,例如:
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,對內容特別長的文本(大於1MB)有性能上的優勢。
例如:
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的設置
針對一些很長的文本,我們不可能在頁面上完整顯示的,我們需要只顯示有關鍵詞的上下文即可,這里設置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文本片段有多個片段,你可以指定就顯示幾個片段。
地理位置
現在基於地理位置的app層出不窮,支持地理位置的組件也有不少,Elasticsearch也不例外,並且ES可以把地理位置、全文搜索、結構化搜索和分析結合到一起,我們來看一下。
geo point數據類型
Elasticsearch基於地理位置的搜索,有一個專門的對象geo_point存儲地理位置信息(經度,緯度),並且提供了一些基本的查詢方法,如geo_bounding_box。
建立geo_point類型的mapping
PUT /location
{
"mappings": {
"hotels": {
"properties": {
"location": {
"type": "geo_point"
},
"content": {
"type": "text"
}
}
}
}
}
插入數據
推薦使用如下插入數據方式:
#latitude:維度,longitude:經度
PUT /location/hotels/1
{
"content":"7days hotel",
"location": {
"lon": 113.928619,
"lat": 22.528091
}
}
還有兩種插入數據的方式,但特別容易搞混經緯度的位置,所以不是很推薦:
# location中括號內,前一個是經度,后一個是緯度
PUT /location/hotels/2
{
"content":"7days hotel ",
"location": [113.923567,22.523988]
}
# location中,前一個是緯度,后一個是經度
PUT /location/hotels/3
{
"text": "7days hotel Orient Sunseed Hotel",
"location": "22.521184, 113.914578"
}
查詢方法
geo_bounding_box查詢,查詢某個矩形的地理位置范圍內的坐標點
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方式,三個點組成的多邊形(三角形)區域
支持多邊形,只是這個過濾器使用代價很大,盡量少用。
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方式
根據當前位置的距離進行搜索,非常實用
GET /location/hotels/_search
{
"query": {
"bool": {
"must": [
{"match_all": {}}
],
"filter": {
"geo_distance": {
"distance": 500,
"location": {
"lon": 113.911231,
"lat": 22.523375
}
}
}
}
}
}
按距離排序
根據當前位置進行條件搜索,會指定一個距離的上限,2km或5km,並且符合條件查詢的結果顯示與當前位置的距離(可以指定單位),並且按從近到遠排序,這個是非常常用的場景。
請求示例:
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: 固定寫法,下面為指定位置的經緯度
- 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里面的內容,就是與當前位置的地面距離,單位是m。
統計我當前位置幾個范圍內酒店的數量
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}
]
}
}
}
}
小結
本篇簡單介紹了一下搜索模板、映射模板、高亮搜索和地理位置的簡單玩法,有些ES相關的項目做得比較深的,搜索模板和映射模板用處還是很大的。高亮搜索一般體現在瀏覽器搜索引擎上,地理位置的應用挺有意思,也可以參與到基於Location的APP應用當中。
專注Java高並發、分布式架構,更多技術干貨分享與心得,請關注公眾號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信群共同探討技術