35

一步步拆解解决 Elasticsearch 检索模板问题

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI2NDY1MTA3OQ%3D%3D&%3Bmid=2247485512&%3Bidx=1&%3Bsn=ae0b09080240e98ea5d5c22edae81d76
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1、线上实战提问

Elasticsearch做模版查询的时候,在使用 terms 进行批量查询的时候放入数组在模版中进行查询失败,类似于模版传入数组该如何实现?

问题来源:死磕Elasticsearch知识星球

# 定义索引
PUT uint-2020-08-17
{
    "mappings": {
      "properties": {
        "clock": {
          "type": "date",
          "format": "epoch_second"
        },
        "itemid": {
          "type": "long"
        },
        "ns": {
          "type": "long"
        },
        "ttl": {
          "type": "long"
        },
        "value": {
          "type": "long"
        }
      }
    }
}

# 添加内容
PUT uint-2020-08-17/_bulk
{ "index" : {  "_id" : "1" } }
{"itemid":1,"ns":643214179,"clock":1597752311,"value":"1123","ttl":604800}
{ "index" : {  "_id" : "2" } }
{"itemid":2,"ns":643214179,"clock":1597752311,"value":"123555","ttl":604800}
{ "index" : {  "_id" : "3" } }
{"itemid":3,"ns":643214179,"clock":1597752311,"value":"1","ttl":604800}
{ "index" : {  "_id" : "4" } }
{"itemid":4,"ns":643214179,"clock":1597752311,"value":"134","ttl":604800}
{ "index" : {  "_id" : "5" } }
{"itemid":2,"ns":643214179,"clock":1597752311,"value":"123556","ttl":604800}

查询语句:

PUT _scripts/item_agg
{
  "script": {
    "lang": "mustache",
    "source": {
      "_source": [
        "value"
      ],
      "size": 0,
      "query": {
        "bool": {
          "filter": [
            {
              "terms": "{{#toJson}}statuses{{/toJson}}"
            },
            {
              "range": {
                "clock": {
                  "gte": "{{startTime}}",
                  "lte": "{{endTime}}"
                }
              }
            }
          ]
        }
      },
      "aggs": {
        "group_terms": {
          "terms": {
            "field": "itemid"
          },
          "aggs": {
            "avg_value": {
              "avg": {
                "field": "value"
              }
            },
            "max_value": {
              "max": {
                "field": "value"
              }
            }
          }
        }
      }
    }
  }
}

查询模版参数:

POST uint-*/_search/template
{
  "id": "item_agg",
  "params": {
    "itemid":{
      "statuses":[1,2]
    },
    "startTime":1597752309,
    "endTime":1597752333
    
  }
}

以上内容看着很长,根据注释拆解为:

  • 定义索引、

  • 插入数据、

  • 创建模板、

  • 构造参数检索

四个子部分你就不会恐慌了。

2、知识点解读——搜索模板

2.1 什么是搜索模板?

很多人都听说使用过 索引模板 index template,索引模板的好处:

  • 便于跨索引统一建模;

  • 尤其适合数据量巨大、索引字段类似的业务系统;

  • 灵活便捷。

检索模板(search template)大家使用相对较少,在实战业务场景中:每次业务请求都要构造 DSL,比如:这次查title、下次查content,除此之外的 DSL 部分 都一样,但两次请求:后端代码那里就要有相应的修改和适配。有没有不修改、拼接DSL使用检索的方案?这就引出了搜索模板。

搜索模板与关系数据库中的存储过程非常相似。可以将常用查询定义为模板,并且使用 Elasticsearch 的应用程序可以简单地通过其 ID 引用查询。

模板接受在运行时指定参数。搜索模板存储在服务器端,可以在不更改客户端代码的情况下进行修改。

模板使用Mustache模板引擎表示。关于 Mustache 可以访问:

http://mustache.github.io/mustache.5.html

2.2 搜索模板举例

根据第一部分实战中的数据,定义了如下的模板。

PUT _scripts/cur_search_template
{
  "script": {
    "lang": "mustache",
    "source": {
      "query": {
      "match": {
        "{{cur_field}}": "{{cur_value}}"
      }
    },
    "size": "{{cur_size}}"
    }
  }
}

POST uint-*/_search/template
{
  "id": "cur_search_template",
  "params": {
    "cur_field":"itemid",
    "cur_value":1,
    "cur_size":50
    
  }
}

该模板:支持用户自定义动态设置搜索字段及搜索参数字段。

实战中可以通过如下_scripts 的方式,将检索模板定义到服务器端。

如果想检索别的字段:客户端或者请求端传递不同的参数即可。

真正意义上的实现了:检索和请求参数的 分离

更多原理和基础参见官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html

2.3 search template 的语法很让人头脑

以下内容摘自:Wood 大叔的——Elastic认证考试心得。

按照要求写一个search template

熟悉search template的mustache模版语言即可轻松写出,但是很遗憾,平常没用过search template,虽然知道个大概,但是当时写的时候,不知道哪里语法有问题,PUT template总是不成功。猜想可能是哪个位置的字符没有转译产生非法json字符,或者哪一层嵌套有问题。总之就是调试不成功,又浪费了很多时间。

https://elasticsearch.cn/article/6133

如上引用想说明的是:search template的语法 比较复杂,如果没用过,很容易头大

3、问题拆解

3.1 原有DSL有错吗?

实战一把,报错如下:

{
  "error": {
    "root_cause": [
      {
        "type": "parsing_exception",
        "reason": "[terms] query malformed, no start_object after query name",
        "line": 1,
        "col": 67
      }
    ],
    "type": "parsing_exception",
    "reason": "[terms] query malformed, no start_object after query name",
    "line": 1,
    "col": 67
  },
  "status": 400
}

3.2 哪里出了问题?

拆解一下。

  • script 部分无非包括:检索部分和聚合部分。

  • 检索部分是定义 search template 的核心,聚合部分无需关注。

这个时候,可以写一个检索 DSL验证一下是否ok,如下:

POST /_search
{
  "_source": [
    "value"
  ],
  "size": 0,
  "query": {
    "bool": {
      "filter": [
        {
          "terms": {
            "itemid": [
              1,
              2
            ]
          }
        },
        {
          "range": {
            "clock": {
              "gte": 1597752309,
              "lte": 1597752333
            }
          }
        }
      ]
    }
  },
  "aggs": {
    "group_terms": {
      "terms": {
        "field": "itemid"
      },
      "aggs": {
        "avg_value": {
          "avg": {
            "field": "value"
          }
        },
        "max_value": {
          "max": {
            "field": "value"
          }
        }
      }
    }
  }
}
  • 而检索和聚合都没错,那多半就是定义 search template 部分出错了。

问题就这么一点点拆解了。

上来直接改这个 DSL貌似也无从下手,那咱们就做:最小化处理吧。

抛去所有:_source、size、aggs、range query 部分,只保留 terms 脚本应该怎么正确的写?

来吧,实战一把:

  • 第一步:最小化 terms 检索模板。

GET _search/template
{
  "source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} }}",
  "params": {
    "statuses" : {
        "itemid": [ 1, 2 ]
    }
  }
}

用现在正确的对比第一部分出错的,可以找到如下两处错误:

  • 错误1:source 里面的内容要加:"\" 。

  • 错误2:查询模版参数中的 statuses 和 itemid 位置写错了。

官方文档的说法:

The {{#toJson}}parameter{{/toJson}} function can be used to convert parameters like maps and array to their JSON representation:

statuses 就是个辅助参数,我们核心的参数是 itemid。

  • 第二步:将第一步内容转成script 形式。

POST _scripts/test_script_01
{
  "script": {
    "lang": "mustache",
    "source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} }}"
  }
}

POST uint-*/_search/template 
{
  "id": "test_script_01",
  "params": {
    "statuses": {
      "itemid": [
        1,
        2
      ]
    },
    "startTime": 1597752309,
    "endTime": 1597752333
  }
}
  • 第三步:按照实战要求补全参数即可。

注意补全的时候,我建议:拷贝 DSL(格式化一行的版本)到第三方文本工具如:Nodepad++,全局替换。

I7FZjmV.png!mobile

切记不要手敲,很容易出错。

替换到模板的 source 部分,然后再根据第一步、第二步内容修改即可。

  • 实战问题答案

GET _search/template
{
  "source": "{\"_source\":[\"value\"],\"size\":0,\"query\":{\"bool\":{\"filter\":[{\"terms\":{{#toJson}}statuses{{/toJson}}},{\"range\":{\"clock\":{\"gte\":{{startTime}},\"lte\":{{endTime}}}}}]}},\"aggs\":{\"group_terms\":{\"terms\":{\"field\":\"itemid\"},\"aggs\":{\"avg_value\":{\"avg\":{\"field\":\"value\"}},\"max_value\":{\"max\":{\"field\":\"value\"}}}}}}",
    "params": {
    "statuses" : {
        "itemid": [ 1, 2 ]
    },
       "startTime":1597752309,
    "endTime":1597752333
  }
}

拷贝 source 部分转换为脚本格式就可以,篇幅问题,不再赘述。

4、小结

看似复杂,拆解后便不复杂。

看似很难,拆解后就很简单。

检索模板用的好,前后端扯皮少、效率高很多!

你的小问题,我的大问题。

和你一起,死磕 Elasticsearch!

参考:

https://elastic-search-in-action.medcl.com/3.site_search/3.3.search_box/search_template/

https://subscription.packtpub.com/book/big_data_and_business_intelligence/9781787128453/7/ch07lvl1sec61/search-templates

推荐:

重磅 | 死磕 Elasticsearch 方法论认知清单(2020年国庆更新版)

能拿驾照就能通过 Elastic 认证考试!

fyQ3mqI.png!mobile

短时间 快习得 多干货!

中国 40%+  Elastic 认证工程师出自于此!

全球   800+  Elastic 爱好者一起死磕 Elasticsearch!

加微信: elastic6 ,索要价值18元星球入场券


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK