ElasticsearchRestTemplate DSL日志打印

痛点

在使用 ElasticsearchRestTemplate 进行数据操作时,经常遇到的一个问题是线上问题排查困难。具体来说,在线上环境中,当出现问题时,我们往往无法直接获取到实际执行的 DSL(Domain Specific Language)语句。这导致我们在排查问题时面临以下几个主要挑战:

  • 缺乏可追溯性:当线上系统出现性能问题或数据不一致的问题时,我们需要能够回溯到具体的查询或更新操作。然而,由于 ElasticsearchRestTemplate 默认并不会记录实际执行的 DSL 语句,我们很难确定问题的具体原因。
  • 难以重现问题:在开发和测试环境中,我们通常会模拟各种场景来测试系统的健壮性和性能。但在生产环境中,由于数据量大且复杂,很多问题只有在实际运行时才会暴露出来。
  • 调试难度增加:当系统出现异常或错误时,我们需要能够快速定位问题并修复。如果无法获取到实际执行的 DSL 语句,我们就无法准确判断问题的原因。
  • 维护成本高:在长期维护过程中,系统可能会经历多次迭代和升级。如果没有记录实际执行的 DSL 语句,每次修改或优化查询逻辑时都需要重新验证其正确性和性能。
  • 性能优化困难:在优化过程中,我们需要能够分析每个查询的实际执行情况,从而找出性能瓶颈并进行针对性的优化。如果没有记录实际执行的 DSL 语句,我们就无法准确评估每个查询的性能表现,从而导致优化效果不佳。

解决方案

为了应对上述痛点,我们需要在使用 ElasticsearchRestTemplate 时打印实际执行的 DSL 日志。

打印基础文档查询信息

经查询发现有人提出打印queryBuilder.build().getQuery()的方法。然而,发现只会显示基础文档查询信息,而不包括过滤、聚合等内容。这并非我们所需的最终DSL语句。

log.info("DSL:{}", queryBuilder.build().getQuery());

在这里插入图片描述

打印最终DSL语句

通过Debug我们发现 SearchRequest 中的source属性,正是我们想要的最终DSL语句。
在这里插入图片描述
SearchRequest 需要通过 RequestFactory 获取,虽然 ElasticsearchRestTemplate 提供了getRequestFactory() 方法获取RequestFactory,但RequestFactory是个私有类,无法编译运行。好在java留了后门,提供了强大的反射机制。于是,对于elasticsearchRestTemplate.search()方法编写了如下切面:

/**
 * Description: ElasticsearchRestTemplate 切面
 *
 * @author YanAn
 * @date 2024/9/20 10:52
 */
@Slf4j
@Aspect
@Component
public class ElasticsearchRestTemplateAspect {

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * AbstractElasticsearchTemplate#search(org.springframework.data.elasticsearch.core.query.Query, java.lang.Class) 前置通知
     *
     * @param joinPoint
     */
    @Before("execution(* org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate.search(org.springframework.data.elasticsearch.core.query.Query, java.lang.Class))")
    public void beforeSearch(JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length == 2) {
                Query query = (Query) args[0];
                Class<?> clazz = (Class<?>) args[1];
                IndexCoordinates index = elasticsearchRestTemplate.getIndexCoordinatesFor(clazz);
                Method searchRequest = Class.forName("org.springframework.data.elasticsearch.core.RequestFactory").getMethod("searchRequest", Query.class, Class.class, IndexCoordinates.class);
                searchRequest.setAccessible(true);
                Object result = searchRequest.invoke(elasticsearchRestTemplate.getRequestFactory(), query, clazz, index);
                if (result instanceof SearchRequest) {
                    SearchSourceBuilder source = ((SearchRequest) result).source();
                    log.info("DSL: {}", source.toString());
                }
            } else {
                log.info("DSL打印失败");
            }
        } catch (Exception e) {
            log.warn("DSL打印失败", e);
        }
    }
}

效果如下:
在这里插入图片描述

### Spring Boot 中实现 Elasticsearch DSL 查询 在 Spring Boot 项目中,可以通过 `ElasticsearchRestTemplate` 或者其他方式来执行自定义的 Elasticsearch DSL 查询。以下是基于提供的参考资料以及专业知识的一个完整解决方案。 #### 使用 ElasticsearchRestTemplate 执行 DSL 查询 通过 `org.springframework.data.elasticsearch.core.ElasticsearchOperations` 接口可以方便地构建并发送 DSL 查询请求。下面是一个完整的代码示例: ```java import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.stereotype.Service; @Service public class ElasticSearchService { private final ElasticsearchRestTemplate elasticsearchRestTemplate; @Autowired public ElasticSearchService(ElasticsearchRestTemplate elasticsearchRestTemplate) { this.elasticsearchRestTemplate = elasticsearchRestTemplate; } public void performDslQuery() throws Exception { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // 构建查询条件 (DSL Query) searchSourceBuilder.query(QueryBuilders.matchQuery("field", "value")); searchSourceBuilder.from(0); searchSourceBuilder.size(10); // 添加排序功能 searchSourceBuilder.sort("timestamp", SortOrder.DESC); String indexName = "your_index_name"; var response = elasticsearchRestTemplate.search(searchSourceBuilder.toString(), YourEntityClass.class, IndexCoordinates.of(indexName)); System.out.println(response.getHits().stream() .map(SearchHit::getContent).toList()); } } ``` 上述代码展示了如何使用 `ElasticsearchRestTemplate` 和 `SearchSourceBuilder` 来创建一个简单的匹配查询,并指定分页和排序参数[^2]。 --- #### 日志打印配置 为了调试目的,在开发环境中通常希望查看实际发出的 DSL 请求内容。这可以通过设置日志级别或者拦截器的方式完成。具体做法如下: ##### 方法一:调整 Logback 配置文件 编辑项目的 `logback-spring.xml` 文件,增加以下内容以捕获 HTTP 请求的日志输出: ```xml <logger name="org.springframework.web.client.RestTemplate" level="DEBUG"/> <logger name="org.springframework.http.HttpLoggingInterceptor" level="DEBUG"/> ``` ##### 方法二:手动记录 DSL 字符串 如果需要更灵活的方式来控制日志行为,则可以在每次调用前将生成的 JSON 字符串写入日志: ```java System.out.println("Generated DSL: " + searchSourceBuilder.toString()); elasticsearchRestTemplate.search(searchSourceBuilder.toString(), ...); ``` 这种方法简单直观,适合快速验证查询逻辑是否正确。 --- #### 分页支持 当处理大量数据时,建议利用 Spring Data 提供的标准接口——`PagingAndSortingRepository` 进行分页操作。例如: ```java Page<MyDocument> pageResult = repository.findAll(PageRequest.of(pageNumber, pageSize)); List<MyDocument> resultList = pageResult.getContent(); ``` 这里的关键在于传递合适的 `Pageable` 对象给存储库方法[^3]。 --- #### 完整流程总结 综上所述,要实现在 Spring Boot 应用程序里运行带有复杂过滤规则的 Elasticsearch 查询,主要分为以下几个方面的工作: 1. **引入依赖**:确保 Maven/Gradle 已经包含了必要的客户端库; 2. **编写服务层代码**:借助于高级 API 如 RestHighLevelClient 或封装后的模板工具类; 3. **优化性能表现**:合理设计索引结构、字段映射关系以及缓存策略等; 4. **增强可观测能力**:启用详细的网络通信跟踪机制以便定位潜在瓶颈所在位置。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值