🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,
15年
工作经验,精通Java编程
,高并发设计
,Springboot和微服务
,熟悉Linux
,ESXI虚拟化
以及云原生Docker和K8s
,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea
【Elasticsearch】filterQuery过滤查询
引言
在数字化时代的今天,数据量呈现出爆炸式增长,如何高效地从海量数据中获取所需信息成为了开发者们面临的重要挑战。Elasticsearch 作为一款强大的分布式搜索引擎,为我们提供了丰富的查询功能来应对这一挑战。其中,过滤查询(Filter Query)是一项极为重要的技术,它在大规模数据查询场景中发挥着关键作用。
想象一下,当我们面对一个拥有数十亿条记录的数据库时,传统的查询方式可能会因为计算每条数据与查询条件的相关性得分而消耗大量的时间和资源。而过滤查询的出现,就像是为我们开启了一扇优化查询性能的大门。它主要专注于对数据进行筛选过滤,并不计算文档与查询条件之间的相关性得分。这意味着什么呢?简单来说,就是在查询过程中,过滤查询能够快速地排除不符合条件的数据,只保留满足条件的部分,从而大大减少了后续处理的数据量,显著提高了查询的执行效率。
在实际的项目开发中,我们常常会遇到各种各样的查询需求。比如,我们可能需要从大量的用户信息中筛选出年龄在某个特定范围内的用户,或者找出拥有特定属性的产品记录。这些场景下,过滤查询就能够大显身手。通过合理运用不同类型的过滤查询条件,我们可以精准地定位到所需的数据,为业务的高效运行提供有力支持。
接下来,让我们一同深入探索 Elasticsearch Java 过滤查询的世界,了解常见的过滤查询条件以及如何将它们与普通查询条件巧妙结合,从而打造出高效、精准的数据检索解决方案。
一、Elasticsearch 基础概述
1.1 Elasticsearch 简介
Elasticsearch 是一个基于 Lucene 的分布式、RESTful 风格的搜索和数据分析引擎。它旨在快速、高效地存储、搜索和分析大量数据,被广泛应用于各种领域,如日志分析、电商搜索、企业搜索等。其分布式架构允许它轻松处理 PB 级别的数据,并能在多台服务器上进行水平扩展,以满足不同规模的业务需求。
1.2 Elasticsearch 核心概念
- 索引(Index):可以理解为一个数据库,是一组文档的集合。每个索引都有自己的映射(Mapping),用于定义文档的结构和字段类型。例如,在一个电商系统中,我们可以创建一个名为
products
的索引来存储所有产品的信息。 - 文档(Document):是 Elasticsearch 中最小的数据单元,类似于关系型数据库中的一行记录。每个文档都有一个唯一的标识符,可以包含一个或多个字段。比如,一个产品文档可能包含
product_id
、product_name
、price
等字段。 - 类型(Type):在 Elasticsearch 7.x 之前,一个索引可以包含多个类型,用于区分不同结构的文档。但从 7.x 版本开始,逐渐弱化了类型的概念,一个索引通常只包含一种类型的数据。
- 分片(Shard):为了处理大规模数据,Elasticsearch 将索引分割成多个分片,每个分片可以存储在不同的节点上。这不仅提高了数据的存储能力,还能通过并行处理提高查询性能。例如,一个大型的索引可能被分成 10 个分片,分布在不同的服务器上。
- 副本(Replica):为了保证数据的高可用性和容错性,Elasticsearch 会为每个分片创建一个或多个副本。当某个节点出现故障时,副本可以接管该节点上的分片工作,确保系统的正常运行。
1.3 Elasticsearch 工作原理
Elasticsearch 的工作原理涉及多个组件和流程。当我们向 Elasticsearch 发送一个查询请求时,首先请求会到达一个协调节点(Coordinating Node)。协调节点负责将查询请求分发到相关的分片上,这些分片会并行处理查询请求。然后,每个分片将查询结果返回给协调节点,协调节点再对这些结果进行合并和排序,最终将最终结果返回给客户端。
在数据写入方面,当我们向 Elasticsearch 插入一个文档时,文档首先会被写入到内存缓冲区(In-Memory Buffer)中。当缓冲区满了或者达到一定的时间间隔时,文档会被刷新(Flush)到磁盘上的一个新的段(Segment)中。段是 Lucene 中的一个数据存储单元,多个段可以在后台进行合并,以优化存储和查询性能。
二、Elasticsearch Java 客户端介绍
2.1 选择合适的 Java 客户端
在使用 Elasticsearch 进行 Java 开发时,有多种客户端可供选择,如官方的 Java High Level REST Client 和 Java Low Level REST Client,以及一些第三方客户端。
- Java High Level REST Client:这是 Elasticsearch 官方推荐的客户端,它基于 RESTful API 构建,提供了丰富的 Java 接口,使用起来更加方便和直观。它支持同步和异步操作,并且对 Elasticsearch 的各种功能进行了很好的封装,适合大多数 Java 开发者使用。
- Java Low Level REST Client:相对底层的客户端,它直接与 Elasticsearch 的 REST API 进行交互,需要开发者手动处理 HTTP 请求和响应。虽然使用起来较为复杂,但它提供了更大的灵活性,适用于对性能和定制化要求较高的场景。
2.2 引入 Maven 依赖
为了在 Java 项目中使用 Elasticsearch 客户端,我们需要在 pom.xml
文件中引入相应的 Maven 依赖。以 Java High Level REST Client 为例,以下是引入依赖的步骤:
首先,确保项目的 pom.xml
文件中包含了 Maven 中央仓库的配置:
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
然后,添加 Elasticsearch 客户端的依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.6</version>
</dependency>
这里的版本号 7.17.6
是当前最新的稳定版本,你可以根据实际情况进行更新。同时,还需要引入 Elasticsearch 核心库以及相关的 HTTP 客户端依赖:
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.17.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
通过引入这些依赖,我们的 Java 项目就能够使用 Elasticsearch Java High Level REST Client 来与 Elasticsearch 集群进行交互了。
2.3 初始化客户端
在代码中初始化 Elasticsearch Java High Level REST Client 通常需要以下步骤:
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
public class ElasticsearchClientInitializer {
public static RestHighLevelClient createClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
return client;
}
}
在上述代码中,我们创建了一个 RestHighLevelClient
实例,通过指定 Elasticsearch 集群的主机地址(这里是 localhost
)和端口号(默认 9200)来建立连接。在实际应用中,你可能需要根据实际的集群配置来调整这些参数。
三、过滤查询与普通查询的区别
3.1 普通查询的特点
普通查询(Query)在 Elasticsearch 中主要用于计算文档与查询条件之间的相关性得分(Relevance Score)。它会根据查询词在文档中的出现频率、位置等因素来评估每个文档与查询条件的匹配程度,最终返回按照相关性得分排序的文档列表。例如,当我们执行一个全文搜索查询时,Elasticsearch 会对每个文档进行分析,计算其与查询词的相关性得分,得分越高的文档排在越前面。
普通查询的优点是能够提供与查询条件高度相关的结果,适用于需要根据相关性进行排序的场景,如电商搜索中的商品推荐、搜索引擎的搜索结果展示等。然而,普通查询的缺点也很明显,由于它需要计算每个文档的相关性得分,在处理大规模数据时,性能开销较大,查询响应时间可能会较长。
3.2 过滤查询的特点
过滤查询(Filter Query)则专注于对数据进行筛选,它并不计算文档与查询条件的相关性得分,而是简单地判断文档是否满足过滤条件。如果文档满足条件,则被保留;否则,被排除。过滤查询通常用于缩小查询范围,例如在一个包含大量用户信息的索引中,我们可以使用过滤查询快速筛选出年龄大于 30 岁的用户文档。
过滤查询的优点在于其高效性,由于不计算相关性得分,它的执行速度非常快,能够在大规模数据中迅速定位到满足条件的数据。此外,过滤查询的结果可以被缓存,进一步提高查询性能。这使得过滤查询在需要快速筛选数据的场景中非常实用,如数据统计、日志分析等。
3.3 过滤查询的优势
3.3.1 性能提升
由于过滤查询不进行评分计算,它的执行速度通常比普通查询快很多。在处理大规模数据时,这种性能提升尤为明显。例如,在一个包含数百万文档的索引中进行查询,如果使用普通查询,可能需要花费几秒钟甚至更长时间来计算每个文档的相关性得分;而使用过滤查询,只需要快速检查文档是否满足过滤条件,可能在几十毫秒内就能返回结果。
3.3.2 缓存支持
Elasticsearch 对过滤查询结果提供了缓存机制。因为过滤查询的结果相对稳定,只要索引数据不变,相同过滤条件的查询结果也不会改变。所以,Elasticsearch 可以将过滤查询的结果缓存起来,下次执行相同的过滤查询时,直接从缓存中获取结果,进一步提高查询性能。
3.3.3 精确筛选
过滤查询适用于需要精确筛选出符合特定条件文档的场景。例如,我们要查找某个特定日期之后创建的订单,或者查找某个特定分类下的所有商品。通过使用过滤查询,可以准确地定位到满足条件的文档,而不会受到其他无关因素的影响。
3.3 两者结合的优势
在实际应用中,我们常常将普通查询和过滤查询结合使用。例如,在一个电商搜索场景中,我们首先使用过滤查询来缩小搜索范围,比如筛选出特定品牌、价格区间的商品,然后再使用普通查询对这些筛选后的商品进行相关性排序,以确保最相关的商品排在前面。这样的结合方式既能保证查询的高效性,又能提供高质量的搜索结果,满足用户的需求。
四、常见的过滤查询条件
4.1 term 过滤
term
过滤用于精确匹配某个字段的值。它不会对查询词进行分词处理,而是直接查找与指定值完全相同的文档。例如,我们有一个包含用户信息的索引,其中有一个 country
字段,我们想查找所有来自 “China” 的用户,可以使用以下代码:
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class TermFilterExample {
public static void main(String[] args) throws Exception {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
SearchRequest searchRequest = new SearchRequest("users");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("country", "China"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
// 处理搜索结果
System.out.println("Total hits: " + searchResponse.getHits().getTotalHits().value);
client.close();
}
}
在上述代码中,我们使用 QueryBuilders.termQuery("country", "China")
构建了一个 term
过滤查询,查找 users
索引中 country
字段值为 “China” 的文档。SearchSourceBuilder
用于构建搜索请求的源部分,我们将过滤查询添加到其中,然后通过 RestHighLevelClient
执行搜索请求并获取结果。
4.2 range 过滤
range
过滤用于筛选某个字段值在指定范围内的文档。例如,我们想查找价格在 100 到 500 之间的产品,可以使用以下代码:
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class RangeFilterExample {
public static void main(String[] args) throws Exception {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
SearchRequest searchRequest = new SearchRequest("products");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.rangeQuery("price")
.from(100)
.to(500));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
// 处理搜索结果
System.out.println("Total hits: " + searchResponse.getHits().getTotalHits().value);
client.close();
}
}
在这段代码中,QueryBuilders.rangeQuery("price").from(100).to(500)
构建了一个 range
过滤查询,查找 products
索引中 price
字段值在 100 到 500 之间的文档。from
和 to
方法分别指定了范围的起始值和结束值。
4.3 exists 过滤
exists
过滤用于判断某个字段是否存在于文档中。例如,我们想查找所有包含 email
字段的用户文档,可以使用以下代码:
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class ExistsFilterExample {
public static void main(String[] args) throws Exception {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
SearchRequest searchRequest = new SearchRequest("users");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.existsQuery("email"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
// 处理搜索结果
System.out.println("Total hits: " + searchResponse.getHits().getTotalHits().value);
client.close();
}
}
这里,QueryBuilders.existsQuery("email")
构建了一个 exists
过滤查询,查找 users
索引中包含 email
字段的文档。
五、将过滤条件与查询条件结合使用
5.1 先过滤后查询
在很多情况下,我们可以先使用过滤条件缩小数据范围,然后再进行普通查询。例如,在一个新闻搜索系统中,我们先通过 range
过滤筛选出最近一周内发布的新闻,然后再使用全文搜索查询用户输入的关键词。代码示例如下:
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class FilterThenQueryExample {
public static void main(String[] args) throws Exception {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
SearchRequest searchRequest = new SearchRequest("news");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 过滤条件:最近一周内发布的新闻
boolQueryBuilder.filter(QueryBuilders.rangeQuery("publish_date")
.gte(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000));
// 查询条件:用户输入的关键词
boolQueryBuilder.must(QueryBuilders.matchQuery("content", "人工智能"));
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest);
// 处理搜索结果
System.out.println("Total hits: " + searchResponse.getHits().getTotalHits().value);
client.close();
}
}
在上述代码中,我们使用 BoolQueryBuilder
来组合过滤条件和查询条件。boolQueryBuilder.filter
方法添加过滤条件,boolQueryBuilder.must
方法添加查询条件。先通过 range
过滤筛选出最近一周内发布的新闻,然后再对这些新闻进行全文搜索,查找包含 “人工智能” 关键词的新闻。
5.2 嵌套查询中的过滤
在复杂的查询结构中,我们也可以在嵌套查询中使用过滤条件。需要使用 NestedQueryBuilder
来处理嵌套文档结构。以下是一个详细的代码示例,假设我们有一个电商产品搜索场景,产品文档包含一个嵌套的 reviews
字段,每个评论又有 rating
(评分)和 comment
(评论内容)等字段,我们要查找某个品牌下,价格在一定范围内且评论评分大于某个值的产品。
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class NestedFilterSearchExample {
public static void main(String[] args) throws IOException {
// 创建 Elasticsearch 客户端
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
// 构建过滤条件:某个品牌下,价格在一定范围内且评论评分大于某个值
String brand = "Samsung";
double minPrice = 500.0;
double maxPrice = 2000.0;
int minRating = 4;
// 构建嵌套查询中的过滤条件
BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.rangeQuery("reviews.rating").gte(minRating));
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery(
"reviews", // 嵌套路径
nestedBoolQuery,
ScoreMode.None
);
// 构建整体查询条件
BoolQueryBuilder mainBoolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("brand", brand))
.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice))
.must(nestedQueryBuilder);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(mainBoolQuery);
SearchRequest searchRequest = new SearchRequest("product_index");
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
// 关闭客户端
client.close();
}
}
代码说明
- 创建客户端:使用
RestHighLevelClient
创建与 Elasticsearch 集群的连接。 - 构建嵌套查询中的过滤条件:
- 首先创建一个
BoolQueryBuilder
用于构建嵌套文档内的过滤条件,这里是评论评分大于minRating
。 - 然后使用
NestedQueryBuilder
将上述条件应用到reviews
嵌套路径上。ScoreMode.None
表示不计算嵌套文档的得分,因为我们主要关注过滤结果。
- 首先创建一个
- 构建整体查询条件:
- 创建一个主
BoolQueryBuilder
,添加品牌过滤、价格范围过滤,并将嵌套查询作为必须满足的条件添加进去。
- 创建一个主
- 执行搜索请求:
- 使用
SearchSourceBuilder
设置查询条件,并将其应用到SearchRequest
上。 - 执行搜索请求并处理搜索结果,遍历并打印每个匹配的文档。
- 使用
请根据实际的索引结构和数据类型调整字段名称和查询条件。这样就实现了在嵌套查询中进行过滤的功能。
六、参考资料
- Elasticsearch 官方文档
- Elasticsearch Java 高级 REST 客户端文档
- Elasticsearch 复合查询官方指南