互联网数据库“跨库分页”架构技术实践 -欧洲杯足彩官网

1顶
0踩

引用
来源:
作者:

一、需求缘起

分页需求

互联网很多业务都有分页拉取数据的需求,例如:
  • 微信消息过多时,拉取第n页消息。
  • 京东下单过多时,拉取第n页订单。
  • 浏览58同城,查看第n页帖子。
这些业务场景对应的消息表,订单表,帖子表分页拉取需求有这样一些特点:
  • 有一个业务主键id,例如msg_id,order_id,tiezi_id
  • 分页排序是按照非业务主键id来排序的,业务中经常按照时间time来排序order by

在数据量不大时,可以通过在排序字段time上建立索引,利用sql提供的offset/limit功能就能满足分页查询需求:
select * from t_msg order by time offset 200 limit 100 
select * from t_order order by time offset 200 limit 100 
select * from t_tiezi order by time offset 200 limit 100 

此处假设一页数据为100条,均拉取第3页数据。

分库需求

高并发大流量的互联网架构,一般通过服务层来访问数据库,随着数据量的增大,数据库需要进行水平切分,分库后将数据分布到不同的数据库实例(甚至物理机器)上,以达到降低数据量,增加实例数的扩容目的。

一旦涉及分库,逃不开“分库依据”patition key的概念,使用哪一个字段来水平切分数据库呢:大部分的业务场景,会使用业务主键id。

确定了分库依据patition key后,接下来要确定的是分库算法:大部分的业务场景,会使用业务主键id取模的算法来分库,这样即能够保证每个库的数据分布是均匀的,又能够保证每个库的请求分布是均匀的,实在是简单实现负载均衡的好方法,此法在互联网架构中应用颇多。

举一个更具体的例子:

用户库user,水平切分后变为两个库,分库依据patition key是uid,分库算法是uid取模:uid%2余0的数据会落到db0,uid%2余1的数据会落到db1。

问题的提出

仍然是上述用户库的例子,如果业务要查询“最近注册的第3页用户”,该如何实现呢?单库上,可以select * from t_user order by time offset 200 limit 100,变成两个库后,分库依据是uid,排序依据是time,数据库层失去了time排序的全局视野,数据分布在两个库上,此时该怎么办呢?

如何满足“跨越多个水平切分数据库,且分库依据与排序依据为不同属性,并需要进行分页”的查询需求,实现select*from t order by time offset x limit y的跨库分页sql,是本文将要讨论的技术问题。

二、全局视野法


如上图所述,服务层通过uid取模将数据分布到两个库上去之后,每个数据库都失去了全局视野,数据按照time局部排序之后,不管哪个分库的第3页数据,都不一定是全局排序的第3页数据。

那到底哪些数据才是全局排序的第3页数据呢,暂且分三种情况讨论。

(1)极端情况,两个库的数据完全一样

如果两个库的数据完全相同,只需要每个库offset一半,再取半页,就是最终想要的数据(如上图中粉色部分数据)。

(2)极端情况,结果数据来自一个库


也可能两个库的数据分布及其不均衡,例如db0的所有数据的time都大于db1的所有数据的time,则可能出现:一个库的第3页数据,就是全局排序后的第3页数据(如上图中粉色部分数据)。

(3)一般情况,每个库数据各包含一部分

正常情况下,全局排序的第3页数据,每个库都会包含一部分(如上图中粉色部分数据)。

由于不清楚到底是哪种情况,所以必须每个库都返回3页数据,所得到的6页数据在服务层进行内存排序,得到数据全局视野,再取第3页数据,便能够得到想要的全局分页数据。

再总结一下这个方案的步骤:
  • 将order by time offset x limit y,改写成order by time offset 0 limit x y。
  • 服务层将改写后的sql语句发往各个分库:即例子中的各取3页数据。
  • 假设共分为n个库,服务层将得到n*(x y)条数据:即例子中的6页数据。
  • 服务层对得到的n*(x y)条数据进行内存排序,内存排序后再取偏移量x后的y条记录,就是全局视野所需的一页数据。
方案优点:通过服务层修改sql语句,扩大数据召回量,能够得到全局视野,业务无损,精准返回所需数据。

方案缺点(显而易见):
  • 每个分库需要返回更多的数据,增大了网络传输量(耗网络);
  • 除了数据库按照time进行排序,服务层还需要进行二次排序,增大了服务层的计算量(耗cpu);
  • 最致命的,这个算法随着页码的增大,性能会急剧下降,这是因为sql改写后每个分库要返回x y行数据:返回第3页,offset中的x=200;假如要返回第100页,offset中的x=9900,即每个分库要返回100页数据,数据量和排序量都将大增,性能平方级下降。
三、业务折衷法

“全局视野法”虽然性能较差, 但其业务无损,数据精准,不失为一种方案,有没有性能更优的方案呢?

“任何脱离业务的架构设计都是耍流氓”,技术方案需要折衷,在技术难度较大的情况下,业务需求的折衷能够极大的简化技术方案。

业务折衷一:禁止跳页查询

在数据量很大,翻页数很多的时候,很多产品并不提供“直接跳到指定页面”的功能,而只提供“下一页”的功能,这一个小小的业务折衷,就能极大的降低技术方案的复杂度。

如上图,不能够跳页,那么第一次只能够查询第一页:
  • 将查询order by time offset 0 limit 100,改写成order by time where time>0 limit 100。
  • 上述改写和offset 0 limit 100的效果相同,都是每个分库返回了一页数据(上图中粉色部分)。
  • 服务层得到2页数据,内存排序,取出前100条数据,作为最终的第一页数据,这个全局的第一页数据,一般来说每个分库都包含一部分数据(如上图粉色部分)。

咦,这个方案也需要服务器内存排序,岂不是和“全局视野法”一样么?第一页数据的拉取确实一样,但每一次“下一页”拉取的方案就不一样了。

点击“下一页”时,需要拉取第二页数据,在第一页数据的基础之上,能够找到第一页数据time的最大值:

这个上一页记录的time_max,会作为第二页数据拉取的查询条件:
  • 将查询order by time offset 100 limit 100,改写成order by time where time>$time_max limit 100。
  • 这下不是返回2页数据了(“全局视野法,会改写成offset 0 limit 200”),每个分库还是返回一页数据(如上图中粉色部分)。
  • 服务层得到2页数据,内存排序,取出前100条数据,作为最终的第2页数据,这个全局的第2页数据,一般来说也是每个分库都包含一部分数据(如上图粉色部分)。
如此往复,查询全局视野第100页数据时,不是将查询条件改写为offset 0 limit 9900 100(返回100页数据),而是改写为time>$time_max99 limit 100(仍返回一页数据),以保证数据的传输量和排序的数据量不会随着不断翻页而导致性能下降。

业务折衷二:允许数据精度损失

“全局视野法”能够返回业务无损的精确数据,在查询页数较大,例如第100页时,会有性能问题,此时业务上是否能够接受,返回的100页不是精准的数据,而允许有一些数据偏差呢?

数据库分库-数据均衡原理

使用patition key进行分库,在数据量较大,数据分布足够随机的情况下,各分库所有非patition key属性,在各个分库上的数据分布,统计概率情况应该是一致的。

例如,在uid随机的情况下,使用uid取模分两库,db0和db1:
  • 性别属性,如果db0库上的男性用户占比70%,则db1上男性用户占比也应为70%;
  • 年龄属性,如果db0库上18-28岁少女用户比例占比15%,则db1上少女用户比例也应为15%;
  • 时间属性,如果db0库上每天10:00之前登录的用户占比为20%,则db1上应该是相同的统计规律;

利用这一原理,要查询全局100页数据,offset 9900 limit 100改写为offset 4950 limit 50,每个分库偏移4950(一半),获取50条数据(半页),得到的数据集的并集,基本能够认为,是全局数据的offset 9900 limit 100的数据,当然,这一页数据的精度,并不是精准的。

根据实际业务经验,用户都要查询第100页网页、帖子、邮件的数据了,这一页数据的精准性损失,业务上往往是可以接受的,但此时技术方案的复杂度便大大降低列,既不需要返回更多的数据,也不需要进行服务内存排序了。

四、终极武器:二次查询法

有没有一种技术方案,即能够满足业务的精确需要,无需业务折衷,又高性能的方法呢?这就是接下来要介绍的终极武器:“二次查询法”。

为了方便举例,假设一页只有5条数据,查询第200页的sql语句为select*from t order by time offset 1000 limit 5。

步骤一:查询改写

将select*from t order by time offset 1000 limit 5改写为select*from t order by time offset 500 limit 5并投递给所有的分库,注意,这个offset的500,来自于全局offset的总偏移量1000,除以水平切分数据库个数2。

如果是3个分库,则可以改写为select*from t order by time offset 333 limit 5,假设这三个分库返回的数据(time, uid)如下:

可以看到,每个分库都是返回的按照time排序的一页数据。

步骤二:找到所返回3页全部数据的最小值
  • 第一个库,5条数据的time最小值是1487501123
  • 第二个库,5条数据的time最小值是1487501133
  • 第三个库,5条数据的time最小值是1487501143

故,三页数据中,time最小值来自第一个库,time_min=1487501123,这个过程只需要比较各个分库第一条数据,时间复杂度很低。

步骤三:查询二次改写

第一次改写的sql语句是select*from t order by time offset 333 limit 5。第二次要改写成一个between语句,between的起点是time_min,between的终点是原来每个分库各自返回数据的最大值:

第一个分库,第一次返回数据的最大值是1487501523;所以查询改写为select*from t order by time where time between time_min and 1487501523。

第二个分库,第一次返回数据的最大值是1487501323;所以查询改写为select*from t order by time where time between time_min and 1487501323。

第三个分库,第一次返回数据的最大值是1487501553;所以查询改写为select*from t order by time where time between time_min and 1487501553。

相对第一次查询,第二次查询条件放宽了,故第二次查询会返回比第一次查询结果集更多的数据,假设这三个分库返回的数据(time, uid)如下:

可以看到:
  • 由于time_min来自原来的分库一,所以分库一的返回结果集和第一次查询相同(所以其实这次查询是可以省略的);
  • 分库二的结果集,比第一次多返回了1条数据,头部的1条记录(time最小的记录)是新的(上图中粉色记录);
  • 分库三的结果集,比第一次多返回了2条数据,头部的2条记录(time最小的2条记录)是新的(上图中粉色记录)。
步骤四:在每个结果集中虚拟一个time_min记录,找到time_min在全局的offset

  • 在第一个库中,time_min在第一个库的offset是333;
  • 在第二个库中,(1487501133, uid_aa)的offset是333(根据第一次查询条件得出的),故虚拟time_min在第二个库的offset是331;
  • 在第三个库中,(1487501143, uid_aaa)的offset是333(根据第一次查询条件得出的),故虚拟time_min在第三个库的offset是330。
综上,time_min在全局的offset是333 331 330=994。

步骤五:既然得到了time_min在全局的offset,就相当于有了全局视野,根据第二次的结果集,就能够得到全局offset 1000 limit 5的记录

第二次查询在各个分库返回的结果集是有序的,又知道了time_min在全局的offset是994,一路排下来,容易知道全局offset 1000 limit 5的一页记录(上图中黄色记录)。

是不是非常巧妙?这种方法的优点是:可以精确的返回业务所需数据,每次返回的数据量都非常小,不会随着翻页增加数据的返回量。

不足是:需要进行两次数据库查询。

五、总结

今天分享了解决“夸n库分页”这一技术难题的四种方法,稍作总结:

方法一:全局视野法
  • 将order by time offset x limit y,改写成order by time offset 0 limit x y。
  • 服务层对得到的n*(x y)条数据进行内存排序,内存排序后再取偏移量x后的y条记录。

方法二:业务折衷法-禁止跳页查询
  • 用正常的方法取得第一页数据,并得到第一页记录的time_max。
  • 每次翻页,将order by time offset x limit y,改写成order by time where time>$time_max limit y以保证每次只返回一页数据,性能为常量。
方法三:业务折衷法-允许模糊数据

将order by time offset x limit y,改写成order by time offset x/n limit y/n。

方法四:二次查询法
  • 将order by time offset x limit y,改写成order by time offset x/n limit y;
  • 找到最小值time_min;
  • between二次查询,order by time between $$time_min and $time_i_max;
  • 设置虚拟time_min,找到time_min在各个分库的offset,从而得到time_min在全局的offset;
  • 得到了time_min在全局的offset,自然得到了全局的offset x limit y。
  • 大小: 15.4 kb
  • 大小: 28.1 kb
  • 大小: 21.7 kb
  • 大小: 20.9 kb
  • 大小: 22 kb
  • 大小: 20.1 kb
  • 大小: 19.6 kb
  • 大小: 7 kb
  • 大小: 22 kb
  • 大小: 21.9 kb
  • 大小: 20.8 kb
  • 大小: 27.7 kb
  • 大小: 29.6 kb
  • 大小: 40 kb
  • 大小: 62.5 kb
  • 大小: 55.9 kb
来自:
1
0
评论 共 3 条 请登录后发表评论
3 楼 2017-11-30 10:30
二次查询有问题,考虑极端情况,分页数据全在第一个库,其他两个库的时间都比第个库大
,数据顺序库a 库b 库c,从偏移1/3处取到的完全不是需要的是数据
2 楼 2017-11-22 08:54
现在不是有分布式数据库吗?比如mycat
1 楼 2017-11-21 14:33
我想了解一下这个业务场景是否有问题?或者说分库的模式是否有问题?
分库是不是根据用户id来分的,如果按照用户id来分的,将id相关的数据都集中于一个库中,这种问题是否就不会存在?

1、以京东为例:按照账号分库,将一个账号的订单分到一个库中
2、最近注册的第三页用户?这个查询和统计肯定是京东内部管理的需求,终端用户不会有此需求,京东的账号规模也就几亿,可以统一归集到elastic检索引擎中,就可以避免跨库查询。


我个人觉得从c端来说没有这个场景,从企业内部数据分析来说,我估计肯定是要归集的。或者说统一存放于hadoop或spark平台上,利用大数据来统计的。

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • id 主键如何处理基于数据库的实现方案uuid获取系统当前时间snowflake 算法设计高并发系统数据库层面的设计,分库和分表,有些系统可能光分库不分表,也可能是光分表不分库,视需求而定。分表分表就是把一个表的数据...

  • 微服务架构是一项在云中部署应用和服务的新技术。大部分围绕微服务的争论都集中在容器或其他技术是否能很好的实施微服务,而红帽说api应该是重点。微服务可以在“自己的程序”中运行,并通过“轻量级设备与http型api...

  • 本文主要介绍vivo商城库存系统发展历程、架构设计思路以及应对业务场景的实践。

  • 业务数据从原来的单库单表模式变成了数据被拆分到多个数据库,甚至多个表中,如果在数据访问层做一下功能的封装和管控,所有分库分表的逻辑和数据的跨库操作都交给应用的开发人员来实现,则对开发人员的要

  • 最近经常被问到分库分表与分布式数据库如何选择,网上也有很多关于中间件 传统关系数据库(分库分表)与newsql分布式数据库的文章,但有些观点与判断是我觉得是偏激的,脱离环境去评价方案好坏其实有失公允。...

  • 这篇文章主要聊如何规划和设计分库分表,以及要考虑哪些问题。

  • 互联网架构,很多时候面临这样的需求: 几千万的数据表结构变更。 水平拆分成3库,要进一步拆分成5库。 底层存储切换,mongodb 换成 mysql。 种种需求,都需要进行数据迁移,如何平滑迁移数据,迁移过程不停机,...

  • 伴随着互联网技术的发展和新媒体创新应用,人们越来越倾向于通过微博、微信、短视频等社交媒体,表达看法,传播诉求,分享信息、甚至建言献策,收集、处理、挖掘其中的价值,洞察观点、情绪、口碑、社情民意,不仅...

  • mysql 是最流行的关系型数据库管理系统(rdbms)之一。无论是在小型企业内部构建应用,还是在大型互联网公司运营数据中心,都依赖于 mysql 的强大功能和稳定性。本文档旨在通过详细地阐述 mysql 在实际生产环境中的...

  • 数据库架构的演变 在业务数据量比较少的时代,我们使用单机数据库就能满足业务使用,随着业务请求量越来越多,数据库中的数据量快速增加,这时单机数据库已经不能满足业务的性能要求,数据库主从复制架构随之...

  • 第6章 数据库 6.1 范式与反范式 数据库范式要求: 第一范式: 每个字段都是原子的,不能再分解。 第二范式: 1.表必有主键,主键可以是单个属性或者几个属性的组合。 2.非主属性必须完全依赖,而不...

  • 10 微服务落地的技术实践 如今,做一个优秀的程序员越来越难。激烈的市场竞争、互联网快速的迭代、软件系统规模化发展,无疑都大大增加了软件设计的难度。因此,对于架构师的能力要求也越来越高,就像我的一本书里...

  • 本文将介绍数据库架构设计中的一些基本概念,常见问题以及对应欧洲杯足彩官网的解决方案,为了便于读者理解,将以“用户中心”为例,讲解数据库架构设计的常见玩法。01用户中心用户中心是一个非常常...

  • 基于中间件 分库分表模式架构简单,技术门槛更低,虽然没有newsql数据库功能全面,但大部分场景最核心的诉求也就是拆分后sql的正确路由,而此功能中间件模式应对还是绰绰有余的,可以说在大多数oltp场景是够用的。...

  • 电商行业是互联网公司的主要业务之一,其带来的商机无处不在,创新驱动着各个领域的发展。随着互联网及移动支付平台的飞速发展,电商行业也在加速发展,尤其是智能化和自动化程度越来越高的时代,迎接这种变化的是...

  • 数据库架构的演变在业务数据量比较少的时代,我们使用单机数据库就能满足业务使用,随着业务请求量越来越多,数据库中的数据量快速增加,这时单机数据库已经不能满足业务的性能要求,数据库主从复制架构随之应运而生...

  • python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。python社区提供了大量的第三方库,如numpy、pandas和requests,极大地丰富了python的应用领域,从数据科学到web开发。python库的丰富性是python成为最受欢迎的编程语言之一的关键原因之一。这些库不仅为初学者提供了快速入门的途径,而且为经验丰富的开发者提供了强大的工具,以高效率、高质量地完成复杂任务。例如,matplotlib和seaborn库在数据可视化领域内非常受欢迎,它们提供了广泛的工具和技术,可以创建高度定制化的图表和图形,帮助数据科学家和分析师在数据探索和结果展示中更有效地传达信息。

  • stm32单片机fpga毕设电路原理论文报告基于ide硬盘的数字图像存储技术研究本资源系百度网盘分享地址

  • 适合rust入门。深入浅出,事无巨细,远胜市面上所有入门书。而且是免费的

global site tag (gtag.js) - google analytics
网站地图