正在加载

如何编写脚本

Elastic Stack Serverless

在 Elasticsearch API 中任何支持脚本的地方,语法都遵循相同的模式:您指定脚本的语言,提供脚本逻辑(或源代码),并添加传递到脚本中的参数

"script": {
  "lang":   "...",
  "source" | "id": "...",
  "params": { ... }
}
lang
指定编写脚本所使用的语言。默认为 painless
source, id
脚本本身,您可以将内联脚本指定为 source,或将存储的脚本指定为 id。使用存储的脚本 API 创建和管理存储的脚本。
params
指定作为变量传递到脚本中的任何命名参数。 使用参数 而不是硬编码的值来减少编译时间。

Painless 是 Elasticsearch 的默认脚本语言。它安全、高效,并且为具有少量编码经验的任何人提供自然的语法。

Painless 脚本的结构为一个或多个语句,并且可以选择在开头有一个或多个用户定义的函数。一个脚本必须始终至少有一个语句。

Painless 执行 API 提供使用简单的用户定义参数测试脚本并接收结果的能力。 让我们从一个完整的脚本开始,并回顾它的组成部分。

首先,索引一个带有单个字段的文档,以便我们有一些数据可以使用

 PUT my-index-000001/_doc/1 {
  "my_field": 5
}

然后,我们可以构建一个操作该字段的脚本,并在查询中评估该脚本。 以下查询使用搜索 API 的 script_fields 参数来检索脚本估值。 这里发生了很多事情,但是我们将分解这些组件以单独理解它们。 现在,您只需要了解此脚本采用 my_field 并对其进行操作。

 GET my-index-000001/_search {
  "script_fields": {
    "my_doubled_field": {
      "script": {
        "source": "doc['my_field'].value * params['multiplier']",
        "params": {
          "multiplier": 2
        }
      }
    }
  }
}
  1. script 对象
  2. script 源代码

script 是一个标准的 JSON 对象,用于定义 Elasticsearch 中大多数 API 下的脚本。 此对象需要 source 来定义脚本本身。 该脚本没有指定语言,因此默认为 Painless。

Elasticsearch 第一次看到新脚本时,它会编译该脚本并将编译后的版本存储在缓存中。 编译可能是一个繁重的过程。 不要在脚本中硬编码值,而是将它们作为命名 params 传递。

例如,在前面的脚本中,我们可以直接硬编码值并编写一个表面上不太复杂的脚本。 我们可以只检索 my_field 的第一个值,然后将其乘以 2

"source": "return doc['my_field'].value * 2"

尽管它可以工作,但是此解决方案非常不灵活。 我们必须修改脚本源代码才能更改乘数,并且 Elasticsearch 每次更改乘数时都必须重新编译脚本。

使用命名 params 而不是硬编码值,以使脚本灵活,并减少脚本运行时编译时间。 现在,您可以更改 multiplier 参数,而无需 Elasticsearch 重新编译脚本。

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

默认情况下,您每 5 分钟最多可以编译 150 个脚本。 对于摄取上下文,默认的脚本编译速率是无限的。

script.context.field.max_compilations_rate=100/10m
重要提示

如果在短时间内编译太多唯一的脚本,Elasticsearch 会拒绝新的动态脚本,并显示 circuit_breaking_exception 错误。

使用 Painless 本身提供的语法功能,您可以减少脚本中的冗长程度并使其更短。 这是一个简单的脚本,我们可以使其更短

 GET my-index-000001/_search {
  "script_fields": {
    "my_doubled_field": {
      "script": {
        "lang":   "painless",
        "source": "doc['my_field'].value * params.get('multiplier');",
        "params": {
          "multiplier": 2
        }
      }
    }
  }
}

让我们看一下该脚本的缩短版本,以了解它比以前的迭代版本包含哪些改进

 GET my-index-000001/_search {
  "script_fields": {
    "my_doubled_field": {
      "script": {
        "source": "field('my_field').get(null) * params['multiplier']",
        "params": {
          "multiplier": 2
        }
      }
    }
  }
}

此版本的脚本删除了多个组件并显着简化了语法

  • lang 声明。 因为 Painless 是默认语言,所以如果您正在编写 Painless 脚本,则无需指定该语言。
  • return 关键字。 Painless 自动使用脚本中的最后一个语句(如果可能)来在需要返回值的脚本上下文中生成返回值。
  • get 方法,已替换为括号 []。 Painless 专门为 Map 类型使用快捷方式,该快捷方式允许我们使用括号代替较长的 get 方法。
  • source 语句末尾的分号。 Painless 不要求块的最后一个语句使用分号。 但是,在其他情况下确实需要它们以消除歧义。

在 Elasticsearch 支持脚本的任何地方,都可以使用此缩写语法,例如在创建运行时字段 时。

您可以使用 存储的脚本 API 从集群状态存储和检索脚本。 存储的脚本允许您引用共享脚本以进行评分、聚合、过滤和重新索引等操作。 您可以引用这些共享操作,而不是在每个查询中嵌入内联脚本。

存储的脚本还可以减小请求有效负载的大小。 根据脚本大小和请求频率,这可以帮助降低延迟和数据传输成本。

注意

与常规脚本不同,存储的脚本要求您使用 lang 参数指定脚本语言。

要创建脚本,请使用创建存储的脚本 API。 例如,以下请求创建一个名为 calculate-score 的存储脚本。

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

您可以使用获取存储的脚本 API检索该脚本。

 GET _scripts/calculate-score 

要在查询中使用存储的脚本,请在 script 声明中包括脚本 id

 GET my-index-000001/_search {
  "query": {
    "script_score": {
      "query": {
        "match": {
            "message": "some message"
        }
      },
      "script": {
        "id": "calculate-score",
        "params": {
          "my_modifier": 2
        }
      }
    }
  }
}
  1. 存储的脚本的 id

要删除存储的脚本,请提交一个删除存储的脚本 API 请求。

 DELETE _scripts/calculate-score 

您可以使用 更新 API 使用指定的脚本更新文档。 该脚本可以更新、删除或跳过修改文档。 更新 API 还支持传递部分文档,该文档将合并到现有文档中。

首先,让我们索引一个简单的文档

 PUT my-index-000001/_doc/1 {
  "counter" : 1,
  "tags" : ["red"]
}

要递增计数器,您可以提交一个带有以下脚本的更新请求

 POST my-index-000001/_update/1 {
  "script" : {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params" : {
      "count" : 4
    }
  }
}

同样,您可以使用更新脚本将标签添加到标签列表中。 因为这只是一个列表,所以即使标签存在也会添加该标签

 POST my-index-000001/_update/1 {
  "script": {
    "source": "ctx._source.tags.add(params['tag'])",
    "lang": "painless",
    "params": {
      "tag": "blue"
    }
  }
}

您还可以从标签列表中删除标签。 Java Listremove 方法在 Painless 中可用。 它采用您要删除的元素的索引。 为避免可能的运行时错误,您首先需要确保标签存在。 如果列表包含标签的重复项,则此脚本仅删除一个。

 POST my-index-000001/_update/1 {
  "script": {
    "source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
    "lang": "painless",
    "params": {
      "tag": "blue"
    }
  }
}

您还可以从文档中添加和删除字段。 例如,此脚本添加字段 new_field

 POST my-index-000001/_update/1 {
  "script" : "ctx._source.new_field = 'value_of_new_field'"
}

相反,此脚本删除字段 new_field

 POST my-index-000001/_update/1 {
  "script" : "ctx._source.remove('new_field')"
}

除了更新文档之外,您还可以更改脚本中执行的操作。 例如,如果 tags 字段包含 green,则此请求将删除文档。 否则,它什么也不做 (noop)

 POST my-index-000001/_update/1 {
  "script": {
    "source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
    "lang": "painless",
    "params": {
      "tag": "green"
    }
  }
}
© . All rights reserved.