0%

ElasticSearch使用painless脚本小记

ElasticSearch使用painless脚本小记

  • 前几天项目中碰到了一个麻烦的问题,统计Doc中一个List中符合筛选条件的项的值的和(简单说就是Doc是商店为主体,统计该商店的某个时间段下单记录),开始用的是二次查询,但是后来数据量大了以后,第二次查询用的IDs查询传了几千个值,超过了ES的限制,最后只好拿出脚本进行解决。
  • 先说一下Doc的结构,不相干的字段就删掉了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"shopid": 11,
"orderList": [
{
"orderTime":"2015-06-06",
"orderAmount":2254
},
{
"orderTime":"2016-06-06",
"orderAmount":7575.66
},
{
"orderTime":"2017-06-06",
"orderAmount":6533.99
},
{
"orderTime":"2018-06-06",
"orderAmount":7870.20
}
]
}
  • 业务的查询需求是筛选2016-2018年,下单金额累计超过13000的商店(当然还有其他附加条件的查询),orderList还是Nested字段,最开始是进行二次查询,也就是首先将其他条件查询出来,然后对结果进行过滤,在这个日期下的符合条件的取出来ID,进行二次查询(这个条件改成Ids查询),最开始还能正常工作,后来不加上这个条件一下查出来一千条数据的时候就麻烦了,es默认Bool查询条件不能超过1024,硬着头皮改了ES配置,调成了1W,总算是正常工作了,但是不是长久之计,随着订单越来越多,迟早还会有问题,而且查询的时间越来越长了(查出来7000多条数据的时候已经超过10s了。。),跟业务部门沟通了,他们暂时可以接受,但是让我们尽快优化。
  • 网上查找了很多资料,也想使用innerHits解决,但是innerHits结果无法参与aggs,实在没有其他办法,只好决定用脚本来解决。在上家公司用的是ES2.X,脚本groovy默认是关闭的,开启还有重启ES修改配置,觉得麻烦,而且据说有安全隐患,对这个一比较抵触,当下用的是ES5.6,看了一下脚本用的是painless,这个从来没有接触过,看了一下语法,好像也不太复杂,和Java差不太多,那就硬着头皮上。
  • painless什么时候开始支持的没太关注,不过看文档说好像已经是容器内运行,相对比较安全了,而且ES5.6默认已经开启了,尝试过程中发现Nested类型取值只能取到一个内部文档的,list其他对象找不到。冗余出来一个非nested字段,发现日期和金额都是分别排序的,这就很尴尬了。。。最后决定吧订单金额和下单时间拼接成字符串,存到一个数组中去,这样取值的时候,金额和日期就是一一对应的。
  • 修改后的Doc结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"shopid": 11,
"orderListStr":[
"2015-06-06@2254",
"2016-06-06@7575.66",
"2017-06-06@6533.99",
"2018-06-06@7870.20"
],
"orderList": [
{
"orderTime":"2015-06-06",
"orderAmount":2254
},
{
"orderTime":"2016-06-06",
"orderAmount":7575.66
},
{
"orderTime":"2017-06-06",
"orderAmount":6533.99
},
{
"orderTime":"2018-06-06",
"orderAmount":7870.20
}
]
}
  • 然后就是对照着文档写painless脚本了,个人水平有限,写的脚本也很低效,不过好赖问题是解决了,附上脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
String fotmat = 'yyyy-MM-dd';
String field = 'orderListStr';
double saleNum = 0.0;
for (int i = 0; i < doc[field].length; ++i) {
def docStr=doc[field][i];
def date = docStr.substring(0,docStr.indexOf('@'));
double value =Double.parseDouble(docStr.substring(docStr.indexOf('@')+1));
SimpleDateFormat sdf = new SimpleDateFormat(fotmat);
if (params.from != null && params.from != '' && params.to != null && params.to != '' ){
if (sdf.parse(params.from).getTime()<=sdf.parse(date).getTime() && sdf.parse(params.to).getTime()>=sdf.parse(date).getTime())
saleNum+=value;
}
else if (params.from != null && params.from != ''){
if( sdf.parse(params.from).getTime()<=sdf.parse(date).getTime())
saleNum+=value;
}
else if (params.to != null && params.to != ''){
if( sdf.parse(params.to).getTime()>=sdf.parse(date).getTime())
saleNum+=value;
}else {
saleNum+=value;
}
}
if (params.inputFrom != null && params.inputTo != null){
return params.inputFrom<=saleNum && saleNum<=params.inputTo;
} else if(params.inputFrom !=null){
return params.inputFrom<=saleNum;
} else if(params.inputTo != null){
return saleNum<=params.inputTo;
}else{
return true;
}
  • 使用脚本解决了时间段下单次数统计和时间段金额统计的查询,时间也从原来的接近10s下降到1s左右(感觉脚本没办法走索引,应该都是全表扫描),这个肯定不是最优方法,但是 现在也想不出来什么其他高效的方法,就先上这个了,最起码目前查询最近5年的订单也是1秒左右,而且不用担心出现bool数量超过限制的问题了。