Elasticsearch multi search 探索

13 篇文章 1 订阅
1 篇文章 0 订阅

场景

3千多个文本需要搜索,如果循环调用接口查询,网络耗时就是一笔大开销,所以使用 multi search 把请求合并统一发给 elasticsearch 去查询,节省下3千多个网络开销。但是,发现 elasticsearch 执行耗时还是需要花费一秒多的耗时,因此尝试了几种途径去测试是否能优化 elasticsearch 执行批量查询的耗时。

优化前的伪代码

public void mSearch(String code, List<String> contents, int timeout) throws IOException {
    Assert.notEmpty(contents, "contents must not be empty!");
    long start = System.currentTimeMillis();
    String index = getIndex(code);
    MultiSearchRequest request = new MultiSearchRequest();
    for (String content : contents) {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery(Constants.FIELD_RULE, content));
        searchSourceBuilder.size(1);
        searchSourceBuilder.timeout(new TimeValue(timeout, TimeUnit.MILLISECONDS));
        searchRequest.source(searchSourceBuilder);
        request.add(searchRequest);
    }

    System.out.println("before search : " + (System.currentTimeMillis() - start));
    start = System.currentTimeMillis();

    MultiSearchResponse multiSearchResponse = client.msearch(request, RequestOptions.DEFAULT);

    System.out.println("search : " + (System.currentTimeMillis() - start));
    start = System.currentTimeMillis();

    MultiSearchResponse.Item[] responses = multiSearchResponse.getResponses();
    List<SearchResult> list = new ArrayList<>(contents.size());
    for (int i = 0; i < responses.length; i++) {
        MultiSearchResponse.Item response = responses[i];
        if (null != response.getFailure()) {
            String error = response.getFailure().getClass().getSimpleName() + ": " + response.getFailure().getMessage();
            log.error("mSearch|{}|{}|{}|{}", index, contents.get(i), error, System.currentTimeMillis() - start);
        } else {
            SearchHits hits = response.getResponse().getHits();
            if (hits.getTotalHits().value > 0) {
                SearchHit firstHit = hits.getHits()[0];
                // TODO 查询结果封装
            } else {
            	log.error("mSearch|{}|{}|查无结果|{}", index, contents.get(i), System.currentTimeMillis() - start);
            }
        }
    }

    System.out.println("after search : " + (System.currentTimeMillis() - start));
}

耗时统计

// elasticsearch 启动后前3次的耗时
before search : 141
search : 3784
after search : 240
    
before search : 16
search : 2082
after search : 239
    
before search : 23
search : 1913
after search : 326

    
// 连续执行10次后耗时基本保持在以下的数值
before search : 11
search : 1361
after search : 417

优化探索

1、相同索引下把查询精简

原本打算一个索引,多个query,但是语法规定了一个索引,一个query,所以这块优化不了。

2、fetchSource只获取用到的字段

public void mSearch(String code, List<String> contents, int timeout) throws IOException {
    Assert.notEmpty(contents, "contents must not be empty!");
    long start = System.currentTimeMillis();
    String index = getIndex(code);
    MultiSearchRequest request = new MultiSearchRequest();
    String[] includes = new String[]{"title"};
    for (String content : contents) {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery(Constants.FIELD_RULE, content));
        searchSourceBuilder.size(1);
        searchSourceBuilder.timeout(new TimeValue(timeout, TimeUnit.MILLISECONDS));
        searchSourceBuilder.fetchSource(includes, null);
        searchRequest.source(searchSourceBuilder);
        request.add(searchRequest);
    }

    System.out.println("before search : " + (System.currentTimeMillis() - start));
    start = System.currentTimeMillis();

    MultiSearchResponse multiSearchResponse = client.msearch(request, RequestOptions.DEFAULT);

    System.out.println("search : " + (System.currentTimeMillis() - start));
    start = System.currentTimeMillis();

    MultiSearchResponse.Item[] responses = multiSearchResponse.getResponses();
    List<SearchResult> list = new ArrayList<>(contents.size());
    for (int i = 0; i < responses.length; i++) {
        MultiSearchResponse.Item response = responses[i];
        if (null != response.getFailure()) {
            String error = response.getFailure().getClass().getSimpleName() + ": " + response.getFailure().getMessage();
            log.error("mSearch|{}|{}|{}|{}", index, contents.get(i), error, System.currentTimeMillis() - start);
        } else {
            SearchHits hits = response.getResponse().getHits();
            if (hits.getTotalHits().value > 0) {
                SearchHit firstHit = hits.getHits()[0];
                // TODO 查询结果封装
            } else {
            	log.error("mSearch|{}|{}|查无结果|{}", index, contents.get(i), System.currentTimeMillis() - start);
            }
        }
    }

    System.out.println("after search : " + (System.currentTimeMillis() - start));
}

耗时统计

before search : 10
search : 1338
after search : 85

从测试结果来看,使用了 fetchSource,优化最明显的反而是程序对每个查询结果封装的耗时。因为结果封装里面,需要把 source 进行反序列化成队列,使用了 fetchSource 后,source内容精简了,所以转换成对象也快了。由于我索引本来就只有5个字段,不算太多,还不能很好地测试出 fetchSource 的魅力。

3、调大search的线程池

3.1、查看线程池的统计信息

http://192.168.249.154:9200/_cat/thread_pool?v&h=node_name,name,active,queue,rejected,completed,size,type&pretty&s=type
elasticsearch节点的线程池统计信息

3.2、 查看指定操作的线程池信息

http://192.168.249.154:9200/_cat/thread_pool/search?v&h=node_name,name,active,queue,rejected,completed,size,type&pretty&s=type
search线程池的统计信息

3.3 调大search的线程池

新版本都只能在配置文件修改,然后重启elasticsearch节点,不支持通过REST请求去动态修改了。

$ vim elasticsearch-7.11.1/config/elasticsearch.yml

thread_pool.search.size: 20

修改search的线程池后(从原来的7变成了20),测试多次后的结果:

// elasticsearch重启后前3次的耗时
before search : 108
search : 3958
after search : 147
    
before search : 18
search : 2515
after search : 77
    
before search : 19
search : 2298
after search : 84


// 执行17次后耗时基本保持在以下的数值
before search : 11
search : 1522
after search : 74

从测试结果来看,调大了search的线程池,elasticsearch执行环节并没有太大的提升,反而下降了,而且不调大线程池前,大概联系查询10次就会稳定,但是调大后,要17次后才稳定。

为什么会出现这种情况呢?

原因很简单,回到前面一开始7个线程的那个search线程池处理的截图就清楚了,调整前7个线程的时候就线程池就没有存在rejected,queue堆积也不大,所以根本就不需要调大线程池。
在这里插入图片描述

总结

1、线程池的调整前,先看线程池有没有达到瓶颈了,等待队列的任务有多大,有没有出现拒绝的情况,有的话再去考虑调大线程池。
2、调大线程池,必须要对照自己服务器线程数,过大反而适得其反,还是老实根据官网建议 ( 系统线程数 * 3 ) / 2 + 1 这个默认配置来就行了。
3、优化的话,从 fetchSource 过滤不必要的字段入手,以及提升服务器性能才行。
4、合理设置分片数,检查集群健康度,分片过多或者部分分片挂掉,也会导致性能下降的。

最后

不要触碰这些配置! 中也提到不要轻易修改垃圾回收器和线程池。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值