数据库分库分表和带来的唯一ID、分页查询问题的解决
需求缘起(用一个公司的发展作为背景)
1.还是个小公司的时候,注册用户就20w,每天活跃用户1w,每天最大单表数据量就1000,然后高峰期每秒并发请求最多就10,此时一个16核32G的服务器,每秒请求支撑在2000左右,负载合理,没有太大压力,基本没有宕机风险。
2.当注册用户达到2000W,每天活跃用户数100W,每天单表新增数据量达到50W条,高峰期请求量达到1W。经过一段时间的运行,单标数据量会越来越多,带来的问题
2.1 数据库服务器的IO,网络宽带,CPU负载,内存消耗都会达到非常高,服务器已经不堪重负
2.2 高峰时期,单表数据量本来就很大,加上数据库服务器负载太高,导致性能下降,此时SQL的性能就更差了,用户体验贼差, 点一个按钮要很久才有响应,如果服务器的配置再低一点的话,数据库可能直接宕机
3. 实现一个基本的分库分表的思路,将一台数据库服务器变成5台数据库,就能有5个库,5个表,这样可以将表中的数据按照ID分别通过同一个映射方法,分布到这5个库中。此时写入数据的时候,需要借助数据库中间件,比如shardng-jdbc或者Mycat。查询的时候先通过一步映射到具体的数据库,再进行查询。
4. 当用户量再次增长时,只能继续分表,比如将一张表拆分成1024张表,这样在操作数据的时候,需要两次路由,一次找到在哪个数据库,一次找到在哪张表。
5. 除了分表,数据库还可以做主从架构,主服务器用以写入,从服务器用以查询,根据业务需求具体实现即可。
分库分表带来的问题
1. 分库分表之后一个必然的问题,如何获取一个全局为一个ID?因为表中的数据是通过ID路由映射的,ID不能重复。
2. 就算有了全局唯一的ID,那面对分页查询的需求,应该怎么处理呢?
唯一ID的生成
下面列举几种常见的唯一ID生成方案,需要满足两大核心需求:1.全局唯一 2趋势有序
1. 用数据库的auto_increment(自增ID)来生成,每次通过写入数据库一条记录,利用数据库ID自增的特性获取唯一,有序的ID。
优点:使用数据库原有的功能,相对简单;能够保证唯一;能够保证递增性;ID之间的步长是固定且可以自定义的
缺点:可用性难以保证,当生成ID的那台服务器宕机,系统就玩不转了;由于写入是单点的,所以扩展性差,性能上限取决于数据库的写性能。
2. 用UUID
优点:简单方便;全球唯一,在遇见数据迁移、合并或者变更时可以从容应对;
缺点:没有递增性;UUID是很长的字符串,作为主键对存储空间有一定要求,查询效率也较低。
3. 使用Redis生成ID,主要利用Redis是单线程的,所以也可以用来生成唯一ID。当使用的是Redis集群的时候,比如集群中有5台Redis,初始化每台Redis的值为1,2,3,4,5,设置步长为5,并且确定一个不随机的负载均衡策略,能够保证有序,唯一。
优点:不依赖数据库,灵活,且性能相对于数据库有一定提高;使用Redis集群策略还能排除单点故障问题;ID天然有序
缺点:如果系统中没有Redis,还需要引入新的组件;编码和配置工作量大
4. 使用Twitter的snowflake算法;其核心思想是一个64位long型ID,使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。可以根据自身需求进行一定的修改。
优点:不依赖数据库,灵活方便,性能优于数据库;ID按照时间在单机上是递增的
缺点:单机上递增,但是当分布式环境下每台机器的时钟不可能完全同步,有时并不能做做全局递增。
5. 使用zookeeper生成唯一ID,主要通过znode数据版本来生成序列号,可以生成32为和64为的数据版本号。很少使用,因为是多步调用API,并发情况下还需要考虑分布式锁,不是很理想。
6. MongoDB的ObjectID,和snowflake算法类似。4字节Unix时间戳,3字节机器编码,2字节进程编码,3字节随机数
分库分表下的分页查询
假设有一张用户表,经过分库分表之后,现在均匀分布在2台服务器实例上。业务需要查询“最近注册的第3页用户”,虽然数据库有分库用的全局的ID,但是没有排序条件time的全局视野,此时应该怎么做呢?
1. 全局视野法:因为不清楚按照时间排序之后的第三页数据到底是如何分布在数据库上的,所以必须每个库都返回3页数据,所得到的6页数据在服务层进行内存排序,得到全局视野,再取第3页数据。
优点:通过服务层修改,扩大数据查询量,得到全局视野,业务无损,精确
缺点(显而易见):每个分库都需要返回更多的数据,增大网络传输量;除了数据库要按照time排序,服务层也需要二次排序,损耗性能;随着页码的增大,性能极具下降,数据量和排序量都将大增,性能平方级下降。
2. 业务折中
2.1 禁止跳页查询,不提供“直接跳到指定页面”的功能,只提供下一页的功能。极大的降低技术方案的复杂度。第一页的选取方法和全局视野法一样,但是点击下一页时:
2.1.1先找到上一页的time的最大值,作为第二页数据拉去的查询条件,只取每页的记录数,
2.2.2这样服务层还是获得两页数据,再做一次排序,获取一页数据。
2.2.3改进了不会因为页码增大而导致数据的传输量和排序量增大
3. 允许数据精度丢失:需要考虑业务员上是否接受在页码较大是返回的数据不是精准的数据。
3.1在数据量较大,且ID映射分布足够随机的话,应该是满足等概率分布的情况的,所以取一页数据,我们在每个数据库中取前半页。
3.2当然这样的到的结果并不是精准的,但是当实际业务可以接受的话, 此时的技术方案的复杂度变大大降低。也不需要服务层内存排序了。
4. 二次查询法:既满足业务的精确需求,也无需业务折中。现在假设每页显示10条数据,要查第三页,数据分了两个库。 正常的语句是 select * from table order by time offset 20 limit 10,取偏移20个之后的10个
4.1首次查询查询每个库的select * from table order by time offset 10 limit 10;得到10条数据。这里的offset是总offset/分库数
4.2 服务层得到来自两个分库的结果集,得到最小的time,也就是最顶层的time,这个time满足最少有10条记录在它前面,然后分别记录每个库的最大time
4.3 分别再次查询最小time->每个库上一次的最大time的数据,得到每个库的查询结果
4.4 在每个集合的最小time都是相同的,所以可以得到该最小time在整个数据库中的offset,加起来就是这个最小time在全局库的offset位置。
4.5 再将第二次查询的结果集拼起来和得到的最小time的offset,推导出 offset 20 limit 10的一页记录。
优点:可以精确得到业务数据,且每次返回的数据量都非常小,不会随着页码增加而数据量增大。
缺点:需要进行两次数据库查询
数据库分库分表和带来的唯一ID、分页查询问题的解决的更多相关文章
- 数据库分库分表(sharding)系列【转】
原文地址:http://www.uml.org.cn/sjjm/201211212.asp数据库分库分表(sharding)系列 目录; (一) 拆分实施策略和示例演示 (二) 全局主键生成策略 (三 ...
- 数据库分库分表(sharding)系列
数据库分库分表(sharding)系列 目录; (一) 拆分实施策略和示例演示 (二) 全局主键生成策略 (三) 关于使用框架还是自主开发以及sharding实现层面的考量 (四) 多数据源的 ...
- 【转】mysql分库分表,数据库分库分表思路
原文:https://www.cnblogs.com/butterfly100/p/9034281.html 同类参考:[转]数据库的分库分表基本思想 数据库分库分表思路 一. 数据切分 关系型数 ...
- php面试专题---mysql数据库分库分表
php面试专题---mysql数据库分库分表 一.总结 一句话总结: 通过数据切分技术将一个大的MySQLServer切分成多个小的MySQLServer,既攻克了写入性能瓶颈问题,同一时候也再一次提 ...
- 转数据库分库分表(sharding)系列(二) 全局主键生成策略
本文将主要介绍一些常见的全局主键生成策略,然后重点介绍flickr使用的一种非常优秀的全局主键生成方案.关于分库分表(sharding)的拆分策略和实施细则,请参考该系列的前一篇文章:数据库分库分表( ...
- 数据库分库分表(sharding)系列(二) 全局主键生成策略
本文将主要介绍一些常见的全局主键生成策略,然后重点介绍flickr使用的一种非常优秀的全局主键生成方案.关于分库分表(sharding)的拆分策略和实施细则,请参考该系列的前一篇文章:数据库分库分表( ...
- Mysql系列四:数据库分库分表基础理论
一.数据处理分类 1. 海量数据处理,按照使用场景主要分为两种类型: 联机事务处理(OLTP) 面向交易的处理系统,其基本特征是原始数据可以立即传送到计算机中心进行处理,并在很短的时间内给出处理结果. ...
- 数据库分库分表(sharding)系列(一) 拆分规则
第一部分:实施策略 数据库分库分表(sharding)实施策略图解 1. 垂直切分垂直切分的依据原则是:将业务紧密,表间关联密切的表划分在一起,例如同一模块的表.结合已经准备好的数据库ER图或领域模型 ...
- 转数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示
本文原文连接: http://blog.csdn.net/bluishglc/article/details/7696085 ,转载请注明出处!本文着重介绍sharding切分策略,如果你对数据库sh ...
随机推荐
- 【java错误】System.out.println()出错
今天想测试java的System的类,没想到居然出错了.在同一个包下的java文件System全错,而其他包中的System没错.上网查了下资料,原来我是重定义了System类,覆盖了原来的Syste ...
- 如何将钉钉集成到FineReport插件中
报表服务器 安装钉钉管理插件后,打开报表管理平台,管理系统下会增加钉钉管理节点,钉钉相关的配置管理都将会放在这个节点中去配置: 同时,设置定时任务的最后一步输出设置中,会增加推送钉钉消息: 钉钉企业应 ...
- flutter 生命周期
前言:生命周期是一个组件加载到卸载的整个周期,熟悉生命周期可以让我们在合适的时机做该做的事情, flutter中的State生命周期和android以及React Native的生命周期类似. 先看一 ...
- button的OnClickListener的三种实现方法
onclick事件的定义方法,分为三种,分别为在xml中进行指定方法:在Actitivy中new出一个OnClickListenner():实现OnClickListener接口三种方式. 代码分别如 ...
- String path = request.getContextPath
<% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+ ...
- 使用DataTables插件与后台对接表格
function getResults(){ var callResults = $.ajax({ url: "....", //接口url type: "GET&quo ...
- Sql Server tempdb原理-启动过程解析实践
我们知道在SqlServer实例启动过程中数据库会进行还原(Redo,Undo)然后打开提供服务,但我们知道tempdb是不提供重做机制的(Redo)那tempdb是如何还原的呢?如果tempdb损坏 ...
- JavaScript DOM 編程藝術(2版) 綜合實例Band js代碼
function addLoadEvent(func){ var oldonload=window.onload; if(typeof window.onload!='function') { win ...
- Linux Transparent Huge Pages 对 Oracle 的影响
1 Transparent Huge Pages 说明 官网上有2篇文章对THP 做了说明: https://access.redhat.com/solutions/46111 https://acc ...
- VMware 11 安装苹果系统
没事研究了一下虚拟机安装苹果系统 1.下载需要的软件- F, c1 X: e- o1 }& V/ o9 J 1.1 VMware 11 下载和安装* P( R; O6 v1 N! ...