1. 认识es脚本
结合官方文档对脚本使用的说明,一般来说应该避免使用脚本。一方面在于性能,使用脚本会导致性能低;另一方面是使用场景相对少,非复杂业务场景下,基础的增、删、改、查基本上就能满足需求。但不能否认的是,在解决复杂业务问题(如自定义评分、自定义文本相关度、自定义过滤、自定义聚合分析等)时,脚本依然是es强悍的利器之一。
如果特殊场景需要,则应该优先选择Painless脚本和expressions引擎。
举例来说,如果有一个包含大量剧院信息的索引,需要查询以Down开头的所有剧院,那么你可能会运行一个如下脚本进行查询。
PUT lwy_index/_bulk
{"index":{"_id":1}}
{"theatre":"Downtown", "popularity":10}
{"index":{"_id":2}}
{"theatre":"Downstyle", "popularity":13}
POST lwy_index/_search
{
"query": {
"bool": {
"filter": {
"script": {
"script": {
"lang": "painless",
"source": "doc['theatre.keyword'].value.startsWith('Down')"
}
}
}
}
}
}
但是这个查询非常耗费资源,会减慢整个系统的运行速度。对此,有如下两个解决方案。
- prefix前缀匹配。实测prefix较scripting性能提升5倍。
- 索引时考虑添加一个名为“theatre_prefix”的keyword类型字段,然后可询"theatre_prefix":"Down"。
Groovy脚本的出现是为了解决MVEL的安全隐患问题,但Groovy仍存在内存泄漏及安全漏洞问题。Painless脚本中文释义是“无痛”,Painless的出现让用户能够更方便、高效地使用脚本。
Painless是一种简单、安全的脚本语言,专为与es一起使用而设计。它是es的默认脚本语言,可以安全地用于内联和存储脚本。
Painless的特点如下。
- 性能较优:Painless脚本运行速度比备选方案(包括Groovy脚本)都要快几倍。
- 安全性强:使用白名单来限制函数与字段的访问,避免了可能的安全隐患。
- 可选输入:变量和参数可以使用显式类型或动态def类型。
- 上手容易:扩展了Java的基本语法,并兼容groove风格的脚本语言特性。
- 特定优化:是es官方专为es脚本编写而设计的一种语言。
2. 脚本的应用场景和模版
增删改查能解决业务场景80%的问题,而Painless脚本操作一般应用于相对复杂的业务场景,包括自定义字段、自定义评分、自定义更新、自定义reindex、自定义聚合,以及其他自定义操作。
"script": {
"lang": "...",
"source" | "id": "...",
"params": {...}
}
- lang:脚本语言,默认指定Painless。
- source:脚本的核心部分,其中id应用于stored script。
- params:传递给脚本使用的变量参数
3. 脚本实战
3.1 自定义字段
返回原有映射未定义的字段值。如下所示,通过my_doubled_field返回my_field字段的值翻倍后的结果。
PUT lwy_index/_bulk
{"index":{"_id":1}}
{"my_field":10}
POST lwy_index/_search
{
"script_fields": {
"my_doubled_field": {
"script": {
"lang": "expression",
"source": "doc['my_field'] * multiplier",
"params": {
"multiplier": 2
}
}
}
}
}
3.2 自定义评分
PUT lwy_index/_bulk
{"index":{"_id":1}}
{"theatre":"Downtown", "popularity":10}
{"index":{"_id":2}}
{"theatre":"Downstyle", "popularity":13}
POST lwy_index/_search
{
"query": {
"function_score": { # 用于对查询结果进行评分调整。它允许你根据各种因素来修改原始的查询得分
"script_score": { # 通过脚本计算新的得分
"script": {
"lang": "expression",
"source": "_score * doc['popularity']"
}
}
}
}
}
3.3 自定义更新
POST lwy_index/_update/1
{
"script": {
"lang": "painless",
"source": "ctx._source.theatre = params.theatre",
"params": {
"theatre": "jingju"
}
}
}
更改doc1的theatre字段。
3.4 自定义reindex
尝试通过reindex API将lwy_index1的文档重新索引到lwy_index1。
要求包括:增加一个整形字段,value是lwy_index1的field_x的字符长度,例如field_x为“abcd”,则长度为4;增加一个数组类型的字段,value是field_y的词集合,而field_y是空格分割的一组词,例如foo bar,要求索引到index_b后变成["foo","bar"])。
PUT lwy_index1
{
"mappings": {
"properties": {
"field_x":{
"type": "keyword"
},
"field_y":{
"type": "keyword"
}
}
}
}
POST lwy_index1/_bulk
{"index":{"_id":1}}
{"field_x":"abcd","field_y":"foo bar"}
PUT _ingest/pipeline/change_pipeline
{
"processors": [
{
"script": {
"source": """
ctx.field_x_len = ctx.field_x.length();
""",
"lang": "painless"
}
},
{
"split": {
"field": "field_y",
"separator": " "
}
}
]
}
POST lwy_index1/_update_by_query?pipeline=change_pipeline
POST lwy_index1/_search
这段代码共创建两个预处理管道
- script预处理管道:基于length函数实现求字段的长度。
- split预处理管道:基于空格切分字段,由字符串分隔为数组类型。
3.5 自定义聚合
POST lwy_index/_search
{
"aggs": {
"terms_aggs": {
"terms": {
"script": {
"source": "doc['popularity'].value",
"lang": "painless"
}
}
}
}
}
上述脚本的聚合实现方式仅说明基于脚本可以获取字段信息。而更为简洁的方式是直接基于字段“field”:“popularity”聚合实现这个目的。
3.6 实战常见问题
- 有哪些脚本类型
- painless的安全性注意事项
尽管Painless是es中安全性更高的脚本语言,但它仍然可能存在漏洞。为了确保系统保持安全,该脚本语言的正确使用和配置是非常重要的。此外,还应该采取其他安全措施,例如访问控制和数据加密来提高安全性。安全方面的核心注意点如下。
- 不要在root账户下运行es。
- 不要公开es路径给其他用户。
- 不要将es路径公开发布到互联网。