干货 | Elasticsearch7.X Scripting脚本使用详解

0、题记
除了官方文档,其余能找到的介绍Elasticsearch脚本(Scripting)的资料少之又少。html

一方面:性能问题。
官方文档性能优化中明确指出使用脚本会致使性能低;java

另外一方面:使用场景相对少。
非复杂业务场景下,基础的增、删、改、查基本上就能搞定。git

但,不可否认,在解决复杂业务问题(如:自定义评分、自定义文本相关度、自定义过滤、自定义聚合分析)时,脚本依然是Elasticsearch强悍的利器之一。github

本文在官方文档基础上,结合实际业务场景,在Elasticsearch7.3环境下进行脚本使用解读。express

一、官方scripting使用建议
Avoid scripts——In general, scripts should be avoided.
If they are absolutely needed, you should prefer the painless and expressions engines.api

ebay在性能优化实践中也强调(本文作了扩展延伸):数组

避免使用脚本查询(script query)计算动态字段。安全

例如:咱们有一个包含大量剧院信息的索引,咱们须要查询以"Down"开头的全部剧院。你可能运行一个以下脚本查询:性能优化

1POST seats/_search
2{
3 "query": {
4 "bool":{
5 "filter": {
6 "script":{
7 "script":{
8 "lang":"painless",
9 "source": "doc['theatre'].value.startsWith('Down')"
10 }
11 }
12 }
13 }
14 }
15}app

这个查询很是耗费资源,而且减慢整个系统。

解决方案:

方案一:prefix前缀匹配;实测性能:prefix较scripting性能提高5倍。

方案二:索引时考虑添加一个名为“theatre_prefix”的keyword类型字段。而后咱们能够查询"theatre_prefix":"Down"。

二、ES Scripting历史

版本 使用脚本
< Elasticsearch 1.4

MVEL 脚本

< Elasticsearch 5.0

Groovy 脚本

‘>= Elasticsearch 5.0

painless 脚本

Groovy 的出现是解决MVEL的安全隐患问题;
但Groovy仍存在内存泄露+安全漏洞问题,

painless脚本的官宣时间:2016年9月21日。看似很新,截止目前,已经三年左右时间了。

正如其名字:无痛。painless的出现是为了用户更方便、高效的使用脚本。

https://www.elastic.co/cn/blog/painless-a-new-scripting-language

三、Painless Scripting 简介
Painless是一种简单,安全的脚本语言,专为与Elasticsearch一块儿使用而设计。它是Elasticsearch的默认脚本语言,能够安全地用于内联和存储脚本。

Painless特色:

性能牛逼:Painless脚本运行速度比备选方案(包括Groovy)快几倍。

安全性强:使用白名单来限制函数与字段的访问,避免了可能的安全隐患。

可选输入:变量和参数可使用显式类型或动态def类型。

上手容易:扩展了java的基本语法,并兼容groove风格的脚本语言特性。

特定优化:是ES官方专为Elasticsearch脚本编写而设计。

四、Scripting 应用场景
认知前提:
增删改查能解决业务场景80%的问题,Painless脚本操做通常应用于相对复杂的业务场景中。

常见场景举例以下:

自定义字段

自定义评分

自定义更新

自定义reindex

聚合

其余自定义操做

五、Scripting 使用模板
心中有模板,脚本认知就有了“套路”。

1"script": {
2 "lang": "...",
3 "source" | "id": "...",
4 "params": { ... }
5 }

lang:表明language脚本语言,默认指定为:painless。

source:脚本的核心部分,id应用于:stored script。

params:传递给脚本使用的变量参数。

六、Scripting 实战
6.1 自定义字段
举例:返回原有Mapping未定义的字段值。
如:以my_doubled_field返回my_field字段的翻倍后的结果。

1GET my_index/_search
2{
3 "script_fields": {
4 "my_doubled_field": {
5 "script": {
6 "lang": "expression",
7 "source": "doc['my_field'] * multiplier",
8 "params": {
9 "multiplier": 2
10 }
11 }
12 }
13 }
14}

注意:这里脚本语言选择的expression,下一节讲解。

如:返回日期字段中的“年”或“月”或“日”等。

1GET hockey/_search
2{
3 "script_fields": {
4 "birth_year": {
5 "script": {
6 "source": "doc.born.value.year"
7 }
8 }
9 }
10}

6.2 自定义评分
1GET my_index/_search
2{
3 "query": {
4 "function_score": {
5 "query": {
6 "match": {
7 "text": "quick brown fox"
8 }
9 },
10 "script_score": {
11 "script": {
12 "lang": "expression",
13 "source": "_score * doc['popularity']"
14 }
15 }
16 }
17 }
18}

6.3 自定义更新
Update:将已有字段值赋值给其余字段。

1POST hockey/_update/1
2{
3 "script": {
4 "lang": "painless",
5 "source": """
6 ctx._source.last = params.last;
7 ctx._source.nick = params.nick
8 """,
9 "params": {
10 "last": "gaudreau",
11 "nick": "hockey"
12 }
13 }
14}

Update_by_query:知足b开头(注意正则)的字段,末尾添加matched。

1POST hockey/_update_by_query
2{
3 "script": {
4 "lang": "painless",
5 "source": """
6 if (ctx._source.last =~ /b/) {
7 ctx._source.last += "matched";
8 } else {
9 ctx.op = "noop";
10 }
11 """
12 }
13}

6.4 自定义reindex
Elasticsearch认证考试题:

有index_a包含一些文档, 要求建立索引index_b,经过reindex api将index_a的文档索引到index_b。

要求:
1)增长一个整形字段,value是index_a的field_x的字符长度;

2)再增长一个数组类型的字段,value是field_y的词集合。

(field_y是空格分割的一组词,比方"foo bar",索引到index_b后,要求变成["foo", "bar"])

1POST _reindex
2{
3 "conflicts": "proceed",
4 "source": {
5 "index": "index_a"
6 },
7 "dest": {
8 "index": "index_b"
9 },
10 "script": {
11 "source": "ctx._source.parts = / /.split(ctx._source.address); ctx._source.tag = ctx._source.city.length();"
12 }
13}

语法参考:

https://www.elastic.co/guide/en/elasticsearch/painless/7.3/painless-regexes.html

6.5 聚合
1GET /_search
2{
3 "aggs" : {
4 "genres" : {
5 "terms" : {
6 "script" : {
7 "source": "doc['genre'].value",
8 "lang": "painless"
9 }
10 }
11 }
12 }
13
14}

6.6 其余自定义操做
须要结合业务去实践。

七、常见坑及问题
7.1 脚本只有Painless吗?
显然不是,第6节用到的expression 是Lucene’s expressions 脚本语言。

还能够基于脚本引擎本身开发插件实现,

https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-engine.html

7.2 怎么界定是expressions 仍是Painless?
"lang": "painless",
"lang": "expressions ",
是惟一区分。

7.3 使用painless就百分之百“无痛”,无漏洞后顾之忧了吗?
凡事不能绝对。
核心注意点:
第一:不要root帐户下运行Elasticsearch。
第二:不要公开ES路径给其余用户。
第三:不要公开ES路径到互联网。

实战推荐:

一、用户在搜索框中键入文本,文本将直接发送到后台的match、match_phrase、Simple query string或 Suggesters.

二、做为应用程序开发过程的一部分(而非所有)开放上述查询的脚本。

三、使用用户提供的参数运行脚本。

四、文档固定的Mapping结构。

不推荐:

一、用户能够编写任意scripts, queries(检索), _search requests(search请求)。

二、文档结构能够用户自定义。

八、小结
本文讲解了脚本的发展历史、使用场景、应用实战,但相比于实际业务的复杂需求仍然是九牛一毛。

实战中,确定还会遇到这样、那样的问题。

一方面:欢迎留言交流。
另外一方面:多研读官方文档,不少细节值得深究。

参考:
https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html
https://www.infoq.cn/article/elasticsearch-performance-tuning-practice-at-ebay
https://github.com/laoyang360/deep_elasticsearch/blob/master/es_dsl_study/6.scripting.md
https://github.com/elastic/elasticsearch/issues/19396
https://www.youtube.com/watch?v=3FLEJJ8PsM4
https://blog.csdn.net/u013613428/article/details/78134170

相关文章
相关标签/搜索