Java elasticsearch scroll模板实现

一、scroll说明和使用场景

scroll的使用场景:大数据量的检索和操作

scroll顾名思义,就是游标的意思,核心的应用场景就是遍历 elasticsearch中的数据;

通常我们遍历数据采用的是分页,elastcisearch还支持from size的方式进行分页查询,使用 from and size 的深度分页,比如说 ?size=10&from=10000,因为 100,000 排序的结果必须从每个分片上取出并重新排序最后返回 10 条。这个过程需要对每个请求页重新进行提取+排序,效率很低,消耗很大,所以默认的最大可分页的数据是10000,超过10000是不建议的;

使用

通过在url末尾带上scroll=1m表示开启一个游标,1m表示游标的有效期为1分钟

javascript 复制代码
POST /record/_search?scroll=1m
{
  "from": 0,
  "size": 20
}

返回结果中会把scroll的id带上,再次查询的时候,直接用scroll id查询即可

javascript 复制代码
POST /_search/scroll
{
    "scroll" : "1m", 
    "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDnF1ZXJ5VGhlbkZldGNoAhZuYmpMbVpwWFRUMnNFMUFFSHlSMHB3AAAAAALBy_0WUWxrNTRTaWNUcy1sOHQ0VUo5dzF6dxZoemFkZTlMeFQ4MmoyOW5SUG8ybE53AAAAAAN6ip8WMmk5TWZlQ21RQnFsNURwaXRzSGhCdw==" 
}

二、基于ElasticsearchRestTemplate的实现

这里我们定义了一个template如下,主要作用就是实现一个基于scroll的数据遍历模板,屏蔽开启scroll 以及 scroll遍历所有数据,通过Consumer<T>钩子函数进行数据处理

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchScrollHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

import java.util.List;
import java.util.concurrent.*;

/**
 * scrollTemplate 模板,用于遍历整个Index的数据
 * @author xiuzhu
 * @Date 2023/7/28 13:12
 */
@Slf4j
public class ElasticSearchScrollTemplate<T> {

    ExecutorService executorService = new ThreadPoolExecutor(1, 4,
                                      30,TimeUnit.SECONDS,
                                      new LinkedBlockingQueue<Runnable>(5),
                                        Executors.defaultThreadFactory(),
                                        new ThreadPoolExecutor.CallerRunsPolicy()
                                    );

    ElasticsearchRestTemplate elasticSearchRestTemplate;

    Class<T> cls;

    String indexName;

    public ElasticSearchScrollTemplate(
            ElasticsearchRestTemplate template,
            Class<T> cls,
            String indexName
    ) {
        this.elasticSearchRestTemplate = template;
        this.cls = cls;
        this.indexName = indexName;
    }

    @FunctionalInterface
    public interface Consumer<T> {
        public void accept(List<T> objects);
    }

    public void execute(Consumer<T> consumer) {
        //构建查询条件
        NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        query.withPageable(PageRequest.of(0, 300));
        query.withQuery(queryBuilder);

        //保留0.5分钟
        long scrollTimeInMillis = 30*1000;

        IndexCoordinates recordIndex = IndexCoordinates.of(indexName);
        SearchScrollHits<T> hits = elasticSearchRestTemplate.searchScrollStart(scrollTimeInMillis, query.build(), cls, recordIndex);

        // scrollId
        String scrollId = hits.getScrollId();
        List<T> recordEntityList = hits.stream().map(SearchHit::getContent).toList();
        long total = 0L;

        log.info("================ began scroll index={} ====================", indexName);

        executorService.submit(()->{
            consumer.accept(recordEntityList);
        });

        total = total + recordEntityList.size();

        log.info("================  has scroll index={} total={} ====================", indexName, total);
        while (!hits.isEmpty()) {
            hits = elasticSearchRestTemplate.searchScrollContinue(scrollId, scrollTimeInMillis, cls, recordIndex);
            List<T> entities = hits.stream().map(SearchHit::getContent).toList();

            executorService.submit(()->{
                consumer.accept(entities);
            });

            total = total + entities.size();
            try {
                //给系统留GC时间,不然容易内存溢出
                Thread.sleep(300);
            } catch (InterruptedException e) {
                log.error("sleep error", e);
            }
            log.info("================  has scroll index={} total={} ====================", indexName, total);
        }
        log.info("================ end scroll index={} ====================", indexName);
    }
}

使用参考:

java 复制代码
@Resource(name = "elasticSearchRestTemplate")
    ElasticsearchRestTemplate elasticsearchRestTemplate;

new ElasticSearchScrollTemplate<>(
                        elasticsearchRestTemplate,
                        RecordEntity.class,
                        "record")
                ).execute((entities)->{
                    entities.forEach(item->{
                        //这里进行数据的处理,比如修改数据
                        recordEntityService.save(item);
                        log.info("tag update success record={} api={}", item.getId());

                    });
                });

本文由mdnice多平台发布

相关推荐
你不是我我5 小时前
【Java 开发日记】HTTP3 性能更好,为什么内网微服务依然多用 HTTP2?HTTP2 内网优势是什么?
java·开发语言·微服务
雪碧聊技术5 小时前
大模型爆火!Java后端如何抓住Agent全栈开发的风口
java·大模型·agent·全栈开发
逻辑驱动的ken6 小时前
Java高频面试场景题25
java·开发语言·深度学习·面试·职场和发展
AI人工智能+电脑小能手7 小时前
【大白话说Java面试题】【Java基础篇】第32题:Java的异常处理机制是什么
java·开发语言·后端·面试
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ8 小时前
通过java后端代码来实现给word内容补充格式文本内容控件,以及 设置控件的标记和标题
java·c#·word
無限進步D9 小时前
Java 面向对象高级 接口
java·开发语言
逸Y 仙X10 小时前
文章二十七:ElasticSearch ES查询模板(Search Template)高效复用实战
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
二哈赛车手10 小时前
新人笔记---Spring AI的Advisor以及其底层机制讲解(涉及源码),包含一些遇见的Spring AI的Advisor缺陷问题的解决方案
java·人工智能·spring boot·笔记·spring
AC赳赳老秦10 小时前
接口测试自动化:用 OpenClaw 对接 Postman,实现批量回归测试、测试报告自动生成与推送
java·人工智能·python·算法·elasticsearch·deepseek·openclaw