Elasticsearch使用小结
前言
ES大家可能都不陌生,它是一个高效的搜索引擎,可以帮助我们快速存储、搜索大量数据。
我工作中负责的模块之一,是一个核心存储服务,我们业务的数据通过这个服务存储到MySQL中,并向内部系统提供查询能力。我们主要数据被拆分成了两张表,因此面对各种各样的查询需求,如果只依赖MySQL本身的查询能力查询,会遇到几个问题:
1.如果需要查询的字段或者查询条件分别在两张表中,那么就需要联表查询。我们一般是不推荐join查询的。因为join的效率较低,如果数据量大的话,查询速度很难保证。
2.如果为了加快查询速度,最好对需要查询的字段建索引。那么作为底层存储服务,面对的查询需求是各种各样的,极端点的可能就是每个字段都可能需要被查询,如果对每个字段建立索引,会增加索引变更的维护成本,并且更多的索引需要占用的空间也更多,因此一般是不推荐表中索引列太多的。即时是只对高频查询字段建立索引,索引列的量仍然不少。
因此我们团队的解决方案是在ES中维护一份与数据库中一致的数据,存储的内容就是各种查询需求需要的字段和我们的唯一业务ID,即订单号。查询时先在ES中查出所有符合条件的订单号,再根据订单号从数据库的两张表中分别查出需要的数据返回。
实例
下面总结一些工作中遇到的比较棘手的查询需求
例1
有两个时间字段,order_datetime、order_create_time,以yyyy-MM-dd HH:mm:ss格式存储,需要查出所有符合order_datetime比order_create_time大30min的订单
解决方案:用脚本查询
Java实现如下:
BoolQueryBuilder queryRequest = new BoolQueryBuilder();
String scriptStr = "(doc['order_datetime'].value.getMillis() - doc['order_create_time'].value.getMillis())/(3600000.0/60) > 30"
Map<String, Object> params = new HashMap<>();
Script script = new Script(ScriptType.INLINE, "painless", scriptStr, params);
ScriptQueryBuilder scriptQueryBuilder = new ScriptQueryBuilder(script);
queryRequest.must(scriptQueryBuilder);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(queryRequest);
searchSourceBuilder.fetchSource(false);
SearchRequest searchRequest = new SearchRequest();
searchRequest.source(searchSourceBuilder);
searchRequest.indices(Constants.ES_ORDER_INDEX);
searchResponse = restHighLevelClient.search(searchRequest);
DSL:
{
"from": 0,
"size": 10,
"query": {
"bool": {
"must": {
"script": {
"script": {
"source": "(doc['order_datetime'].value.getMillis() - doc['create_time'].value.getMillis())/(3600000.0/60) >= 30",
"lang": "painless"
},
"boost": 1.0
}
}
}
},
"_source": false
}
例2
某个字段price_plan有几种可能的值:1,2,3,4或null,为null的含义与为1相同。查询时的入参为多个值,如[2,3]表示查询出所有price_plan = 2或price_plan=3的数据,[1,4]表示查出所有price_plan = 1或price_plan=4或price_plan为空的数据。null的含义与为1相同是带来麻烦的核心点。
Java:
BoolQueryBuilder queryRequest = new BoolQueryBuilder();
if (!CollectionUtils.isEmpty(request.getPricePlan())) {
BoolQueryBuilder queryShould = new BoolQueryBuilder();
queryShould.should(new TermsQueryBuilder("price_plan", request.getPricePlan()));
// 如果查询1,则应该把null也查出来
if (request.getPricePlan().contains(1)){
BoolQueryBuilder queryExist = new BoolQueryBuilder();
ExistsQueryBuilder existsQueryBuilder = QueryBuilders.existsQuery("price_plan");
queryExist.mustNot(existsQueryBuilder);
queryShould.should(queryExist);
}
queryRequest.must(queryShould);
}
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(queryRequest);
searchSourceBuilder.fetchSource(false);
SearchRequest searchRequest = new SearchRequest();
searchRequest.source(searchSourceBuilder);
searchRequest.indices(Constants.ES_ORDER_INDEX);
searchResponse = restHighLevelClient.search(searchRequest);
DSL:
{
"from": 0,
"size": 10,
"query": {
"bool": {
"must": [{
"terms": {
"order_status": [0],
"boost": 1.0
}
}, {
"bool": {
"should": [{
"terms": {
"price_plan": [1, 3, 2],
"boost": 1.0
}
}, {
"bool": {
"must_not": [{
"exists": {
"field": "price_plan",
"boost": 1.0
}
}],
"adjust_pure_negative": true,
"boost": 1.0
}
}]
}
}]
}
},
"_source": false
}