数据库扩容方案
参考:
https://m.w3cschool.cn/architectroad/architectroad-database-smooth-expansion.html
https://www.cnblogs.com/kafeixiaoluo/p/9156855.html
https://developer.aliyun.com/article/578120
数据库秒级平滑扩容架构方案
一、缘起
(1)并发量大,流量大的互联网架构,一般来说,数据库上层都有一个服务层,服务层记录了“业务库名”与“数据库实例”的映射关系,通过数据库连接池向数据库路由sql语句以执行:
如上图:服务层配置用户库user对应的数据库实例物理位置为ip(其实是一个内网域名)。
(2)随着数据量的增大,数据要进行水平切分,分库后将数据分布到不同的数据库实例(甚至物理机器)上,以达到降低数据量,增强性能的扩容目的:
如上图:用户库user分布在两个实例上,ip0和ip1,服务层通过用户标识uid取模的方式进行寻库路由,模2余0的访问ip0上的user库,模2余1的访问ip1上的user库。
关于数据库水平切分,垂直切分的更多细节,详见《一分钟掌握数据库垂直拆分》。
(3)互联网架构需要保证数据库高可用,常见的一种方式,使用双主同步+keepalived+虚ip的方式保证数据库的可用性:
如上图:两个相互同步的主库使用相同的虚ip。
如上图:当主库挂掉的时候,虚ip自动漂移到另一个主库,整个过程对调用方透明,通过这种方式保证数据库的高可用。
关于高可用的更多细节,详见《究竟啥才是互联网架构“高可用”》。
(4)综合上文的(2)和(3),线上实际的架构,既有水平切分,又有高可用保证,所以实际的数据库架构是这样的:
提问:如果数据量持续增大,分2个库性能扛不住了,该怎么办呢?
回答:继续水平拆分,拆成更多的库,降低单库数据量,增加库主库实例(机器)数量,提高性能。
最终问题抛出:分成x个库后,随着数据量的增加,要增加到y个库,数据库扩容的过程中,能否平滑,持续对外提供服务,保证服务的可用性,是本文要讨论的问题。
二、停服务方案
在讨论平滑方案之前,先简要说明下“x库拆y库”停服务的方案:
(1)站点挂一个公告“为了为广大用户提供更好的服务,本站点/游戏将在今晚00:00-2:00之间升级,届时将不能登录,用户周知”
(2)停服务
(3)新建y个库,做好高可用
(4)数据迁移,重新分布,写一个数据迁移程序,从x个库里导入到y个库里,路由规则由%x升级为%y
(5)修改服务配置,原来x行配置升级为y行
(6)重启服务,连接新库重新对外提供服务
整个过程中,最耗时的是第四步数据迁移。
回滚方案:
如果数据迁移失败,或者迁移后测试失败,则将配置改回x库,恢复服务,改天再挂公告。
方案优点:简单
方案缺点:
(1)停服务,不高可用
(2)技术同学压力大,所有工作要在规定时间内做完,根据经验,压力越大约容易出错(这一点很致命)
(3)如果有问题第一时间没检查出来,启动了服务,运行一段时间后再发现有问题,难以回滚,需要回档,可能会丢失一部分数据
适用:
- 小型网站;
- 大部分游戏;
- 对高可用要求不高的服务。
有没有更平滑的方案呢?
三、秒级、平滑、帅气方案
再次看一眼扩容前的架构,分两个库,假设每个库1亿数据量,如何平滑扩容,增加实例数,降低单库数据量呢?三个简单步骤搞定。
(1)修改配置
主要修改两处:
a)数据库实例所在的机器做双虚ip,原来%2=0的库是虚ip0,现在增加一个虚ip00,%2=1的另一个库同理
b)修改服务的配置(不管是在配置文件里,还是在配置中心),将2个库的数据库配置,改为4个库的数据库配置,修改的时候要注意旧库与新库的映射关系:
%2=0的库,会变为%4=0与%4=2;
%2=1的部分,会变为%4=1与%4=3;
这样修改是为了保证,拆分后依然能够路由到正确的数据。
(2)reload配置,实例扩容
服务层reload配置,reload可能是这么几种方式:
a)比较原始的,重启服务,读新的配置文件
b)高级一点的,配置中心给服务发信号,重读配置文件,重新初始化数据库连接池
不管哪种方式,reload之后,数据库的实例扩容就完成了,原来是2个数据库实例提供服务,现在变为4个数据库实例提供服务,这个过程一般可以在秒级完成。
整个过程可以逐步重启,对服务的正确性和可用性完全没有影响:
a)即使%2寻库和%4寻库同时存在,也不影响数据的正确性,因为此时仍然是双主数据同步的
b)服务reload之前是不对外提供服务的,冗余的服务能够保证高可用
完成了实例的扩展,会发现每个数据库的数据量依然没有下降,所以第三个步骤还要做一些收尾工作。
(3)收尾工作,数据收缩
有这些一些收尾工作:
a)把双虚ip修改回单虚ip
b)解除旧的双主同步,让成对库的数据不再同步增加
c)增加新的双主同步,保证高可用
d)删除掉冗余数据,例如:ip0里%4=2的数据全部干掉,只为%4=0的数据提供服务啦
这样下来,每个库的数据量就降为原来的一半,数据收缩完成。
优点
- 扩容期间,服务照常进行,保证高可用;
- 时间长,项目组压力没有这么大,出错率低;
- 扩容期间,遇到什么问题,都可以随时调试,不怕影响线上服务;
- 每个数据库少了一半的数据量。
缺点
- 程序复杂,需要配置双主、主主双写、检测数据同步等额外技术;
- 但后期数据库成千上万台的时候,扩容复杂(情况非常少,除非将很多业务数据放在同一个数据库)。
适用:
- 大型网站;
- 对高可用要求高的服务。
四、总结
该帅气方案能够实现n库扩2n库的秒级、平滑扩容,增加数据库服务能力,降低单库一半的数据量,其核心原理是:成倍扩容,避免数据迁移。【4扩8也同样适用】
迁移步骤:
(1)修改配置
(2)reload配置,实例扩容完成
(3)删除冗余数据等收尾工作,数据量收缩完成
- 停机扩容:简单,不高可用,易出错,扩容后不能回滚,只能回档,会丢失一部分数据。
- 平滑扩容:复杂,高可用,出错调试容易,易回滚,不会造成数据丢失
数据库优化和扩容(图文详解)
一、数据库瓶颈
不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)。
IO瓶颈
第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表。
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库。
CPU瓶颈
第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表。
二、分库分表
水平分库
概念:以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。
结果:
- 每个库的结构都一样;
- 每个库的数据都不一样,没有交集;
- 所有库的并集是全量数据;
场景:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。
分析:库多了,io和cpu的压力自然可以成倍缓解。
水平分表
概念:以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。
结果:
- 每个表的结构都一样;
- 每个表的数据都不一样,没有交集;
- 所有表的并集是全量数据;
场景:系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。
分析:表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担。
垂直分库
概念:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。
结果:
- 每个库的结构都不一样;
- 每个库的数据也不一样,没有交集;
- 所有库的并集是全量数据;
场景:系统绝对并发量上来了,并且可以抽象出单独的业务模块。
分析:到这一步,基本上就可以服务化了。例如,随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化。
垂直分表
概念:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
结果:
- 每个表的结构都不一样;
- 每个表的数据也不一样,一般来说,每个表的字段至少有一列交集,一般是主键,用于关联数据;
- 所有表的并集是全量数据;
场景:系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
分析:可以用列表页和详情页来帮助理解。垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。但记住,千万别用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。
分库分表工具
sharding-sphere:jar,前身是sharding-jdbc;
TDDL:jar,Taobao Distribute Data Layer;
Mycat:中间件。
注:工具的利弊,请自行调研,官网和社区优先。
分库分表步骤
根据容量(当前容量和增长量)评估分库或分表个数 -> 选key(均匀)-> 分表规则(hash或range等)-> 执行(一般双写)-> 扩容问题(尽量减少数据的移动)。
分库分表问题
非partition key的查询问题
基于水平分库分表,拆分策略为常用的hash法。
端上除了partition key只有一个非partition key作为条件查询
映射法
基因法
注:写入时,基因法生成user_id,如图。关于xbit基因,例如要分8张表,23=8,故x取3,即3bit基因。根据user_id查询时可直接取模路由到对应的分库或分表。根据user_name查询时,先通过user_name_code生成函数生成user_name_code再对其取模路由到对应的分库或分表。id生成常用snowflake算法。
端上除了partition key不止一个非partition key作为条件查询
映射法
冗余法
注:按照order_id或buyer_id查询时路由到db_o_buyer库中,按照seller_id查询时路由到db_o_seller库中。感觉有点本末倒置!有其他好的办法吗?改变技术栈呢?
后台除了partition key还有各种非partition key组合条件查询
NoSQL法
冗余法
非partition key跨库跨表分页查询问题
基于水平分库分表,拆分策略为常用的hash法。
注:用NoSQL法解决(ES等)。
扩容问题
基于水平分库分表,拆分策略为常用的hash法。
水平扩容库(升级从库法)
注:扩容是成倍的
水平扩容表(双写迁移法)
第一步:(同步双写)修改应用配置和代码,加上双写,部署;
第二步:(同步双写)将老库中的老数据复制到新库中;
第三步:(同步双写)以老库为准校对新库中的老数据;
第四步:(同步双写)修改应用配置和代码,去掉双写,部署;
注:双写是通用方案。
分库分表总结
- 分库分表,首先得知道瓶颈在哪里,然后才能合理地拆分(分库还是分表?水平还是垂直?分几个?)。且不可为了分库分表而拆分。
- 选key很重要,既要考虑到拆分均匀,也要考虑到非partition key的查询。
- 只要能满足需求,拆分规则越简单越好。
--转载自:不用找了,大厂在用的分库分表方案,都在这里! - 潇湘夜雨的文章 - 知乎 https://zhuanlan.zhihu.com/p/143072827不用找了,大厂在用的分库分表方案,都在这里! - 潇湘夜雨的文章 - 知乎 https://zhuanlan.zhihu.com/p/143072827https://zhuanlan.zhihu.com/p/143072827
不用找了,大厂在用的分库分表方案,都在这里! - 潇湘夜雨的文章 - 知乎 https://zhuanlan.zhihu.com/p/143072827
不用找了,大厂在用的分库分表方案,都在这里! - 潇湘夜雨的文章 - 知乎 https://zhuanlan.zhihu.com/p/143072827
MySQL数据库水平扩容方案
一、 背景
随着云技术的不断发展,存储资源和计算资源的成熟,成本不断下降,使得企业开发部署提供服务更加的便捷。得益于此,对于高速发展的中小型企业,可以通过不断地堆砌机器,增加应用集群来应对不断增长的流量。
但是随着企业的不断发展,其中一个瓶颈并不能简单地通过堆砌机器来解决,这便是越来越庞大的数据库带来的性能天花。而应对这个天花板的有效手段之一,就是通过对数据库的分库分表,使得单表的数据量低于500W。
今天我们讨论一个更长远一点的问题。就是当企业进入了高速增长的赛道后,即便是对数据分库分表后,无论是数据库的容量,还是单库单表的数据量也总会到达天花板,此时该如何扩展我们的数据库性能。
二、 基础知识
1、分库分表
分库分表这个概念十分好理解,就是原来存储在一个数据表内的数据,通过某种规则平均的分散在多个数据库的多张同样结构的数据表中。
我们假定以用户表(t_user)来举例子,假设当前这个表的数据量已经到达了2kw,相对500w这个临界值来说足足超过了4倍。那么我们如何通过分库分表来调整该表?
正如上面所说到的,我们可以对数据表其中一个或多个字段,通过某个可计算的平均的每次计算结果绝对一致的算法来分散存储我们的数据。可以很容易的想到很多种算法,比如对id进行mod运算,对创建时间按月分成多个表等等。
图1.分库分表示意图
如上图所展示的,表通过函数fx的计算后倍分散到不同数据库的不同表中。这里可以简单代入一种分库算法——id取膜。
上图中一共分了三个数据库,每个库中又分了两张数据表。那么id % 3 == 0的数据都会集中到分库1中,分库1中的数据id % 2 == 0 的数据,又会存储在第一个分表中,其他如此类推。
三、 水平扩展
首先来思考一下,水平扩展有什么技术难度?
图2.水平扩容示意图
第一个显而易见的问题就是规则的变化和数据的迁移。
如果控制分库分表的规则是通过应用程序内完成的,规则的变化意味着必须重新发布使用新规则的应用集群。而数据迁移带来的麻烦则更加严重,在数据没有完成迁移之前,需要编写专门的脚本来处理数据的导出导入,不同的业务不同的表关系,都会使得这个脚本变得极其的复杂,而且还要同时兼顾增量数据同步,时间点,数据一致性等问题,稍有不慎,便会对用户的数据造成影响。
思考,是否有一种分库规则,在扩展分库的时候不需要进行规则的变化和数据的迁移呢?
答案当然是有的,就是将分库的规则修改为按段分库。如下图所示,如果我们分三个库,每个库中有两张分表(分表规则还是按取膜运算),那么一共可以存储3kw的数据,其中分库规则为id的值在[1,1kw]的会被存储到0库上,在[1kw+1, 2kw]范围的会被存储到1库,在[2kw+1, 3kw]范围的会被存储到2库。当我们的数据量突破3kw时,我们只需要增加一个分库,用于存储[3kw+1, 4kw]范围的数据即可,完全不需要对前三个分库的数据做处理。同理,也可以基于时间的分库方式。
图3.避免数据迁移和规则更新的分库示例图
这种分库的规则优势可以说是非常的明显了,但是这种分库规则会带来什么样的劣势呢?
可以想象,我们为什么要做分库分表?就是单库的性能已经不能满足我们日常的业务需求了,需要将单个数据库的性能压力分摊到多个数据库上。而上述这种分库方式,势必会导致insert/update压力都集中到一个数据库实例上,并不能很好得分摊性能压力。
那么现在第三个问题来了,是否还有什么分库规划方式,既能避免数据迁移的成本,又能解决单库性能热点问题的方案呢?
答案肯定也是有的,下面我们来介绍一下阿里云TDDL团队给出的几种水平扩展模式。
图4.水平扩展模式1
第一种水平扩展的模式如上图,在我们只有一个分库的时候,可以通过设定4个分表,示例中使用简单的id取膜分表方式。当单库的容量已经达到上限,我们可以通过增加一个数据库实例,把分表2,3整体迁移到新的实例上。这样做的好处是,只需把整表迁移到新的库中即可,无需考虑单条数据因为规则的变化而重新计算需要迁移到那个库。当两个库也不够用是,以此类推,增加两个分库,分别吧table1和table3迁移到新的分库即可。
上述方案有一个缺点,就是在从一个库到4个库的过程中,单表的数据量一直在增长。当单表的数据量超过一定范围时,可能会带来性能问题。另外当开始预留的分表个数用尽,到了4物理库每库1个表的阶段,再进行扩容的话,不可避免的要再次从表上下手。
为了解决模式1的问题,我们接下来看看模式是如何处理的:
图5.水平扩展模式2
模式2与模式1有类似之处,在扩展阶段,还是选择整表迁移的方式,为了简化说明,此处使用两个分表来做说明。
如上图5,扩展了分库,把table1整表迁移到新库中后,如果此时单边已经快接近500w,我们可以在每个分库中再创建一个分表,用于存放超过500w部分的数据。此时分库分表的规则就变为:
通过id % 2确定分库,然后通过id段[1+0.5kw, 1kw]的数据分表存放在table_0_1和table_1_1中。这样既满足的降低单表500w水平线值,也解决了热点数据库的问题。
如果随着时间的流逝,我们的数据库容量需要再次升级,也只需要重新购买两个实例,把table_0_1和table_1_1分别迁移到新的实例上即可,同理也可以通过为每个分库建立新分表来解决500w问题。
以上都是倍数增长的扩容方案,对于中小型的企业来说,数据库资源的开销很是很大的。用2实例到4实例的费用就增长了一倍,而从4实例到8实例又增长了一倍。那么非倍数扩容的方案是如何的呢?
其实原理是相通的,譬如我们从2实例扩容到3实例时,此时我们table_0和table_1很大可能已经饱和了(单表达到500w),我们可以新购一个实例,用于存放这两个“历史数据”表,另外两个则按照模式2进行扩展,这样单库热点问题还是平均到两个库上。当然,我们也可以通过给每个库增加一个分表,来达到每个分库都承担1/3的压力。只不过这种模式,对于分库分表的规则就提高和很大的复杂度。
图6.水平扩展模式3
四、 结语
一个好的设计往往可以为后期的升级维护带来便利,数据库的水平扩容是一个很大的技术难点,但是通过优化我们的分库分表策,还是可以在一定程度上减轻工作量。这个准则无论是放到代码编写,产品设计或是生活的方方面面都一样适用,所以当我们遇到一个难以实现的设计时,也需要反思这种设计是否合理,是否会有更优的方案?