为什么需要分库分表
请求数太高
在高并发情况下,大量读写请求落入数据库处理,最终会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service层来看就是,可用数据库连接锐减甚至无连接可用,接下来面临的就是并发量急剧增加、吞吐量严重下降、连接出现异常、数据库时常宕机、系统经常崩溃一系列后患问题。
数据查询慢
- 单表或单库数据量过大,导致数据检索的效率直线降低。
- 单库整体并发连接数接近系统阈值,从而导致此请求获取不到连接数,一直处于等待获取连接的状态。
- 已经获取但由于并发过高导致CPU被打满,就算SQL所查询的表数据行很少,也同样因为没有CPU资源无法执行,所以一直处于阻塞状态,最终出现查询过慢的现象。
数据量太大
当一个库的数据存储量太大时,就算每张表的并发数不多,但是因为是海量数据,单库中存在大量的数据表,每张表都有一部分并发请求,导致最终单库的连接数阈值成为数据库的瓶颈。
当一张表数据太多时,导致单表查询速度严重下降,虽然InnoDB存储引擎的表允许的最大行数为10亿,但是如果一张表的数据行记录达到上亿级别,那就算通过索引去查询一条数据,它也需要至少经过上十次到几十次磁盘IO,从而导致单表查询速度直线下降;一般一张表的数据行数在800~1200W左右最合适。
再聊为何需要分库分表
其实不管是并发过高、或访问变慢、亦或数据量过大,本质上都属于数据库遭遇到了瓶颈,但只不过根据情况不同,分为不同类型的数据库瓶颈,但是最终对于客户端而言,就是数据库不可用了或者变慢了。
而导致数据库出现此类问题的原因,实则就是随着业务的发展,系统的数据不断增多、用户量不断增长、并发量不断变大,因此对于数据再进行CRUD操作的开销也会越来越大,再加上物理服务器的CPU、磁盘、内存、IO等资源有限,最终也会限制数据库所能承载的最大数据量、数据处理能力。
当出现上述这类问题,并且无法通过升级硬件、版本、调优等手段解决时,或者只能临时解决,却无法保障未来业务增长的可用性时,此刻就需要合理的设计数据库架构来满足不断增长的业务,这就是分库分表诞生的初衷,目的就是为了避免单库由于压力过高,导致出现之前所说的一系列问题,合理的设计架构能最大限度上提高数据库的整体吞吐量。
传统单库架构到分库分表的演进史
但由于某些库是经常需要被访问到的(资金库、信审库、后台库),所以这些核心库以单节点方式去承载流量还是显得有点吃力(吞吐量下降、响应速度变慢),最终又对核心业务库进行横向扩容,架构如下:
最终,根据服务不同的业务规模,拆成了规模不同、业务不同的库,但是这其中的拆分规则到底是什么呢?以及拆分的依据又是啥?
分库分表正确的拆分手段
分库分表的拆分规则也可分为:水平
、垂直
两个维度。
注意:分库、分表是两个概念,两者并不是同一个名词,所以这里需要牢记!按拆分的粒度来排序,共计可分为四种方案:垂直分表
、水平分表
、垂直分库
、水平分库
。
不同场景下的分表方案
分表大多是在单表字段过多或数据过多的场景下,会选择的一种优化方案,当一个表字段过多时,应当考虑垂直分表方案,将多余的字段拆分到不同的表中存储。当一个表的数据过多时,或者数据增长速率过快时,应当考虑通过水平分表方案,来降低单表的数据行数。
垂直分表:结构不同,数据不同(表级别)
当一张表由于字段过多时,会导致表中每行数据的体积变大,而之前不仅一次聊到过:单行数据过大带来的后患,一方面会导致磁盘IO次数增多,影响数据的读写效率;同时另一方面结果集响应时还会占用大量网络带宽,影响数据的传输效率;再从内存维度来看,单行数据越大,缓冲区中能放下的热点数据页会越少,当读写操作无法在内存中定位到相应的数据页,从而又会产生大量的磁盘IO。
从上述的几点原因可明显感受到,当单表的字段数量过多时,会导致数据检索效率变低、网络响应速度变慢、数据库吞吐量下降等问题,面对于这种场景时,就可以考虑垂直分表。
例:现在有一张表,总共43个字段,但是对于程序来说,一般经常使用的字段不过其中的十余个,而这些经常使用的字段则被称之为热点字段,假设此时这张表中的热点字段为18个,剩下的冷字段为25个,那么我们就可以根据冷热字段来对表进行拆分,如下:
对字段过多的表做了垂直拆分后,这时就能很好的控制表中单行数据的体积,从而能够让经常使用的字段数据更快的被访问、更快的返回。不过在做垂直拆分时,记得在冷字段的表中多加一个列,作为热字段表的外键映射,保证在需要用到冷数据时也能找到
。
经过垂直拆分后的两张或多张表,各自之间的表结构不同,并且各自存储的数据也不同,这是垂直分表后的特性,以上述例子来说,热点字段表会存储热数据,冷字段表会存储冷数据,两张表的拼接起来后会组成完整的数据。
水平分表:数据不同,结构相同(库级别)
现在有一张表,里面有三千万条数据记录,当基于该表去执行一条在索引上的复杂SQL时,也需要一定时间,至少会比1000万的数据表慢了好几倍,此时可以把这张3000W的表,拆为三张1000W的表,如下:
对一张大表做了水平分表之后,咱们能够很好的控制单表的数据行数,3000W条数据的表和1000W条数据的表,查询速度其实不仅仅只是3倍的差距,数据过了千万级别时,数据量每向上增长一个量级,查询的开销也会呈直线性增长,因此做水平分表时,一般要求控制在500-1200W之间为一张表。
阿里内部的单表数据量大概控制在500600W一张,因为这个数据量级,就算使用分布式策略生成的分布式ID作为主键,也能够很好的把索引树高控制在35以内,也就意味着最多三到五次磁盘IO就一定能得到数据,从而将单表的查询性能控制在最佳范围内。
水平拆分之后的两张或多张表,每张表的表、索引等结构完全相同,各表之间不同的地方在于数据,每张表中会存储不同范围的数据。不过拆分之后的水平表究竟会存储哪个范围的数据,这要根据水平分表的策略来决定,你可以按ID来以数据行分表,也可以按日期来以周、月、季、年…….分表。
分表方案总结
分表方案主要是针对于单表字段过多或数据过多的情况去做的,通过垂直、水平分表的手段,能够很好解决单表由于字段、数据量过多产生的一系列负面影响,但无论是垂直分表还是水平分表,都必须建立在单库压力不高,但是单表性能不够的情况下进行的,因为它们都属于库内分表。
不同场景下的分库方案
分库和分表一样,也可以按垂直和水平两个维度来分,垂直分库本质上就是按业务分库,也就是现在分布式/微服务架构中,业务独享库的概念,而水平分库则是对同一个节点作横向拓展,也就是高可用集群的概念。
垂直分库:结构不同,数据不同(库级别)
当数据库使用单机的结构部署
,在大流量/高并
发情况下遇到瓶颈时,此时就可以考虑分库方案了,首先来聊聊垂直分库。
在项目开发过程中,一般为了方便团队分工合作和后续管理维护,通常都会对单个项目划分模块,按照业务属性的不同,会将一个大的项目拆分为不同的模块,同时每个业务模块也会在数据库中创建对应的表。
而所谓的垂直分库,就是根据业务属性的不同,将单库中具备同一业务属性的表,全部单独拧出来,放在一个单独的库中存储,也就按业务特性将大库拆分为多个业务功能单一的小库,每个小库只为对应的业务提供服务,这样能够让数据存储层的吞吐量呈几何倍增长。
例:以前面给出的金融项目来说,当单个库无法承载整个业务系统产生的流量压力时,比如此时单个数据库节点的QPS上限为2000,但业务高峰期抵达数据库的瞬时流量,造成了2W个并发请求,这时如果处理不当,数据库基本上会被这波瞬时流量打宕机。
对于前面所说的这种情况,就可以考虑根据业务属性拆分整个大库了,核心思想就是:既然单个节点扛不住,那就加机器用多个节点来抗,在客户端按照不同的业务属性,将过来的请求按照不同的业务特性做分流处理,如下:
原本之前单库时,无论是查询用户业务相关的SQL语句,还是放款/还款之类的SQL语句,不管三七二十一统统发往同一个数据库处理,全部都由这一个数据库节点提供数据支持,但按业务特性做了垂直分库后,用户相关的读写请求落入用户库,放款/还款之类的读写请求会落入资金库…..,这样就能很好的去应对单库面临的负载过高问题
。
垂直分库后,每个库中存储的数据都不相同,因为是按照业务特性去将对应的表抽出去了组成新库,所以库结构也是不同的,用户库是由用户相关的表组成、信审库是由心生相关的表组成…….。
水平分库:结构相同,数据不同(库级别)
经过前面的垂直分库后,根据不同的业务类型,将访问压力分发到不同的库处理后,虽然在极大程度上提升了数据层的负荷能力,但如果某类业务的并发数依旧很高,比如经过前面的业务分流后,假设平台库需要承载5000的并发、信审库依旧需要承载1W的并发,这也远超出了单个数据库节点的处理瓶颈,最终可能还是会能把对应的数据库节点打宕机,所以此时可通过水平分库的方案,来提升某类业务库的抗并发吞吐量。如下:
通过水平拆分的方案,能够根据压力的不同,分配不同的机器数量,从而使得不同库的抗压性都能满足对应的业务需求,这也就类似于分布式/微服务项目中,对单个服务做集群保证高可用的策略。
水平分库是基于一个节点,然后直接横向拓展,这也就意味着同一业务的数据库,各节点之间的库结构完全相同,但每个节点中的数据是否相同,这就要看你自己去决定了,一般情况下都是不同的,也就是不同节点的库会存储不同范围的数据。
另类的分库方案
前面聊清楚了分库分表中经典的垂直分库和水平分库方案,但除开这两种之外,还有一些另类的分库方案,也就是指一些数据库的高可用方案,例如主从复制、读写分离、双主热备等方案。
主从方案
:一般会搭建读写分离,写请求发往主节点处理,读请求发往从节点处理,从节点会完全同步主节点的数据,从而实现读写请求分开处理的效果,能够再一定程度上提升数据存储层整体的并发处理能力。同时当主机挂掉时,从机也能够在很快的时间内替换成主机,以此确保数据层的高可用。
多主方案
:一般是双主方案,两台数据库节点之间互为主从,相互同步各自的数据,两台节点中都具备完整的数据,读写请求可以发给任意节点处理。相较于前面的主从读写分离架构,这种双主双写架构的灾备能力更强,因为当其中某个节点宕机时,另一个节点可以完全接替对方的流量,不存在从机切换成主机的时间开销,因此能够保证数据100%不丢失。
库分表后带来的“副作用”一站式解决方案
垂直分表后带来的隐患
垂直分表后当试图读取一条完整数据时,需要连接多个表来获取,对于这个问题只要在切分时,设置好映射的外键字段即可。当增、删、改数据时,往往需要同时操作多张表,并且要保证操作的原子性,也就是得手动开启事务来保证,否则会出现数据不一致的问题。
水平分表后带来的问题
水平分表就是将一张大表的数据,按照一定的规则划分成不同的小表,当原本的一张表变为多张表时,虽然提升了性能,但问题随之也来了
多表联查问题(Join)
之前在库中只存在一张表,所以非常轻松的就能进行联表查询获取数据,但是此时做了水平分表后,同一张业务的表存在多张小表,这时再去连表查询时具体该连接哪张呢?似乎这时问题就变的麻烦起来了,怎么办?解决方案如下:
①如果分表数量是固定的,直接对所有表进行连接查询,但这样性能开销较大,还不如不分表。
②如果不想用①,或分表数量会随时间不断变多,那就先根据分表规则,去确定要连接哪张表后再查询。
③如果每次连表查询只需要从中获取1~3个字段,就直接在另一张表中设计冗余字段,避免连表查询。
增删改数据的问题
当想要增加、删除、修改一条数据时,因为存在多张表,所以又该去哪张表中操作呢?这里还是前面那个问题,只要在操作前确定好具体的表即可。但还有一种情况,就是批量变更数据时,也会存在问题,要批量修改的数据该具体操作哪几张分表呢?依旧需要先定位到具体分表才行。
聚合操作的问题
之前因为只有一张表,所以进行sum()、count()….、order by、gorup by….等各类聚合操作时,可以直接基于单表完成,但此刻似乎行不通了呀?对于这类聚合操作的解决方案如下:
①放入第三方中间件中,然后依赖于第三方中间件完成,如ES。
②定期跑脚本查询出一些常用的聚合数据,然后放入Redis缓存中,后续从Redis中获取。
③首先从所有表中统计出各自的数据,然后在Java中作聚合操作。
垂直分库后产生的问题
垂直分库是按照业务属性的不同,直接将一个综合大库拆分成多个功能单一的独享库,分库之后能够让性能提升N倍,但随之而来的是需要解决更多的问题,而且问题会比单库分表更复杂!
因为将不同业务的表拆分到了不同的库中,而往往有些情况下可能会需要其他业务的表数据,在单库时直接join连表查询相应字段数据即可,但此时已经将不同的业务表放到不同库了,这时咋办?跨库Join也不太现实呀,此时有如下几种解决方案:
①在不同的库需要数据的表中冗余字段,把常用的字段放到需要要数据的表中,避免跨库连表。
②选择同步数据,通过广播表/网络表/全局表将对应的表数据直接完全同步一份到相应库中。
③在设计库表拆分时创建ER绑定表,具备主外键的表放在一个库,保证数据落到同一数据库。
④Java系统中组装数据,通过调用对方服务接口的形式获取数据,然后在程序中组装后返回。
问题
- 在MySQL中处理一个包含2000万数据的日志表的水平分表(sharding)是一个相对复杂的任务,特别是在需要保持数据一致性和同步的情况下。
停止写入或选择低峰时段
:
如果有可能,在分表期间暂时停止对原始表的写入操作,或选择一个业务低峰时段进行。
如果无法完全停止写入,那么您需要有一个策略来处理
在分表过程中发生的写入操作。
评估并决定分表键
:
选择一个合适的分表键(sharding key)是关键。它应该是一个分布均匀且能够支持查询需求的字段。
例如,如果日志表中有一个时间戳字段,您可以考虑按时间范围进行分表。
数据迁移
使用SQL脚本或ETL工具将数据从原始表迁移到新的分表中。
您可以编写一个脚本来遍历原始表,根据分表键将数据插入到相应的分表中。
如果数据量很大,考虑使用批量插入和事务来优化性能。
etl工具
:
当然也可使用 离线同步工具Datax
同时可以部署 datax-web
可视化管理工具,方便管理数据同步任务。
当需要 保持数据一致性和同步的情况下:使用增量同步配置