Elasticsearch 脚本功能

1. 脚本功能的基本介绍

1.1 使用场景

当需要生成自定义字段或者需要自定义评分时,可以考虑使用脚本。

1.2 基本使用模式
  "script": {
    "lang":   "...",  			//语言
    "source" | "id": "...", 	//脚本代码或者脚本ID
    "params": { ... } 			//参数
  }
1.3 默认脚本语言的变化

--《Breaking Changes in 5.0》
从5.0版本开始,默认脚本语言从groovy改为painless,一个主要的差别是对脚本参数的使用,groovy中可以直接使用,painless需要加"params"前缀。
Groovy:

{
  "script_score": {
    "script": {
      "lang": "groovy",
      "source": "Math.log(_score * 2) + my_modifier",
      "params": {
        "my_modifier": 8
      }
    }
  }
}

Painless (my_modifer is prefixed with params):

{
  "script_score": {
    "script": {
      "lang": "painless",
      "source": "Math.log(_score * 2) + params.my_modifier",
      "params": {
        "my_modifier": 8
      }
    }
  }
}

如果需要使用painless之外的脚本语言,需要在"script"."lang"参数中显示指定,目前支持的语言如下:
在这里插入图片描述
在这里插入图片描述

1.4 一些注意事项

1.脚本的编辑是一个耗时操作,ES对脚本只编译一次,然后将其缓存到内层,因此,脚本中的参数尽量写到"params"对象中,而不要使用硬编码:

"source": "doc['my_field'] * 2"

应该写成:

  "source": "doc['my_field'] * multiplier",
  "params": {
    "multiplier": 2
  }

2.不能在短时间内提交大量不同的脚本, 如果ES在短时间内编译了过多的脚本,抛出circuit_breaking_exception异常。默认每分钟不超过15个,可以通过"script.max_compilations_rate"动态设置。

3.为了减少编译次数,所有脚本都会被缓存。默认最多缓存100个脚本,但不设置过期时间,可通过 script.cache.expire 和 script.cache.max_size 修改。

4.存储脚本最长支持65,535字节,可以通过 script.max_size_in_bytes 设置,但还要考虑脚本引擎的上限。

2 关于Stored Script

如果脚本基本不会变动,且需要反复使用,则可以将其持久化到集群状态中 /_scripts/{id}:

POST _scripts/calculate-score
{
  "script": {
    "lang": "painless",
    "source": "Math.log(_score * 2) + params.my_modifier"
  }
}

GET _scripts/calculate-score

GET _search
{
  "query": {
    "script": {
      "script": {
        "id": "calculate-score",
        "params": {
          "my_modifier": 2
        }
      }
    }
  }
}

DELETE _scripts/calculate-score

3.使用实例

3.1 样例数据

准备两条商品数据

{
	"id": "1",
	"name": "L'oreal/欧莱雅复颜玻尿酸水光充盈导入膨润精华液",
	"brand": "欧莱雅",
	"price": 279,
	"desc": "补水 提拉紧致 淡化细纹"
}
{
	"id": "2",
	"name": "资生堂面膜",
	"brand": "资生堂",
	"price": 199,
	"desc": "补水 美白 修复",
	"comments": ["女朋友说很好用!",
	"一直用的这款,价格也实惠。"]
}
3.2 脚本字段(新增自定义字段)

新增一个自定义字段"档次",当"price">=250元"档次"=“高档”,反之"档次"=“中低档”。

GET test_index_20211220/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "档次": {
      "script": {
        "source": " if(doc['price'].value >= 250) {return '高档' } else { return '中低档' }"
      }
    }
  }
}
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 2,
    "successful": 2,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "test_index_20211220",
        "_type": "e-com",
        "_id": "1",
        "_score": 1,
        "fields": {
          "档次": [
            "高档"
          ]
        }
      },
      {
        "_index": "test_index_20211220",
        "_type": "e-com",
        "_id": "2",
        "_score": 1,
        "fields": {
          "档次": [
            "中低档"
          ]
        }
      }
    ]
  }
}

这里和预想的有点不一样,明明有查询所有文档,但是只返回了脚本字段,没有返回文档字段,如果想要返回文档字段,需要使用"_source"显示控制:

{
  "_source": {
    "includes": [
      "id",
      "name",
      "brand",
      "price",
      "desc"
    ]
  },
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "档次": {
      "script": {
        "source": " if(doc['price'].value >= 250) {return '高档' } else { return '中低档' }"
      }
    }
  }
}
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 2,
    "successful": 2,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "test_index_20211220",
        "_type": "e-com",
        "_id": "1",
        "_score": 1,
        "_source": {
          "price": 279,
          "name": "L'oreal/欧莱雅复颜玻尿酸水光充盈导入膨润精华液",
          "id": "1",
          "brand": "欧莱雅",
          "desc": "补水 提拉紧致 淡化细纹"
        },
        "fields": {
          "档次": [
            "高档"
          ]
        }
      },
      {
        "_index": "test_index_20211220",
        "_type": "e-com",
        "_id": "2",
        "_score": 1,
        "_source": {
          "price": 199,
          "name": "资生堂面膜",
          "id": "2",
          "brand": "资生堂",
          "desc": "补水 美白 修复"
        },
        "fields": {
          "档次": [
            "中低档"
          ]
        }
      }
    ]
  }
}
3.3 在搜索和聚合中使用脚本

在这里插入图片描述
除了脚本字段,也可以在搜索上下文或者聚合上下文中使用脚本,但是与脚本字段不同的是,脚本字段是先搜索命中,再执行脚本。而搜索和聚合是先执行脚本,再搜索或者聚合,因此最后的性能取决于索引的文档数以及脚本的执行效率。

3.3.1 在搜索中使用脚本
GET test_index_20211220/_search
{
  "query": {
    "script": {
      "script": "return doc['price'].value >= 250"
    }
  }
}

// 放到filter上下文避免打分
GET test_index_20211220/_search
{
  "query": {
    "bool": {
      "filter": {
        "script": {
          "script": "return doc['price'].value < 250"
        }
      }
    }
  }
}

搜索结果和一般搜索结果一样,这里不贴了。

3.3.2 在聚合中使用脚本

按价格档次分类:

GET test_index_20211220/_search
{
  "_source": {
    "includes": [
      "id",
      "name",
      "brand",
      "price",
      "desc"
    ]
  },
  "query": {
    "match_all": {}
  },
  "aggs": {
    "档次统计": {
      "terms": {
        "script": {
          "source": " if(doc['price'].value >= 250) {return '高档' } else { return '中低档' }"
        }
      }
    }
  }
}
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 2,
    "successful": 2,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "test_index_20211220",
        "_type": "e-com",
        "_id": "1",
        "_score": 1,
        "_source": {
          "price": 279,
          "name": "L'oreal/欧莱雅复颜玻尿酸水光充盈导入膨润精华液",
          "id": "1",
          "brand": "欧莱雅",
          "desc": "补水 提拉紧致 淡化细纹"
        }
      },
      {
        "_index": "test_index_20211220",
        "_type": "e-com",
        "_id": "2",
        "_score": 1,
        "_source": {
          "price": 199,
          "name": "资生堂面膜",
          "id": "2",
          "brand": "资生堂",
          "desc": "补水 美白 修复"
        }
      }
    ]
  },
  "aggregations": {
    "档次统计": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "中低档",
          "doc_count": 1
        },
        {
          "key": "高档",
          "doc_count": 1
        }
      ]
    }
  }
}
3.3.3 可以使用脚本的地方

以下图片来自ES1.7版本官网文档,可能已经有更新。
在这里插入图片描述

3.4 如何在脚本中访问文档值

在脚本中,一共有三种方式访问文档值:

  1. doc-values
  2. stored fields
  3. _source field
3.4.1 doc[‘field_name’]

速度最快,特点:

  1. 不可访问开启分词的字段
  2. 不能访问JSON对象类型字段(对象类型和嵌套类型等等)
  3. 可以访问一般非JSON对象数组
    在这里插入图片描述
3.4.2 _fields[‘field_name’]

速度比doc[]方式慢,特点:

  1. 需要开启字段的 “store”: true
  2. 不会被加载到内存
  3. 通过下面两种方式访问:
_fields['field_name'].value
_fields['field_name'].values
3.4.3 _source.field_name.field_name

_source是特殊的stored field,所以速度和_fields[]相当,特点是可以访问对象类型,通过连续"."好分割每层字段名

缺点是在_source文档内容很大的时候,用_source访问的每次开销都比较大:

if at all possible, because _source must be loaded and parsed every
time it’s used. Using _source is very slow.

但是对类似top ten结果做脚本字段操作时,_source的开销就可能可以忽略了:
在这里插入图片描述
同样,如果source很大,_fields也会比source快:
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值