最近星球群里讨论了脚本的使用。
当看到复杂脚本的时候,我的第一反应是:
那么问题来了。
带着这些疑问,本文展开讲解。
版本 | 使用脚本类型 |
---|---|
< 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 是一种简单,安全的脚本语言,专为与 Elasticsearch 一起使用而设计。它是 Elasticsearch 的默认脚本语言,可以安全地用于内联(inline)和存储(stored)脚本。
关于 inline 和 stored 的区别,后面会讲解。
Painless特点:
从 Elastic 中文社区、各个微信群、QQ群的技术交流可见一斑,几乎隔几天就会有“脚本语法如何使用?”的问题抛出来。
Elasticsearch 脚本会给集群带来沉重的负担,编写脚本往往仅考虑功能实现层面,而极大可能会忽略或者忘记考虑到它可能需要的资源。
直接上替换方案——空间换时间,在写入前将相关数据尽可能使用 Ingest 管道完成“ETL”抽取、转换、加载“清洗”工作。
这时候读者不免有读者会问:“上来就提方案,你的方案依据是什么?空间换时间仅是你的一家之言,你有什么资格提方案?”
其实这是企业内部讨论方案经常被问到的问题,实际说辞可能会比这要委婉一些。
首先:实践和咨询经验的总结。
其次,官方文档有详细阐述,可以参考如下,为了更精准说明,我保留了英文原文。
英文释义解读如下:
三条解释,清晰明了。
https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/scripts-and-search-speed.html
这里要分为几种情况:
第一:java 或者 python 客户端直连 Elasticsearch。
借助 Ingest pipeline 可以实现写入数据的预处理。
第二:Beats 直连 Elasticsearch。
借助 Ingest pipeline 可以实现写入数据的预处理。
Beats 端输出到 Elasticsearch 配置 pipeline 参考:
output.elasticsearch:
hosts: ["localhost:9200"]
pipeline: my-pipeline
第三:logstash 直连 Elasticsearch。
借助 Ingest pipeline 或者 logstash filter 可以实现数据预处理。
那,到底什么是空间换时间呢?
举个栗子进一步解释一把:
以最易理解的学生成绩表为例,要统计每个学生的成绩总和,并要求按照成绩之和排序。
DELETE my_test_scores
# 创建索引并指定 Mapping
PUT my_test_scores
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"english_score": {
"type": "integer"
},
"math_score": {
"type": "integer"
}
}
}
}
# 写入数据
POST my_test_scores/_bulk
{"index":{"_id":1}}
{"name":"xiaoming","english_score":95,"math_score":85}
{"index":{"_id":2}}
{"name":"xiaowang","english_score":75,"math_score":95}
{"index":{"_id":3}}
{"name":"xiaozhang","english_score":94,"math_score":81}
# 按照两门课程成绩之和召回排序结果
GET /my_test_scores/_search
{
"sort": [
{
"_script": {
"type": "number",
"script": {
"source": "doc['math_score'].value + doc['english_score'].value"
},
"order": "desc"
}
}
]
}
# 不用脚本如何搞?
# 更新 Mapping
PUT /my_test_scores/_mapping
{
"properties": {
"total_score": {
"type": "long"
}
}
}
# 写入之前管道预处理:两门课程之和字段生成
PUT _ingest/pipeline/sum_score_pipeline
{
"description": "Calculates the total test score",
"processors": [
{
"script": {
"source": "ctx.total_score = (ctx.math_score + ctx.english_score)"
}
}
]
}
# 数据迁移
POST _reindex
{
"source": {
"index": "my_test_scores"
},
"dest": {
"index": "my_test_scores_ext",
"pipeline": "sum_score_pipeline"
}
}
GET my_test_scores_ext/_search
# 重新获取排序结果数据
GET /my_test_scores_ext/_search
{
"sort": [
{
"total_score": {
"order": "desc"
}
}
]
}
如上的示例前半部分使用脚本处理实现。
后半部分使用预处理管道实现,sum_score_pipeline 预处理管道实现了成绩字段的求和操作。
本质上是:新增了total_score 字段,且写入前加了管道的处理。写入会慢一些,但检索的时候就不涉及任何脚本处理,所以是增加空间换来了检索时间的缩短,提升了检索效率。
既然前面提到了脚本的使用弊端和“空间换时间”前置预处理的替换解决方案。
那么问题来了,作为集群管理人员或者研发团队的Boss,能否做到整个集群禁用脚本呢?
这个脑洞可以!实际也是可以实现的,操作层面实现参考如下内容。
安全无小事,“小心驶得万年船”。
使用 XPack 的免费的精简安全(配置登录账号和密码)、基础安全(SSL层面)功能。
即便可以实现,但安全风险把控角度,强烈不建议。
尽可能保持 Elasticsearch 的隔离,最好是在防火墙和 VPN 之后使用 Elasticsearch。
关于“裸奔”的危害,看这里:你的Elasticsearch在裸奔吗?
Kibana 就可能快捷上手配置,包含但不限于:Space(空间)、Role(角色)、Privelege(访问权限)。
字段级别的权限是收费功能,其他都是免费的。
这个也是我的知识盲点,我也是近期才关注到的。
Elasticsearch 支持两种脚本类型:内联(inline)和存储(stored)。
默认情况下,Elasticsearch 配置为运行这两种类型的脚本。
相信读者读到这里会和我一样一脸懵逼,啥叫 inline?啥叫 stored?inline 和 stored 有什么区别?
所谓:stored 存储类型的脚本。就是先定义好脚本,“存储起来”,后面可以用,当然也可以不用。
用的时候指定 id 来取就可以。
如下示例延续上面的例子。
定义脚本,实现成绩求和。
POST _scripts/sum_score_script
{
"script": {
"lang": "painless",
"source": "ctx._source.total_score=ctx._source.math_score + ctx._source.english_score"
}
}
批量全量更新,更新时使用刚才定义的脚本 id(这里本质就是 stored 类型脚本)。
POST my_test_scores/_update_by_query
{
"script": {
"id": "sum_score_script"
},
"query": {
"match_all": {}
}
}
检索,验证脚本是否生效。
GET my_test_scores/_search
批量全量更新,更新时使用刚才定义的脚本 id。
POST my_test_scores/_update_by_query
{
"script": {
"lang": "painless",
"source": "ctx._source.total_score=ctx._source.math_score + ctx._source.english_score"
},
"query": {
"match_all": {}
}
}
检索,验证脚本是否生效。
GET my_test_scores/_search
对比 stored 类型,inline 脚本就是使用的时候直接指定脚本,不存在提前创建脚本的说法。
知道了两者的区别,如何做限制呢?
如下使用的配置:script.allowed_types 是 集群配置层面 elasticsearch.yml 的配置,不支持动态更新配置。
等价于:
script.allowed_types: both
如果你想保留现状,脚本不做任何使用限制,那就无需更改任何配置。
要限制运行的脚本类型,请将 script.allowed_types 设置为内联(inline)或存储(stored)。
script.allowed_types: inline
如果仅设置支持:inline,再创建 stored 类型脚本就会报错。
如果仅设置支持:stored,集群启动就会报错。
如果您使用 Kibana,请将 script.allowed_types 设置为 both 或 inline。
因为:某些 Kibana 功能依赖于内联脚本,如果 Elasticsearch 不允许内联脚本,则无法按预期运行。
要阻止任何脚本运行,请将 script.allowed_types 设置为 none。
script.allowed_types: none
范围有哪些?举例:
默认情况下,使用范围是没有任何限制的。
如果全部范围都不允许使用,可以配置如下:
script.allowed_contexts: none
如果仅允许部分使用,比如只允许评分、更新使用脚本,可以配置如下:
script.allowed_contexts: score, update
如上几条,就实现了脚本的受控的使用,实战环节结合当前业务需求和未来扩展业务需求,谨慎选型。
对于脚本来说,要辩证的看待。存在即为合理,有应用场景就会有新的 feature。
如果没有准实时的要求的业务场景,多半都会接受延时写入,但对检索响应慢会“深恶痛绝”。
遇到类似问题的时候,多在建模、设计阶段花时间。建议不要把问题都抛到检索的时候实现,一方面:脚本实现起来的确有性能问题;另一方面:脚本处理的方式已然不是 Elasticsearch 最擅长的事。
可以选择 logstash filter 环节做数据的预处理 或者借助 pipeline 实现写入数据的预处理。
空间换时间,推荐使用 Ingest 管道预处理的方式在写入前尽可能的对字段实时预处理。
选型阶段多考虑选型 Elasticsearch 终极目的和初衷,使用过程中发挥 Elasticsearch 优势,且要确保优势最大化。
PS:细心的 Elastic 爱好者会发现 Elasticsearch 的官方文档在往条理更加清晰、模块更加分明的方向努力,这样我们的学习有了更新的、更大的动力!
https://opster.com/guides/elasticsearch/best-practices/elasticsearch-all-script-types-allowed/
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-security.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/es-security-principles.html
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/w397090770/article/details/121413530
内容来源于网络,如有侵权,请联系作者删除!