本文共 2551 字,大约阅读时间需要 8 分钟。
关键词: 桶数限制, 分区partition, cardinality, script
最近生产上的定时任务发生了OOM异常.很遗憾的是,st环境无法重现,生产环境也由于权限的问题无法定位,所以找不到问题的原因.
不过从程序来看,代码还是有很大的优化空间.
目前业务现状(模拟的业务场景):
1,表中有约140万条不同的会员卡(卡信息表)数据,对应有大约3200万条消费记录存在es中.
2.需要对半个月/一个月都未消费的会员卡发送短信提醒,如果卡有异常(另一表记录),则不发送
3.同一张会员卡可以给多个人使用(会员与卡映射表)
现有程序设计:
1.分批遍历卡信息,剔除有异常的会员卡
2.用每张卡去ES查询最后一条消费记录的时间,然后判断是否半个月/一个月都未消费
3.符合条件的卡发短信提醒卡的所有成员
缺点:
1.需要全部卡都进行遍历,并到ES进行聚合查询,再做判断,占内存多,计算量大,耗时
2.查卡过程的SQL使用了not exist,查询时间长
3.很多会员卡不符合条件,做了不必要的筛选,耗时且浪费巨大
因为无法找到内存溢出的原因,只有通过其他方法进行代码优化.
=>改进办法:
1.设计思路: 原先是 会员卡->es查询最后一笔消费时间->符合条件的数据->发短信 (正向)
改为:es分别获取最后一笔交易是半个月前,一个月前的会员卡->数据库查会员卡信息->发短信 (反向)
2.发短信步骤改为异步
为什么原先的设计没有按这种思路来呢? 是因为当时一直找到符合要求的es脚本去筛选数据.所以只能遍历会员卡,而不是相反先从es筛选开始. 光是这一步就有巨大的区别.
参考资料:
在es使用聚合查询时,由于,因此需要对查询进行分区(即分批次循环查询)
先获取es总记录数
private int getRecordTotalNum(RestHihLevelclient client) { SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); CardinalityAgeregationBuilder countAee = AggregationBuilders.cardinality(CARD_CNT).field(CARD_ID); sourceBuilder.agregation(countAgg.size(0); SearchRequest request = new SearchRequest(INDEX_NAME).types(TYPE_NAME).source (sourceBuilder); long total = 0; try { SearchResponse search = client.search(request); Cardinality cntAgg = sesearch.getAggregations().get(CARD_CNT); total = cntAgg.getValue(); catch (Exception e) { log.error("查询ES异常", e); } return total;}
至于每个批次数据量多少,要根据实际情况而定,如综合查询所需时间,cup消耗等.
在分区的聚合查询中,要对聚合结果进行having过滤(筛选消费时间为半个月前).
而此时又发现新的问题:查询结果会比实际的时间少8小时--原来是时区原因导致的.
所以格式化要设置时区, es脚本大致如下:
{ "size": 0, "aggregations": { "by_card_id": { "terms": { "field": "card_id", "size": 1000000, "min_doc_count": 1, . . . "include": { "partition": 0, "num_partitions": 12 } }, "aggregations": { "by_trx_tim": { "max": { "field": "trx_tim" } }, "having": { "buckket_selector": { "buckets_path": { "trxTim": "by_trx_tim" }, "script": { "source": "SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd\"); sdf.setTimeZone(TimeZone.getTimeZone(\"GMT0\")); String tt = sdf.format(params.trxTim); return tt.compareTo(\"2020-08-26\") == 0;", "lang": "painless" }, "gap_policy": "skip" } } } } }}
由此获取会员卡卡号,就是满足半个月无消费的条件的数据了. 再做一次满足一个月的查询,就得到全部要发短信的会员卡.
此时再单独查出消费异常的会员卡,剔除掉即可,不用和以前那样做not exist耗时的操作了.
剩下的就是组装信息发短信了.
总结:
原先对ES了解很浅,并不知道ES有类似having的查询,也不知道可以插入自定义查询脚本. 当时也试图找过相关的资料,可能是查资料不得法,没有找到这块内容.所以问题驱动开发,能不断扩充知识.
有疑问或不同的观点,欢迎提出,一起探讨.
转载地址:http://rhbws.baihongyu.com/