ElasticSearch第5天 es实现分页查询的几种方式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ElasticSearch第5天 es实现分页查询的几种方式相关的知识,希望对你有一定的参考价值。
参考技术A es实现分页查询,在ES中有三种方式可以实现分页:from+size、scroll、search_after这种分页方式虽然查询变快了,但滚动上下文代价很高,每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上,那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。
searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
search_after的理念是,=在不同分片上(假设有5个分片),先按照指定顺序排好,根据我们传的search_after值 ,然后仅取这个值之后的size个文档。这 5*size 个文档拿到Es内存中排序后,返回前size个文档即可。避免了浅分页导致的内存爆炸情况,经实际使用性能良好,ES空闲状态下查询耗时稳定在50ms以内,平均10~20ms。
ElasticSearch之Search_After的注意事项
1.搜索时,需要指定sort,并且保证值是唯一的(可以通过加入_id或者文档body中的业务唯一值来保证);
2.再次查询时,使用上一次最后一个文档的sort值作为search_after的值来进行查询;
3.不能使用随机跳页,只能是下一页或者小范围的跳页(一次查询出小范围内各个页数,利用缓存等技术,来实现小范围分页,比较麻烦,比如从第一页调到第五页,则依次查询出2,3,4页的数据,利用每一次最后一个文档的sort值进行下一轮查询,客户端或服务端都可以进行,如果跳的比较多,则可能该方法并不适用)
它与滚动API非常相似,但与它不同,search_after参数是无状态的,它始终针对最新版本的搜索器进行解析。因此,排序顺序可能会在步行期间发生变化,具体取决于索引的更新和删除
from+ size 分页,如果数据量不大或者from、size不大的情况下,效率还是蛮高的。但是在深度分页的情况下,这种使用方式效率是非常低的,并发一旦过大,还有可能直接拖垮整个ElasticSearch的集群。
scroll 分页通常不会用在客户端,因为每一个 scroll_id 都会占用大量的资源,一般是后台用于全量读取数据使用
search_after通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景,一般用于客户端的分页查询
大体而言就是在这三种分页方式中,from + size不适合数据量很大的场景,scroll不适合实时场景,而search after在es5.x版本之后应运而生,较好的解决了这个问题。
SpringBoot整合ElasticSearch实现模糊查询,批量CRUD,排序,分页,高亮
文章目录
上述部分为理论部分,本章跟着我一起来看一下具体开发中es是如何使用的
本章的完整代码在文末可以自行查看下载
4.1 导入elasticsearch依赖
在pom.xml里加入如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
我的SpringBoot版本:2.6.2
引入之后记得要看一下你的依赖版本是否和es的版本是否适配,如果不一致,会连接失败
启动es,在浏览器输入http://localhost:9200/查看es版本
很明显,我们的版本是不兼容的,我找了半天spring-boot-starter-data-elasticsearch依赖包也没找到适配es 8.6.1的依赖,为了不影响进度,我先退而求其次,先使用7.15.2这个版本的es
安装包:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-windows-x86_64.zip
安装过程和我们第二章的过程一样,详情可参考:《第二章:ElasticSearch安装》
安装之后,我们可以看到我们的版本号已经变为7.15.2啦
4.2 创建高级客户端
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticSearchClientConfig
@Bean
public RestHighLevelClient restHighLevelClient()
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")));
return client;
如果你的es是部署在服务器上,那么127.0.0.1则需要改成你服务器的ip地址
4.3 基本用法
1.创建、判断存在、删除索引
- 创建索引
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引
*
* @return
* @throws IOException
*/
@GetMapping("/index/createIndex")
public Object createIndex() throws IOException
//1.创建索引请求
CreateIndexRequest request = new CreateIndexRequest("ninesunindex");
//2.客户端执行请求IndicesClient,执行create方法创建索引,请求后获得响应
CreateIndexResponse response =
restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
return response;
可以看到索引已经创建成功
PS:如果不知道索引以及我们后面提到的名次概念,可以花几分钟读一下:《第三章:ElasticSearch相关概念》
- 查询索引
/**
* 查询索引
*
* @return
* @throws IOException
*/
@GetMapping("/index/searchIndex")
public Object searchIndex() throws IOException
//1.查询索引请求
GetIndexRequest request = new GetIndexRequest("ninesunindex");
//2.执行exists方法判断是否存在
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
return exists;
- 删除索引
/**
* 删除索引
*
* @return
* @throws IOException
*/
@GetMapping("/index/delIndex")
public Object delIndex() throws IOException
//1.删除索引请求
DeleteIndexRequest request = new DeleteIndexRequest("ninesunindex");
//执行delete方法删除指定索引
AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
return delete.isAcknowledged();
2.对文档的CRUD
创建文档:
注意:如果添加时不指定文档ID,他就会随机生成一个ID,ID唯一。
创建文档时若该ID已存在,发送创建文档请求后会更新文档中的数据。
- 新增文档
/**
* 新增文档
*
* @return
* @throws IOException
*/
@GetMapping("/document/add")
public Object add() throws IOException
//1.创建对象
User user = new User("Go", 21, new String[]"内卷", "吃饭");
//2.创建请求
IndexRequest request = new IndexRequest("ninesunindex");
//3.设置规则 PUT /ljx666/_doc/1
//设置文档id=6,设置超时=1s等,不设置会使用默认的
//同时支持链式编程如 request.id("6").timeout("1s");
request.id("6");
request.timeout("1s");
//4.将数据放入请求,要将对象转化为json格式
//XContentType.JSON,告诉它传的数据是JSON类型
request.source(JSONValue.toJSONString(user), XContentType.JSON);
//5.客户端发送请求,获取响应结果
IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
System.out.println(indexResponse.status());
return indexResponse;
- 获取文档中的数据
/**
* 获取文档中的数据
*
* @return
* @throws IOException
*/
@GetMapping("/document/get")
public Object get() throws IOException
//1.创建请求,指定索引、文档id
GetRequest request = new GetRequest("ninesunindex", "6");
GetResponse getResponse = restHighLevelClient.get(request, RequestOptions.DEFAULT);
System.out.println(getResponse);//获取响应结果
//getResponse.getSource() 返回的是Map集合
System.out.println(getResponse.getSourceAsString());//获取响应结果source中内容,转化为字符串
return getResponse;
- 更新文档数据
注意:需要将User对象中的属性全部指定值,不然会被设置为空,如User只设置了名称,那么只有名称会被修改成功,其他会被修改为null。
/**
* 更新文档数据
*
* @return
* @throws IOException
*/
@GetMapping("/document/update")
public Object update() throws IOException
//1.创建请求,指定索引、文档id
UpdateRequest request = new UpdateRequest("ninesunindex", "6");
User user = new User("GoGo", 21, new String[]"内卷", "吃饭");
//将创建的对象放入文档中
request.doc(JSONValue.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());//更新成功返回OK
return updateResponse;
- 删除文档数据
/**
* 删除文档数据
*
* @return
* @throws IOException
*/
@GetMapping("/document/delete")
public Object delete() throws IOException
//1.创建删除文档请求
DeleteRequest request = new DeleteRequest("ninesunindex", "6");
DeleteResponse deleteResponse = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());//更新成功返回OK
return deleteResponse;
3.批量新增文档数据
- 批量新增文档数据
/**
* 批量新增文档数据
*
* @return
* @throws IOException
*/
@GetMapping("/document/addBatch")
public Object addBatch() throws IOException
BulkRequest bulkRequest = new BulkRequest();
//设置超时
bulkRequest.timeout("10s");
List<User> list = new ArrayList<>();
list.add(new User("Java", 25, new String[]"内卷"));
list.add(new User("Go", 18, new String[]"内卷"));
list.add(new User("C", 30, new String[]"内卷"));
list.add(new User("C++", 26, new String[]"内卷"));
list.add(new User("Python", 20, new String[]"内卷"));
int id = 1;
//批量处理请求
for (User u : list)
//不设置id会生成随机id
bulkRequest.add(new IndexRequest("ninesunindex")
.id("" + (id++))
.source(JSONValue.toJSONString(u), XContentType.JSON));
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures());//是否执行失败,false为执行成功
return bulkResponse;
4.查询所有、模糊查询、分页查询、排序、高亮显示
@GetMapping("test")
public Object test() throws IOException
SearchRequest searchRequest = new SearchRequest("ninesunindex");//里面可以放多个索引
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构造搜索条件
//此处可以使用QueryBuilders工具类中的方法
//1.查询所有
sourceBuilder.query(QueryBuilders.matchAllQuery());
//2.查询name中含有Java的
sourceBuilder.query(QueryBuilders.multiMatchQuery("java", "userName"));
//3.分页查询
sourceBuilder.from(0).size(5);
//4.按照score正序排列
sourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.ASC));
//5.按照id倒序排列(score会失效返回NaN)
sourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.DESC));
//6.给指定字段加上指定高亮样式
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("userName").preTags("<span style='color:red;'>").postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//获取总条数
System.out.println(searchResponse.getHits().getTotalHits().value);
//输出结果数据(如果不设置返回条数,大于10条默认只返回10条)
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits)
System.out.println("分数:" + hit.getScore());
Map<String, Object> source = hit.getSourceAsMap();
System.out.println("index->" + hit.getIndex());
System.out.println("id->" + hit.getId());
for (Map.Entry<String, Object> s : source.entrySet())
System.out.println(s.getKey() + "--" + s.getValue());
return searchResponse;
4.4 总结
1.大致流程
创建对应的请求 --> 设置请求(添加规则,添加数据等) --> 执行对应的方法(传入请求,默认请求选项)–> 接收响应结果(执行方法返回值)–> 输出响应结果中需要的数据(source,status等)
2.注意事项
如果不指定id,会自动生成一个随机id
正常情况下,不应该这样使用new IndexRequest(“indexName”),如果索引发生改变了,那么代码都需要修改,可以定义一个枚举类或者一个专门存放常量的类,将变量用final static等进行修饰,并指定索引值。其他地方引用该常量即可,需要修改也只需修改该类即可。
elasticsearch相关的东西,版本都必须一致,不然会报错
elasticsearch很消耗内存,建议在内存较大的服务器上运行elasticsearch,否则会因为内存不足导致elasticsearch自动killed
git地址:https://gitee.com/ninesuntec/es-better.git
PS:本章git上的代码如果有被注释掉的,只是为了防止和后面的章节不冲突,并无错误,大家自行解注查看即可
下一章:《ElasticSearchRepository和ElasticsearchRestTemplate的使用》
以上是关于ElasticSearch第5天 es实现分页查询的几种方式的主要内容,如果未能解决你的问题,请参考以下文章
bos 第5天(定区的添加定区的分页查询hessian远程调用实现获取客户信息)
SpringBoot整合ElasticSearch实现模糊查询,批量CRUD,排序,分页,高亮
elasticsearch(es)查询api,结果集排序,分页,范围查询