1. 程式人生 > >ES學習記錄11——搜尋模板(Search Template)

ES學習記錄11——搜尋模板(Search Template)

1. 搜尋模板

/_search/template端點允許使用mustache language(繼承ES沙箱指令碼語言的一種語言,是護胡言亂語嗎……)為每個呈現搜尋請求,在執行前和用模板引數填充現有的模板:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
    "source" : {
      "query": { "match" : { "{{my_field}}" : "{{my_value}}" } },
      "size" : "{{my_size}}"
    }
, "params" : { "my_field" : "message", "my_value" : "some message", "my_size" : 5 } } '

執行這個請求時,ES卡住沒有響應,一直處於loading的狀態。

模板樣例

用單值填充查詢字串

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
    "source": {
        "query": {
            "term"
: { // 使用佔位符的方式{{xxx}}使用一個變數 "user": "{{query_string}}" } } }, "params": { // 定義上述使用到的變數 "query_string": "kimchy3" } } '

轉換引數成JSON

{{#toJson}}parameter{{/toJson}}函式可以用來將map或陣列的引數parameter轉換成對應的JSON(下面是查詢使用者名稱為kimchy4kimchy3

兩個使用者):

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
    // 將變數users轉成對應的JSON
  "source": "{ \"query\": { \"terms\": {{#toJson}}users{{/toJson}} }}",
  "params": {
      // 將users變數填充到上面的函式中
    "users" : {
        "user": [ "kimchy4", "kimchy3" ]
    }
  }
}
'

最終的轉換結果(但是實際是看不到的)為:

// 轉成JSON串
"query": {
    "terms": {
        "users": {
            "user": [
                "kimchy4",
                "kimchy3"
            ]
        }
    }
}

連線陣列的值

{{#join}}array{{/join}}函式可以用來將陣列array內的元素連線為一個用逗號,分隔的字串(twitter索引中有kimchy1kimchy4kimchy,5五個文件),下面是一個栗子:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
  "source": {
    "query": {
      "match": {
        "user": "{{#join}}user{{/join}}"
      }
    }
  },
  "params": {
    "user": [ "kimchy", "5" ]
  }
}
'

這樣最終傳入的user變數為kimchy,5(由陣列[ "kimchy", "5" ]轉成kimchy,5),即直接去匹配了。上述是使用預設的分隔符(即逗號,)來連線陣列各個元素,當前這個分隔符也可以自定義,使用自定義分隔符函式{{#join delimiter='xx'}}date.formats{{/join delimiter='xx'}}即可將預設的,改為||,比如:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
  "source": {
    "query": {
      "range": {
        "born": {
            "gte"   : "{{date.min}}",
            "lte"   : "{{date.max}}",
            // 將原來逗號的分隔符改為雙豎槓||
            "format": "{{#join delimiter='||'}}date.formats{{/join delimiter='||'}}"
            }
      }
    }
  },
  "params": {
    "date": {
        "min": "2016",
        "max": "31/12/2017",
        "formats": ["dd/MM/yyyy", "yyyy"]
    }
  }
}
'

born最終的轉換為:

"born" : {
    "gte" : "2016",
    "lte" : "31/12/2017",
    "format" : "dd/MM/yyyy||yyyy"
}

預設值

預設值的寫法為:{{var}}{{^var}}default{{/var}},下面是小栗子:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
  "source": {
    "query": {
      // 查詢範圍內獲贊數的文件
      "range": {
        // 指定按獲贊數進行篩選
        "likes": {
          // 獲贊數的下限
          "gte": "{{start}}",
          // 獲贊數的上限
          "lte": "{{end}}{{^end}}20{{/end}}"
        }
      }
    }
  },
  "params": {
      ...
  }
}
'

如果params中定義startend兩個變數,比如:

  "params": {
      "start": 12,
      "end": 13
  }

那最終的查詢引數最終為:

    "line_no": {
        "gte": "12",
        "lte": "13"
    }

但如果params中只定義了start變數,沒有定義end變數,如:

  "params": {
      "start": 12
  }

那預設變數{{end}}{{^end}}20{{/end}}生效,此時查詢引數為:

    "line_no": {
        "gte": "12",
        "lte": "20"
    }

條件語句(待處理)

條件語句不能使用JSON形式的模板,而是必須用字串的方式,下面是一個查詢行號的小栗子:

{
  "query": {
    "bool": {
      "must": {
        "match": {
          // 將text引數填充
          "line": "{{text}}"
        }
      },
      // 這個過濾器只有params中指定了line_no才會建立,否則不會建立
      "filter": {
        {{#line_no}} // 這裡使用{{#xx}}..{{/xx}}包裹
          "range": {
            "line_no": {
              {{#start}}
                "gte": "{{start}}"
                {{#end}},{{/end}}
              {{/start}}
              {{#end}}
                "lte": "{{end}}"
              {{/end}}
            }
          }
        {{/line_no}}
      }
    }
  },
  "params": {
      "text": "words to search for",
      // 下面這些引數都是可選的
      "line_no": { // 這個引數可省
          "start": 10, // 可省
          "end": 20 // 可省
      }
  }
}

上述的條件語句中其實就是使用{{#A}}..B..{{/A}}將執行語句B包裹在內部,換成java裡面的寫法就是if(A != null) {B},即只有變數A不為空就執行B,否則不執行B。特別注意的是上述並不是有效的JSON格式(含有{{#line_on}}這些標記),所以需要將整個配置寫成字串的形式,注意雙引號的轉義,簡單寫成:

"source": "{\"query\":{\"bool\":{\"must\":{\"match\":{\"line\":\"{{text}}\"}},\"filter\":{{{#line_no}}\"range\":{\"line_no\":{{{#start}}\"gte\":\"{{start}}\"{{#end}},{{/end}}{{/start}}{{#end}}\"lte\":\"{{end}}\"{{/end}}}}{{/line_no}}}}}}"

這裡執行遇到如下問題(待解決):

{
    "error": {
        "root_cause": [
            {
                "type": "general_script_exception",
                "reason": "Failed to compile inline script [{\"query\":{\"bool\":{\"must\":{\"match\":{\"line\":\"{{text}}\"}},\"filter\":{{{#line_no}}\"range\":{\"line_no\":{{{#start}}\"gte\":\"{{start}}\"{{#end}},{{/end}}{{/start}}{{#end}}\"lte\":\"{{end}}\"{{/end}}}}{{/line_no}}}}}}] using lang [mustache]"
            }
        ],
        "type": "general_script_exception",
        "reason": "Failed to compile inline script [{\"query\":{\"bool\":{\"must\":{\"match\":{\"line\":\"{{text}}\"}},\"filter\":{{{#line_no}}\"range\":{\"line_no\":{{{#start}}\"gte\":\"{{start}}\"{{#end}},{{/end}}{{/start}}{{#end}}\"lte\":\"{{end}}\"{{/end}}}}{{/line_no}}}}}}] using lang [mustache]",
        "caused_by": {
            "type": "mustache_exception",
            "reason": "Improperly closed variable in query-template:1"
        }
    },
    "status": 500
}

URL編碼

{{#url}}value{{/url}}函式可以用來將HTML文字編碼為字串,比如:

curl -X GET "localhost:9200/_render/template" -H 'Content-Type: application/json' -d'
{
    "source" : {
        "query" : {
            "term": {
                "http_access_log": "{{#url}}{{host}}/{{page}}{{/url}}"
            }
        }
    },
    "params": {
        "host": "https://www.elastic.co/",
        "page": "learn"
    }
}
'

最終上述請求的結果為:

{
    "template_output": {
        "query": {
            "term": {
                "http_access_log": "https%3A%2F%2Fwww.elastic.co%2F%2Flearn"
            }
        }
    }
}

預註冊模板

通過儲存指令碼API可以註冊搜尋模板,比如:

// 註冊模板testtemplate,可以自定義
curl -X POST "localhost:9200/_scripts/testtemplate" -H 'Content-Type: application/json' -d'
{
    "script": {
        // 定義使用語言未mustache
        "lang": "mustache",
        "source": {
            "query": {
                "match": {
                    "title": "{{query_string}}"
                }
            }
        }
    }
}
'

取出上述的模板:

curl -X GET "localhost:9200/_scripts/testtemplate"

// 結果
{
    "_id": "testtemplate",
    "found": true,
    "script": {
        "lang": "mustache",
        "source": "{\"query\":{\"match\":{\"title\":\"{{query_string}}\"}}}",
        "options": {
            "content_type": "application/json; charset=UTF-8"
        }
    }
}

驗證模板(關於模板驗證見下面的驗證模板):

curl -X POST "localhost:9200/_render/template/twittertemplate" -H 'Content-Type: application/json' -d'
{
	"params": {
		"query_string": "kimchy2"
	}
}
'

// 結果
{
    "template_output": {
        "query": {
            "match": {
                "user": "kimchy2"
            }
        }
    }
}

使用上述預定義的模板可以如下使用:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
    // 這裡的id就是註冊模板的名字
    "id": "<templateName>",
    // 未模板注入引數
    "params": {
        "query_string": "search for these words"
    }
}
'

刪除模板:

curl -X DELETE "localhost:9200/_scripts/<templatename>"

下面是一套完整的模板從註冊到使用的過程:

// 1. 註冊模板
curl -X POST "localhost:9200/_scripts/twittertemplate" -H 'Content-Type: application/json' -d'
{
    "script": {
        "lang": "mustache",
        "source": {
            "query": {
                "match": {
                    "user": "{{query_string}}"
                }
            }
        }
    }
}
'

// 2. 使用模板
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
    "id": "twittertemplate",
    "params": {
        "query_string": "kimchy2"
    }
}
'

// 結果
{
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 15,
        "successful": 15,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 0.6931472,
        "hits": [
            {
                "_index": "twitter",
                "_type": "_doc",
                "_id": "2",
                "_score": 0.6931472,
                "_source": {
                    "user": "kimchy2",
                    "likes": 9,
                    "post_date": 1542197883,
                    "message": "trying out Elasticsearch"
                }
            }
        ]
    }
}

驗證模板

模板可以帶著使用的引數出現在響應中,比如:

curl -X GET "localhost:9200/_render/template" -H 'Content-Type: application/json' -d'
{
  "source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} }}",
  "params": {
    "statuses" : {
        "status": [ "pending", "published" ]
    }
  }
}
'

// 結果
{
    "template_output": {
        "query": {
            "terms": {
                "status": [
                    "pending",
                    "published"
                ]
            }
        }
    }
}

通過結果看其實就是看一下轉換的結果(可以帶入引數檢視)。

得分說明

使用模板時也可以使用explain引數開啟得分說明:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
	"id": "twittertemplate",
	"params": {
		"query_string": "kimchy2"
	},
	"explain": true
}
'

// 結果
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 15,
        "successful": 15,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 0.6931472,
        "hits": [
            {
                "_shard": "[twitter][2]",
                "_node": "bKeGC-Q-SXuyyGlcarDrMg",
                "_index": "twitter",
                "_type": "_doc",
                "_id": "2",
                "_score": 0.6931472,
                "_source": {
                    "user": "kimchy2",
                    "likes": 9,
                    "post_date": 1542197883,
                    "message": "trying out Elasticsearch"
                },
                "_explanation": {
                    "value": 0.6931472,
                    "description": "weight(user:kimchy2 in 0) [PerFieldSimilarity], result of:",
                    "details": [
                        {
                            "value": 0.6931472,
                            "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
                            "details": [
                                {
                                    "value": 0.6931472,
                                    "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                                    "details": [
                                        {
                                            "value": 1,
                                            "description": "docFreq",
                                            "details": []
                                        },
                                        {
                                            "value": 2,
                                            "description": "docCount",
                                            "details": []
                                        }
                                    ]
                                },
                                {
                                    "value": 1,
                                    "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1) from:",
                                    "details": [
                                        {
                                            "value": 1,
                                            "description": "termFreq=1.0",
                                            "details": []
                                        },
                                        {
                                            "value": 1.2,
                                            "description": "parameter k1",
                                            "details": []
                                        },
                                        {
                                            "value": 0,
                                            "description": "parameter b (norms omitted for field)",
                                            "details": []
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            }
        ]
    }
}

配置

使用模板時也可以使用profile引數:

curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
  "id": "twittertemplate",
  "params": {
	"query_string": "kimchy2"
  },
  "profile": true
}
'

2. 多搜尋模板(Mutil Search Template)

 多搜尋模板API可以使用_msearch/template端點執行多個搜尋模板請求(和多搜尋請求API類似),形式如下:

header\n
body\n
header\n
body\n

header\n:頭部header和多搜尋API類似支援相同的indexsearch_typepreferencerouting選項;
body\n:體body包括一個搜尋模板體請求並支援內聯、儲存和檔案模板,如下:

$ cat requests
{"index": "test"}
// 內聯搜尋模板請求
{"source": {"query": {"match":  {"user" : "{{username}}" }}}, "params": {"username": "john"}}
{"source": {"query": {"{{query_type}}": {"name": "{{name}}" }}}, "params": {"query_type": "match_phrase_prefix", "name": "Smith"}}
{"index": "_all"}
// 基於儲存模板的搜尋模板請求
{"id": "template_1", "params": {"query_string": "search for these words" }}

$ curl -H "Content-Type: application/x-ndjson" -XGET localhost:9200/_msearch/template --data-binary "@requests"; echo