Loading... # Elasticsearch(六) ## ES Scripts ES在1.4.x-5.0版本用的是Groovy,在5.0版本以后,放弃Groovy改用Painless。 Painless是ES默认的脚本语言,除此之外,ES还支持其他几种语言: - Expression: 每个文档的开销较低,表达式的作用更多,可以非常快速地执行,甚至比编写native脚本还要快,支持JavaScript语法的子集,单个表达式。缺点:只能访问数字,布尔值,日期和geo point字段,存储的字段不可用。 - Mustache: 提供模板参数化查询 - java ### Painless Painless是一种专门用于Elasticsearch的简单、用于内联和存储脚本,类似于java,也有注释、关键字、类型、变量、函数等。它是Elasticsearch默认的脚本语言,是一个安全的脚本语言。 #### 更新 ``` POST product2/_update/4 { "script": { "source": "ctx._source.price-=1" } } ``` script中没有其它参数,也可以简化为: ``` POST product2/_update/4 { "script": "ctx._source.price-=1" } ``` #### 查询 ``` GET index/_search { "script_fields": { "test_field": { "script": { "lang": "painless", "source": "doc['price'].value } } } } ``` lang表示要用的脚本语言,默认是painless。 #### 支持操作 1、delete: 删除操作 ``` POST product2/_update/3 { "script": { "lang": "painless", "source": "ctx.op='delete'" } } ``` 2、upsert: 为update与insert的结合,有就更新,没有就插入 ``` POST product2/_update/3 { "script": { "lang": "painless", "source": "ctx._source.tags.add(params.tag_name)", "params": { "tag_name": "无线充电" } }, "upsert": { "name": "小米10", "price": 19999 } } ``` 3、noop: 表示什么都不做。 #### 参数化查询 ``` GET product2/_search { "script_fields": { "test_field": { "script": { "lang": "painless", "source": "doc['price'].value*params.discount", "params": { "discount": 0.9 } } } } } ``` ``` GET product2/_search { "script_fields": { "price": { "script": { "lang": "painless", "source": "doc['price']" } }, "test_field": { "script": { "lang": "painless", "source": "[doc['price'].value*params.discount_9,doc['price'].value*params.discount_8,doc['price'].value*params.discount_7]", "params": { "discount_9": 0.9, "discount_8": 0.8, "discount_7": 0.7 } } } } } ``` 参数化查询,可以将变量提取出来,这样每次修改变量就可以不用去脚本中修改了。相比较于普通方式,更大的好处是,ES第一次执行某个脚本时,会先进行编译,然后缓存到内存中,用以提升效率。脚本修改之后,就会判定为第一次执行,进行重新编译,使用参数化脚本,不需要改变脚本,因此不用重新编译,效率更高,而且ES为存储脚本的内存容量有限,并且编译行为每分钟只执行15次。 #### Stored Scripts 可以理解为script的模板,缓存在集群的cache中,如果一个脚本在很多地方重复使用,或者有很多人都需要使用该脚本,那么就可以使用Stored Scripts保存一份模板。 语法: ``` /_scripts/{id} ``` 这里的id是为Stored设置的一个唯一标识,用以在使用的时候可以获取到。 创建Stored: ``` POST _scripts/calculate-discount { "script": { "lang": "painless", "source": "doc['price'].value*params.discount" } } ``` 查看Stored: ``` GET _scripts/calculate-discount ``` 使用Stored: ``` GET product2/_search { "script_fields": { "test_field": { "script": { "id": "calculate-discount", "params": { "discount": 0.5 } } } } } ``` Stored的作用域为整个集群。默认缓存大小是100MB,默认没有过期时间,可以手工设置过期时间:`script.cache.expire`,通过`script.cache.max.size`可以设置缓存大小,脚本最大64MB,可以通过`script.max_size_in_bytes`设置,只有发生变更时才会重新编译。 #### Dates Date使用的是ZonedDateTime类型,因此它们支持诸如getYear、getDayOfWeek之类的方法或例如从公历元年开始的毫秒数getMills。要在脚本中使用它们,需要省略get前缀,并将剩余部分的首字母小写。 ``` GET product2/_search { "script_fields": { "test_year": { "script": { "source": "[doc.createtime.value.year,doc.createtime.value.dayOfMonth]" } } } } ``` #### 代码段 可以使用`"""`来包裹脚本代码,来实现类似在IDE中编写代码的效果: ``` POST product2/_update/1 { "script": { "lang": "painless", "source": """ ctx._source.name += params.name; ctx._source.price += params.discount; """, "params": { "name": "test", "discount": 0.5 } } } ``` #### 正则表达式 ES Script也支持正则表达式 ``` POST product2/_update/1 { "script": { "lang": "painless", "source": """ def name = ctx._source.name; if(name=~/^[\s\S]*phone[\s\S]*$/){ ctx._source.name += "***matched"; }else { ctx.op="noop"; } """ } } ``` 正则表达式默认情况下处于禁用状态,因为它们绕过了Painless的针对长时间运行和占用内存的脚本保护措施,而且有深度堆栈行为。可以在elasticsearch.yml中设置`script.painless.regex.enabled: true`开启。 #### params\['_source'\]\['field_name'\] doc['field_name']和params\['_source'\]\['field_name'\]的区别。首先,使用doc关键字,该字段的条件会被加载到内存(缓存),执行效率更高,但消耗更多的内存。此外,doc\['field_name'\]只允许简单类型,不支持复杂类型(object或者nested类型),只有在非分析或单个词条的基础上有意义。但是,如果可以使用doc尽量使用doc,因为params每次使用时都必须加载并解析,速度非常慢。 ``` GET test_index/_search { "aggs": { "nanxing": { "sum": { "script": { "source": """ def total = 0; for(a in params['_source']['jsbax_sjjh2_xz_ryjbxx_cleaning']){ if(a.SF=="男"){ total++; } } return total; """ } } } }, "size": 0 } ``` ## ES写入原理 ![ES写入.jpg][1] 当有写入请求过来时,会将数据写入内存buffer中,同时写入translog.当内存buffer满了,或者到一定时间就会refresh到一个新的segment file中,同时将segment file放入OS Cache中,这个过程称为refresh,默认每一秒钟执行一次,这时数据还处于不可查询的状态,OS Cache收到文件后,会将文件标记为open状态,这时数据就可以搜索到了,所以说ES是近实时(NTR)的,因为插入数据之后,不是立马就能搜索到的,需要等待1秒,才可以搜索到(refresh默认1秒一次)。在生成segment file的同时也会生成del file记录删除状态,实际上删除与修改的doc并不立即从文件中移除,del file中记录的就是这些状态,当查询一条记录的时候,有可能会把废弃的数据也查出来,然后ES会根据del file中的记录进行过滤操作。refresh行为会把内存buffer中的数据写入segment中,但是此时的segment是写在OS Cache中的,如果出现断电等异常,数据就丢失了。所以,ES在接收到写入请求时,也会将请求写入translog文件,这个文件是持久化在磁盘上的,发生异常,也可以从该文件中恢复数据。每次refresh之后,内存buffer都会清空,translog保留,随着过程推进,translog文件会越来越大,当translog达到一定长度后,会触发commit操作。 commit操作: 1. 将内存buffer现有数据refresh到OS Cache中,清空buffer。 2. 将一个commit point文件写入磁盘,标记了所有可用的segment。 3. 将OS Cache中现有数据全部fsync到磁盘中。 4. 清空translog文件,重启一个新的translog。 这个commit操作叫做flush,就算translog文件长度没到到,默认也会30分钟执行一次,flush操作对应着commit的全过程,可以通过`POST /index/_flush`手动执行flush操作,但不推荐这么用,谨慎使用。 每一次refresh操作都会生成一个新的segment file,1秒钟1个,随着过程推进,文件就会越来越多,速度会越来越慢,因此,ES会定期执行segment合并操作,称为segment merge。 merge过程: 1. 选择相似segment进行合并 2. 执行flush操作 3. 创建新的commit point,标记新的segment,删除旧的标记(此时,旧的文件还未删除) 4. 将新的segment搜索状态打开 5. 删除旧的segment文件 可以执行`POST /index/_optimize?max_num_segments=10`来强制合并成指定数量。谨慎使用,通常ES的segment越少,效率越高,但是optimize不适合用在频繁更新的索引上,对于频繁更新的索引,ES默认的就是最优策略。一般适用于静态索引,也就是没有写入操作,只有查询操作的索引,这些索引是比较适合使用optimize进行优化的。 修改refresh默认间隔时间: ``` PUT /index { "settings": { "refresh_interval": "10s" } } ``` [1]: https://www.princelei.club/usr/uploads/2020/07/3822348076.jpg Last modification:July 21st, 2020 at 12:11 am © 允许规范转载