Elasticsearch的脚本模块可以使用脚本对Elasticsearch的字段进行再次处理。例如,可以用来重新评估查询的自定义得分,可以对索引中的某个字段再次加工处理。
脚本默认是关闭的,如果这个时候执行脚本会报以下错误:
{
"error": {
"root_cause": [
{
"type": "remote_transport_exception",
"reason": "[Left-Winger][127.0.0.1:9300][indices:data/write/update[s]]"
}],
"type": "illegal_argument_exception",
"reason": "failed to execute script",
"caused_by": {
"type": "script_exception",
"reason": "scripts of type [indexed], operation [update] and lang [groovy] are disabled"
}
},
"status": 400
}
可以通过配置来启用脚本引擎,配置的位置在elasticsearch.yml文件中添加如下内容:
script.inline: true
script.indexed: true
对这些设置,有三种配置值:
值 |
描述 |
false |
完全禁用脚本 |
true |
启用脚本 |
sandbox |
脚本仅可以用沙盒语言执行 |
表格 8.7 细粒度动态脚本设置
默认配置值为:
script.inline: sandbox
script.indexed: sandbox
script.file: true
也可以在已下操作中执行脚本:
值 |
描述 |
aggs |
聚合 |
search |
搜索接口、过滤接口或建议接口(例如filters、script_fields)。 |
update |
升级接口 |
plugin |
通用plugin类别下,脚本使用的任何插件 |
插件也可以进行自定义操作,利用这种格式:${pluginName}_${operation}。例如在任何脚本引擎上禁用更新和映射:
script.update: false
script.mapping: false
也支持明确的脚本语言设置,需要script.engine.<engine>前缀,优先权高于其他通用设置:
script.engine.groovy.file.aggs: true
script.engine.groovy.file.mapping: true
script.engine.groovy.file.search: true
script.engine.groovy.file.update: true
script.engine.groovy.file.plugin: true
script.engine.groovy.indexed.aggs: true
script.engine.groovy.indexed.mapping: false
script.engine.groovy.indexed.search: true
script.engine.groovy.indexed.update: false
script.engine.groovy.indexed.plugin: false
script.engine.groovy.inline.aggs: true
script.engine.groovy.inline.mapping: false
script.engine.groovy.inline.search: false
script.engine.groovy.inline.update: false
script.engine.groovy.inline.plugin: false
系统的脚本模块默认使用groovy作为的脚本语言。可以通过设置script.default_lang可以进行修改。
可以用语言插件来支持不同的语言脚本。所以在使用脚本接口的参数中提供了lang参数定义脚本语言。
语言 |
沙盒 |
必要插件 |
groovy |
否 |
内置 |
expression |
是 |
内置 |
mustache |
是 |
内置 |
javascript |
否 |
elasticsearch-lang-javascript |
python |
否 |
elasticsearch-lang-python |
为了增加安全性,Elasticsearch不允许在请求中指定非沙盒语言。默认的脚本目录位置可以在elasticsearch.yml中设置path.scripts进行修改。放置在这个目录中的脚本会自动选中并且可以被使用。一旦脚本被放在这个目录中,可以通过脚本名进行引用。
1.1.1 脚本使用
在Elasticsearch中使用脚本有三种方式:
1、直接在请求体中使用脚本。
2、把脚本存储在索引中,通过引用脚本id来使用。
3、把脚本存储在本地磁盘中,默认的位置为:elasticsearch\config\scripts,通过引用脚本名称进行使用。
下面通过举例来说明使用脚本的三种方法。
首先建一个索引,添加一条数据。
PUT localhost:9200/secisland/secilog/1
{
"eventCount":1,
"eventName" :"linux login event"
}
下面我们用脚本对eventCount做加法操作。
n 用第一种方法直接在请求中执行脚本:
http://127.0.0.1:9200/secisland/secilog/1/_update
{
"script" : "ctx._source.eventCount+=count",
"params" : {
"count" : 4
}
}
n 下面我们用第二种方法操作,在用第二种方法前,先要把脚本存储在Elasticsearch中:
POST 127.0.0.1:9200/_scripts/groovy/indexedCalculateCount
{
"script": "ctx._source.eventCount+=count"
}
然后通过脚本Id进行文档操作:
POST http://127.0.0.1:9200/secisland/secilog/1/_update
{
"script": {
"id": "indexedCalculateCount",
"lang": "groovy",
"params": {
"count": 8
}
}
}
n 下面我们用第三种方法操作,在第三种方法操作前,先要把脚本存储在文件中,文件名为indexedCalculateCount.groovy,文件中的内容为:ctx._source.eventCount+=count。
注意:Elasticsearch对文件读取有个时间,刚建好后,不能生效,做验证的时候可以重启进行生效。
POST http://127.0.0.1:9200/secisland/secilog/1/_update/
{
"script": {
"file": "indexedCalculateCount",
"lang": "groovy",
"params": {
"count": 8
}
}
}
1.1.2 脚本配置
A. 索引脚本
Elasticsearch可以在名为_scripts的内部索引中存储脚本,并且通过id进行引用。脚本请求的格式:/_scripts/{lang}/{id}
lang表示脚本语言,id表示脚本编号。
1. 保存脚本
POST localhost:9200/_scripts/groovy/indexedCalculateScore
{
"script": "log(_score * 2) + my_modifier"
}
2. 使用脚本
POST localhost:9200/_search
{
"query": {
"function_score": {
"query": {
"match": {
"body": "foo"
}
},
"functions": [
{
"script_score": {
"script": {
"id": "indexedCalculateScore",
"lang" : "groovy",
"params": {
"my_modifier": 8
}
}
}
}
]
}
}
}
3. 查看脚本
GET localhost:9200/_scripts/groovy/indexedCalculateScore
4. 删除脚本
DELETE localhost:9200/_scripts/groovy/indexedCalculateScore
B. 启用动态脚本
在应用或代理后面执行Elasticsearch,可以从外界保护Elasticsearch。如果允许用户运行内联脚本或索引脚本,会继承运行Elasticsearch的用户权限。因此动态脚本默认仅支持沙盒语言。
首先,应该使用root用户权限运行Elasticsearch,可以允许脚本在服务器上访问或做任何事情。其次,不应该让用户直接访问Elasticsearch,而是要有一个中间代理应用。如果确实想要用户直接访问Elasticsearch,需要决定是否足够信任用户来运行脚本。
C. 脚本自动重载
周期性扫描config/scripts目录的修改。新的和修改的脚本会被重载删除的脚本会从预加载脚本缓存中移除。重载频率可以使用resource.reload.interval设置指定,默认值为60s。设置script.auto_reload_enabled为false可以完全禁用脚本重载。
本文由赛克 蓝德(secisland)原创,转载请标明作者和出处。
D. 本地(Java)脚本
有时,groovy和expressions是不够的。在这种情况下,可以执行本地脚本。
执行本地脚本的最好方式是编写并且安装一个插件。
为了注册实际脚本,需要引用NativeScriptFactory来构成脚本。实际脚本需要继承AbstractExecutableScript或AbstractSearchScript。第二种可能是最有用的并且有多个可以继承的子类,比如AbstractLongSearchScript、AbstractDoubleSearchScript和AnstractFloatSearchScript。最后,插件需要通过onModule(ScriptModule)方法注册本地脚本。
public class MyNativeScriptPlugin extends Plugin {
@Override
public String name() {
return "my-native-script";
}
@Override
public String description() {
return "my native script that does something great";
}
public void onModule(ScriptModule scriptModule) {
scriptModule.registerScript("my_script", MyNativeScriptFactory.class);
}
public static class MyNativeScriptFactory implements NativeScriptFactory {
@Override
public ExecutableScript newScript(@Nullable Map<String, Object> params) {
return new MyNativeScript();
}
@Override
public boolean needsScores() {
return false;
}
}
public static class MyNativeScript extends AbstractFloatSearchScript {
@Override
public float runAsFloat() {
float a = (float) source().get("a");
float b = (float) source().get("b");
return a * b;
}
}
}
可以执行脚本通过指定lang为native,指定inline为脚本名。
POST localhost:9200/_search
{
"query": {
"function_score": {
"query": {
"match": {
"body": "foo"
}
},
"functions": [
{
"script_score": {
"script": {
"inline": "my_script",
"lang" : "native"
}
}
}
]
}
}
}
E. Lucene表达式脚本
警告:Lucene表达式模块正经历重大的发展,展现的功能可能会被修改。
Lucene的表达式模块提供了一个机制来将Javascript表达式编译成字节码。表达式脚本可以用于script_score、script_fields、排序脚本和数字型聚合脚本。
表达式脚本变量可以接受:
l 单值文档字段,例如doc[‘myfield’].value,也可以写作doc[‘myfield’]。
l 传给脚本的参数,例如mymodifier。
l 当前文档的得分,_score(只有用在script_score的时候)。
表达式脚本日期类型可以使用这些方法:
l getYear()
l getMonth()
l getDayOfMonth()
l getHourOfDay()
l getMinutes()
l getSeconds()
比如,获取日期字段间年份的不同:
doc[‘date1’].getYear() – doc[‘date0’].getYear()
相对于其他脚本语言,有一些限制:
l 只接受数字型字段
l 保存的字段不可用
l 如果只有一部分文档包含字段值,缺失字段值的文档默认值为0。
F. 得分
聚合中所有可以使用的脚本,当前文档的得分可以用_score获得。
G. 文档字段
大多数脚本围绕指定文档字段数据的使用。doc[‘field_name’]可以用来访问文档内指定字段数据。注意,只能是简单值字段(不能返回Json对象)并且只有不分词字段或单索引词字段是有意义的。
可以从字段中获取下列数据:
表达式 |
描述 |
doc[‘field_name’].value |
字段的本地值。 |
doc[‘field_name’].values |
字段的本地数组值。如果字段没有值,返回一个空数组。 |
doc[‘field_name’].empty |
布尔值,表示文档中的字段是否有值。 |
doc[‘field_name’].multiValued |
布尔值,表示文集中有多个值的字段。 |
doc[‘field_name’].lat |
地理点类型的纬度。 |
doc[‘field_name’].lon |
地理点类型的经度。 |
doc[‘field_name’].lats |
地理点类型的纬度(复数)。 |
doc[‘field_name’].lons |
地理点类型的经度(复数)。 |
doc[‘field_name’].distance(lat,lon) |
地理点字段和提供的经纬度之间的平面距离(米)。 |
doc[‘field_name’].distanceWithDefault(lat,lon,default) |
地理点字段从提供的经纬度和默认值的平面距离(米)。 |
doc[‘field_name’].distanceInMiles(lat,lon) |
地理点字段和提供的经纬度之间的平面距离(英里)。 |
doc[‘field_name’].distanceInMilesWithDefault(lat,lon,default) |
地理点字段从提供的经纬度和默认值的平面距离(英里)。 |
doc[‘field_name’].distanceInKm(lat,lon) |
地理点字段和提供的经纬度之间的平面距离(千米)。 |
doc[‘field_name’].distanceInkmWithDefault(lat,lon,default) |
地理点字段从提供的经纬度和默认值的平面距离(千米)。 |
doc[‘field_name’].arcDistance(lat,lon) |
地理点字段和提供的经纬度之间的天穹距离(米)。 |
doc[‘field_name’].arcDistanceWithDefault(lat,lon,default) |
地理点字段从提供的经纬度和默认值的天穹距离(米)。 |
doc[‘field_name’].arcDistanceInMiles(lat,lon) |
地理点字段和提供的经纬度之间的天穹距离(英里)。 |
doc[‘field_name’].arcDistanceInMilesWithDefault(lat,lon,default) |
地理点字段从提供的经纬度和默认值的天穹距离(英里)。 |
doc[‘field_name’].arcDistanceInKm(lat,lon) |
地理点字段和提供的经纬度之间的天穹距离(千米)。 |
doc[‘field_name’].arcDistanceInKmWithDefault(lat,lon,default) |
地理点字段从提供的经纬度和默认值的天穹距离(千米)。 |
doc[‘field_name’].factorDistance(lat,lon) |
地理点字段从提供的经纬度之间的距离因子。 |
doc[‘field_name’].factorDistance(lat,lon,default) |
地理点字段从提供的经纬度和默认值的距离因子。 |
doc[‘field_name’].geohashDistance(geohash) |
地理点字段从提供的地理散列的天穹距离(米)。 |
doc[‘field_name’].geohashDistanceInKm(geohash) |
地理点字段从提供的地理散列的天穹距离(千米)。 |
doc[‘field_name’].geohashDistanceInMiles(geohash) |
地理点字段从提供的地理散列的天穹距离(英里)。 |
表格 8.9 脚本提取数据
H. 保存的字段
执行脚本的时候,保存的字段也可以访问。通过使用_fields[‘my_field_name’].value或_fields[‘my_field_name’].values。
I. 在脚本中访问文档的得分
当使用脚本计算文档得分(例如,通过function_score查询),可以在groovy脚本中使用_score变量访问得分。
J. 源字段
当执行脚本的时候,可以访问源字段。每个文档都会加载源字段、分析并且提供给脚本进行评估。使用方式,形如_source.obj1.obj2.field3。
K. Groovy内置方法
方法 |
描述 |
sin(a) |
返回角度的三角正弦 |
cos(a) |
返回角度的三角余弦 |
tan(a) |
返回角度的三角正切 |
asin(a) |
返回反正弦值 |
acos(a) |
返回反余弦值 |
atan(a) |
返回反正切值 |
toRadians(angdeg) |
转换角度度量近似为弧度 |
toDegrees(angrad) |
转换弧度度量近似为角度 |
exp(a) |
返回欧拉数e的指数值 |
log(a) |
返回值的自然对数(基于e) |
log10(a) |
返回值基于10的对数 |
sqrt(a) |
返回值的平方根 |
cbrt(a) |
返回双精度值的立方根 |
IEEEremainder(f1,f2) |
进行余数运算 |
ceil(a) |
向下取整 |
floor(a) |
向上取整 |
rint(a) |
取整 |
atan2(y,x) |
转换直角坐标(x,y)到极坐标(r,θ),返回θ值 |
pow(a,b) |
返回ab |
round(a) |
取整 |
random() |
返回随机双精度数 |
abs(a) |
返回值的绝对值 |
max(a,b) |
返回大值 |
min(a,b) |
返回小值 |
ulp(d) |
返回参数的ulp的大小 |
signum(d) |
返回自变量的符号函数 |
sinh(x) |
返回值的双曲正弦值 |
cosh(x) |
返回值的双曲余弦值 |
tanh(x) |
返回值的双曲切线 |
hypot(x,y) |
返回sqrt(x2+y2) |
赛克蓝德(secisland)后续会逐步对Elasticsearch的最新版本的各项功能进行分析,近请期待。