【Elasticsearch】搜索分页和deep paging问题

作者 : 开心源码 本文共3340个字,预计阅读时间需要9分钟 发布时间: 2022-05-14 共217人阅读

推荐阅读:

  • 我总结了72份面试题,累计3170页,斩获了30+互联网公司offer(含BATJM)

  • 2020首战告捷,这份Java面试神技Plus版,让我成功拿到了阿里、京东、字节跳动等大厂offer

  • 膜拜!阿里内部都在强推的K8S(kubernetes)学习指南,不能再详细了

概要

本篇从详情搜索分页为起点,简单阐述分页式数据搜索与原有集中式数据搜索思维方式的差异,就分页问题对deep paging问题的现象进行分析,最后详情分页式系统top N的案例。

搜索分页语法

Elasticsearch中search语法有from和size两个参数用来实现分页的效果:

  • size:显示应该返回的结果数量,默认是10。
  • from:显示查询数据的偏移量,即应该跳过的初始结果数量,默认是0。

from和size这两个参数的含义和MySql使用limit关键字分页的参数含义是一样的。

举几个示例,查询第1-3页的请求:

GET /music/children/_search?size=10GET /music/children/_search?size=10&from=10GET /music/children/_search?size=10&from=20

分布式数据与集中式数据的差异

集中式数据存储方式,从最早的单体应用模式,到早期的SOA服务模式,那时存储大多数都是采用集中式数据存储,数据落地到mysql等关系型数据中,有支持读写分离,部署了多台数据库实例实现主-从结构的本质上也还是集中式存储。

在单数据库或者主从数据库中,执行分页查询,统计排序等思路相对清晰,毕竟数据都完完整整地放在一起,直接挑一台实例搞就是了,可能就是容量有上限,出结果慢少量而已。

关系型数据库使用分布式数据存储的经典方案是分库分表,同一张表的数据,用肯定的路由逻辑,拆分在不同的数据库实例里存储,此时做数据统计,就不能只关注一个实例了。

分布式数据存储方式,搜索思路就开始有了细微的改变,比方说Elasticsearch,索引的数据是拆分存储在各个shard里的,每个shard可能散布在ES集群的各个node上,这种情况下,做查询,统计分析等操作,尽管ES已经封装好了技术细节,我们依然需要明白这是一个分布式储存的查询方案。

个人认为,分布式数据与集中式数据的解决差异,尽管在关系型数据库或者ES方面,已经有成熟的框架对其进行封装,但使用者还是需要从思维上去了解分布式带来的改变,这样才能得到正确的结果。

deep paging问题

deep paging简单来说叫深度分页,就是搜索得特别深,显示第好几百页的数据。为什么说deep paging是有问题的?

我们假定索引内有20000条数据,存储在5个shard里,发送一个有条件和指定排序字段的查询请求,假如我要取第1页的数据,那么每个shard都取10条数据,汇总到Coordinate Node里,共50条,Coordinate Node对这50条数据再进行排序,滤掉后面的40条数据,只取最前面的
10条,返回给用户端。

假如是第1000页呢?
按老套路每个shard取10000-10010条,汇总到Coordinate Node里,还是50条,最后返回给用户端?

这么做就错啦,分布式数据不是这么查的,第1000页,在每个shard中不是取第10000-10010条,而是取前面10010,5个shard共取50050条给Coordinate Node,Coordinate Node汇总数据完成排序等操作后再取第10000-10010条,返回用户端10条数据。

费了这么大劲收集到50050条数据,实际给用户端的就10条,丢掉50040条数据,好费内存。

假如第10000页呢?这结果不忍直视

假如一个索引的分片数越多,需要汇总的数据就会成倍增长 ,可以看到分布式系统对结果排序分布的成本随深度呈指数上升,最重要的两个影响维度是分页深度和shard数量。这种重量级的查询,极有可能拖垮整个Elasticsearch集群,所以说搜索引擎对任何查询都不要返回超过1000个结果。

引申top N问题模型

deep paging的问题,通过优化搜索关键词,控制分页深度,问题能得到肯定的改善,那top N问题如如何处理呢?

聚合查询中,经常能遇到查询最XX的10条记录这种分析需求,这种就是top N问题模型。

完美处理的场景

我们先举个熟习的案例:统计播放量最高的10首的英文儿歌。

document数据结构:

{  "_index": "music",  "_type": "children",  "_id": "2",  "_version": 6,  "found": true,  "_source": {    "name": "wake me, shark me",    "content": "don't let me sleep too late, gonna get up brightly early in the morning",    "language": "english",    "length": "55",    "likes": 0  }}

这个需求ES解决起来得心应手,有下面几个起因:

  • 一个document只会存在于一个shard中
  • 每个document数据里中有播放数量的统计值

有这上面几点的保证,查询时ES即可以放心大胆地在每个shard取播放数最高的前10条数据,Coordinate Node汇总的数据也就50条,此时性能非常高。

不宜直接查询的场景

上一小节依赖document的预先设计和shard存储数据的特性,避免了全索引扫描,性能特别高,假设系统中针对每天的播放点击,都有一个播放日志记录,记录着歌曲ID,点击人,点击时间,收听时长,时长百分比(与完整歌曲的百分比,有听到一半就退出不听了的,这个值就是50%),该document的数据示例:

{  "_index": "playlog-20191121",  "_type": "music",  "_id": "1",  "_version": 1,  "found": true,  "_source": {    "music_id": 1,    "listener": "tony wang",    "listen_date": "2019-11-21 15:35:00",    "music_length": 52,    "isten_percentage": 0.95  }}

假设歌曲总量200万条,每天的播放日志1亿条,日志索引每天建立一个,primary shard数量为10,命名格式playlog-yyyyMMdd,需求是搜索当天的播放排行榜,取排名前10的记录。

假如直接统计,就只能硬扛了,基本过程如下:

  1. 每个shard根据music_id做分组统计,理论上单shard数量量最多200万条。
  2. Coordinate Node收集10个shard的数据,进行合并,数据解决量上限2000万条,最后合并成200万条。
  3. 从这200万条数据取前面10条,返回给用户端。

这个过程绝对是重量级,假如每次都实时统计的话,ES集群的压力可想而知。

改进方案
  1. 播放功能添加数据升级逻辑
    预先添加按日期统计的索引数据结构,每次有客户点击播放时,额外发送一条升级消息将其数据升级,查询时直接从统计的索引里出结果,避免每次查询。

  2. 定时任务统计数据
    数据统计的需求,可以用定时任务进行计算,将计算结果存储起来,通过降低实时性,来避免全索引扫描计算的压力。

简单比照:

  • 相同点:都是以空间换时间的做法,避免全索引扫描。
  • 不同点:前者通过更改业务实现逻辑,添加数据级联升级,有肯定的业务耦合性;后者将实时计算变定时任务,灵活性较高,与业务耦合性低,但实时性差。
补充一点

良好的数据结构设计可以很大程度地降低ES查询压力,提高实时查询的性能,但有一点需要接受:考虑得再周全的设计,也难适应千变万化的需求;需求变更是无法避免的,没有一劳永逸的方案。

小结

本篇从分页查询入手,阐述了deep paging的问题起因,并顺带将自己对分布式系统与集中式系统解决的思维差异做了简单形容,最后引申了top N场景的问题,上面提到的改进方案,只是针比照较简单的场景,实际生产要面临的情况一定更复杂,比方采用分布式计算组件storm来处理top N 问题的,这里当做是抛砖引玉,欢迎各位分享自己的看法。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【Elasticsearch】搜索分页和deep paging问题

发表回复