如何使用Elasticsearch脚本
Elasticsearch默认脚本语言为Painless
。其他lang插件使您可以运行以其他语言编写的脚本。
语言 | 沙盒 | 是否需要插件 | 备注 |
---|---|---|---|
painless | 是 | 内置的 | 默认脚本语言 |
expression | 是 | 内置的 | 快速的自定义排名和排序 |
mustache | 是 | 内置的 | 范本 |
脚本语法
"script": {
"lang": "...",
"source" | "id": "...",
"params": { ... }
}
参数解释
参数名称 | 作用 |
---|---|
lang | 脚本使用的语言 |
source | 内联脚本,或者id存储脚本指定脚本本身 |
params | 应该传递给脚本的任何命名参数 |
官方文档中提到一个情况:Elasticsearch遇到一个新脚本时,将对其进行编译并将编译后的版本存储在缓存中。编译可能是一个繁重的过程。如果我们有相同逻辑的脚本只是参数不一样的时候,应以命名形式传递变量,而不是将值硬编码到脚本本身中。
第一个脚本每一次乘数的改变都必须重新编译。
"source": "doc['my_field'] * 2"
第二个脚本参数发生改变的时候,只有在第一次遇见的时候进行编译。
"source": "doc['my_field'] * multiplier",
"params": {
"multiplier": 2
}
简单脚本
当脚本中source
只有很简单的内容,不涉及参数变化等复杂逻辑可以直接在script
后使用字符串而不是对象。
下面两个脚本是等价的:
“ script” :“ ctx._source.likes ++”
"script": {
"source": "ctx._source.likes++"
}
保存脚本
我们可以将脚本保存在集群中,然后使用它的时候只需要指定其ID。
脚本保存
保存脚本需要使用/_scripts/{id}
的POST
请求
POST _scripts/calculate-score
{
"script": {
"lang": "painless",
"source": "Math.log(_score * 2) + params.my_modifier"
}
}
查询脚本
查询脚本需要使用/_scripts/{id}
的GET
请求
GET _scripts/calculate-score
使用保存的脚本
后续再使用脚本的时候就可以指定ID,而不是再次编写脚本了。
GET _search
{
"query": {
"script": {
"script": {
"id": "calculate-score",
"params": {
"my_modifier": 2
}
}
}
}
}
删除脚本
DELETE _scripts/calculate-score
默认情况下,脚本没有基于时间的到期,但是可以通过script.cache.expire
设置来更改此行为。也可以使用该script.cache.max_size
设置来配置此缓存的大小。默认情况下,缓存大小为100。
脚本中访问文档字段和特殊变量
根据脚本使用的不同位置,它们可以访问的变量的文档字段是不同的。
更新操作中脚本访问数据
在update
,update-by-query
或reindex API
中使用的脚本将有权访问显示以下内容的ctx变量
参数 | 作用 |
---|---|
ctx._source | 访问文档_source字段 |
ctx.op | 应用于文档的操作:index或delete |
ctx._index | 访问文档元字段,其中某些字段可能是只读的 |
在搜索和聚合中脚本访问数据
进行搜索和聚合操作中的脚本,除了会对命中的数据进行脚本字段操作之外,针对可能与查询或汇总匹配的文档都会执行一次。这使得搜索中脚本操作的文档量会根据实际拥有的文档数量,对于比较大的业务数据会非常庞大,所以要求脚本必须足够的效率。
官方建议使用doc-values
或 stored fields
段或_source field
从脚本访问字段值 ,
官方提供的三种访问方式
访问语法 | 使用场景 | 说明 |
---|---|---|
doc[‘field_name’] | 需要注意的是doc值只能返回“简单”的字段值,如数字、日期、地理点、术语等(numeric , date , or geopoint | 目前从脚本中获取字段值最快的方式 |
_fields[‘field_name’].value或_fields[‘field_name’] | 字段必须被设置为stored | |
params._source.field_name | 普通的访问方式 |
创建例子
现在创建一个测试用的索引
PUT test_index/_mapping
{
"properties": {
"name": {
"type": "text",
"store": true
},
"desc": {
"type": "text"
},
"num": {
"type":"long"
}
}
}
PUT test_index/_doc/2?refresh
{
"name":"测试",
"desc":"描述",
"num" : 100
}
doc-values
目前从脚本中获取字段值最快的方式是使用
doc['field_name']
格式,此方法会从doc值中检索字段数据,但是需要注意的是需要注意的是doc值只能返回“简单”的字段值,如数字、日期、地理点、术语等,如果字段是多值的,则只能返回这些值的数组。它不能返回JSON对象。
在对上面的索引执行脚本test
的时候会成功完成, 但是执行脚本test2
的时候会发生错误
GET test_index/_search
{
"script_fields": {
"test": {
"script": {
"lang": "expression",
"source": "doc['num']"
}
}
}
}
GET test_index/_search
{
"script_fields": {
"test2": {
"script": {
"lang": "expression",
"source": "doc['name']"
}
}
}
}
执行脚本test2
会发生错误
"reason": "Fielddata is disabled on text fields by default.
Set fielddata=true on [name] in order to load fielddata in memory by uninverting the inverted index.
Note that this can however use significant memory.
Alternatively use a keyword field instead."
官方对此说明是
如果启用了fielddata, doc[‘field’]语法也可以用于分析的文本字段,但是要注意:在文本字段上启用fielddata需要将所有的术语加载到JVM堆中,这在内存和CPU方面都非常昂贵。从脚本访问文本字段很少有意义。
params._fields[‘field_name’]
使用params._fields['field_name']
可以访问被设置为"store": true
的参数。但是对没有设置此参数的数据使用此方法时会抛出异常
GET test_index/_search
{
"script_fields": {
"test": {
"script": {
"lang": "painless",
"source": "params._fields['desc']"
}
}
}
}
上面请求的desc
没有被设置"store": true
此时会返回内容
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "cannot write xcontent for unknown value of type class org.elasticsearch.search.lookup.FieldLookup"
}
],
"type": "illegal_argument_exception",
"reason": "cannot write xcontent for unknown value of type class org.elasticsearch.search.lookup.FieldLookup"
}
params._source.field_name
此方式对数据的访问没有其他限制
三种访问方式对比
-
存储字段取值方式比doc取值慢得多。doc值针对访问许多文档中特定字段的值进行了优化,使用_source或存储字段取值它们针对每个结果返回多个字段进行了优化。对于需要获取结果中内容生成脚本字段时是有意义的。但是对于一些聚合或者排序操作的时候,doc取值会更好。
-
_source和存储字段取值。_source字段只是一个特殊的存储字段,因此其性能与其他存储字段类似。使用存储字段取值而不是_source字段取值可能的场景是在当在_source非常大的时候访问几个小的存储字段而不是整个_source的成本更低。
脚本安全性的配置
默认情况下,允许执行所有脚本类型。通过修改script.allowed_types
我们可以指定允许执行的类型;
## 只允许执行内联脚本,而不允许执行存储脚本(或任何其他类型)。
script.allowed_types: inline
默认情况下,允许执行所有脚本上下文。通过修改cript.allowed_contexts
只允许一部分操作执行
## 这将只允许执行搜索和更新脚本,而不允许执行aggs或插件脚本(或任何其他上下文)
script.allowed_contexts: search, update
个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。