分布式跨分片深度分页问题的总结

 

目录

1、问题背景

2、ElasticSearch是怎么解决的?

2.1   from+size

2.2   深度分页之scroll

2.3   search_after 

2.4   三种分页方式比较

3、ShardingSphere-JDBC是如何解决的?

4、Mycat是如何解决的?

5、客户端可以做什么来避免深度分页问题?

5.1 客户端连续翻页


1、问题背景

      我们有一些数据存储中间件是支持分布式存储的,是存储数据分片这一场景,例如用户的数据范围为1亿条数据,数据分别存储在10个分片上:sharding-0,sharding-1,......,sharding-9,
现在有这么一个需求,需要按照某一列进行排序

select * from t_order order by create_time desc limit 10 offset 1000000

,那么这种场景,特别是offset比较大的情况小,会存在深度分页问题。如果数据存储中间件
对这类问题没有特别处理的话,正常的一个处理逻辑是改写sql : 

select * from t_order order by create_time desc limit (10+1000000) offset 0

      将上述改写的sql发送给所有的分片,这样带来的问题是,每个分片需要向客户端/中间件代理返回(10+1000000)条数据,然后客户端/中间件需要对10*(10+1000000)这些大量的数据进行排序,这样必须存在
大量数据的排序并且大量的网络IO,极大地影响了组件的性能,会存在极大的隐患,页越深,问题越明显。大部分支持分片的中间件都会面临这个问题,现在,我们一起探讨一下,一些常用的中间件是如何解决
这个问题的。

2、ElasticSearch是怎么解决的?

参考文档:elasticsearch深度分页问题


2.1   from+size

         es 默认采用的分页方式是 from+ size 的形式,在深度分页的情况下,这种使用方式效率是非常低的。

2.2   深度分页之scroll

        在es中如果我们分页要请求大数据集或者一次请求要获取较大的数据集,scroll都是一个非常好的解决方案。

       使用scroll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的。每次发送scroll请求,我们还需要指定一个scroll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了。

      一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据库里的cursors(游标)。
滚屏搜索会及时制作快照。这个快照不会包含任何在初始阶段搜索请求后对index做的修改。它通过将旧的数据文件保存在手边,所以可以保护index的样子看起来像搜索开始时的样子。这样将使得我们无法得到用户最近的更新行为。

2.3   search_after 

from + size的分页方式虽然是最灵活的分页方式,但是当分页深度达到一定程度将会产生深度分页的问题。scroll能够解决深度分页的问题,但是其无法实现实时查询,即当scroll_id生成后无法查询到之后数据的变更,因为其底层原理是生成数据的快照。这时 search_after应运而生。其是在es-5.X之后才提供的。

search_after 是一种假分页方式,根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,但是只要能表示其唯一性就可以。

2.4   三种分页方式比较

分页方式 性能 优点 缺点 场景
from + size 灵活性好,实现简单 深度分页问题 数据量比较小,能容忍深度分页问题
scroll 解决了深度分页问题

无法反应数据的实时性(快照版本)

维护成本高,需要维护一个 scroll_id

海量数据的导出(比如笔者刚遇到的将es中20w的数据导入到excel)

需要查询海量结果集的数据

search_after

性能最好

不存在深度分页问题

能够反映数据的实时变更

实现复杂,需要有一个全局唯一的字段

连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果

海量数据的分页

   

3、ShardingSphere-JDBC是如何解决的?

参考文档:Sharding-JDBC 分库分表-分页查询优化方案

Sharding-JDBC的优化
(1)采用流式处理 + 归并排序的方式来避免内存的过量占用。由于SQL改写不可避免的占用了额外的带宽,但并不会导致内存暴涨。 与直觉不同,大多数人认为Sharding-JDBC会将1,000,010 * 2记录全部加载至内存,进而占用大量内存而导致内存溢出。 但由于每个结果集的记录是有序的,因此Sharding-JDBC每次仅获取各个分片的当前结果集记录,驻留在内存中的记录仅为当前路由到的分片的结果集的当前游标指向而已。 对于本身即有序的待排序对象,归并排序的时间复杂度仅为O(n),性能损耗很小。
(2)Sharding-JDBC对仅落至单分片的查询进行进一步优化。 落至单分片查询的请求并不需要改写SQL也可以保证记录的正确性,因此在此种情况下,Sharding-JDBC并未进行SQL改写,从而达到节省带宽的目的。

4、Mycat是如何解决的?

参考文档:Mycat分页中的坑
Mycat分页的大坑容易出现堆内存的OOM,例如:

SELECT * FROM customer ORDER BY id LIMIT 1000100, 100;

这个sql语句被Mycat转化后,

1 -> dn1{SELECT * FROM customer ORDER BY id LIMIT 0, 1000100}
2 -> dn2{SELECT * FROM customer ORDER BY id LIMIT 0, 1000100}

所以要在Mycat的server.xm里面开启使用非堆内存。否则内存会爆掉

<property name="useOffHeapForMerge">1</property>

5、客户端可以做什么来避免深度分页问题?

5.1 客户端连续翻页

          控制客户端对分页功能的限制,分页只能连续分页,只提供“下一页”功能按钮,只能一页一页的翻页,例如第1次分页请求是这样的,offset=1000000 size=10

select * from t_order order by id asc limit 1000000,10

       则下次分页请求记录上一次分页请求最大的id=last_max_id,则将以下改写之后的sql发送各个分片节点,这样,数据量限制在10条,大大地降低了性能损耗

select * from t_order where id > ${last_max_id} order by id asc limit 10

猜你喜欢

转载自blog.csdn.net/s2008100262/article/details/111614879