引言

本篇数据库专栏内容,主要会讲解不同高并发场景下的MySQL架构设计方案,也包括对于各类大流量/大数据该如何优雅的处理,也包括架构调整后带来的后患又该如何解决?其中内容会涵盖库内分表、主从复制、读写分离、双主热备、垂直分库、水平集群、分库分表实践、分布式事务、分布式ID、数据一致性探讨……等内容。

话归正题,分库分表这个概念基本上碰过数据库的小伙伴都有听说过,但很多小伙伴对这块具体该如何落地并不清楚,因此接下来这篇会先阐述MySQL分库分表的方法论,以及详细讲解分库分表后产生的后患问题,但在此之前先送上一句话,请牢记:

不要为了分库分表而分库分表!!引入SOA架构中的一句话:架构不是一蹶而起的,而是慢慢演进的。只有真正需要分库分表来解决问题时,才去真正的做拆分,否则会导致很多不必要的麻烦产生,这点在《阿里Java开发规范手册》中有明确的写出:

一、为什么需要分库分表?

在讲为什么需要分库分表之前,咱们先来讲一个故事:在很久很久之前有一位名唤风的美男子,最初由于年幼并未娶妻生子,因此出行时都只需要一匹马来拉车,但随着年龄渐长,慢慢的开始娶妻纳妾,每次出行时的人数也会直线增长,而之前负责拉车的那匹老马却苦不堪言,因为随着日子一天一天的过,自身的压力也随之增加,终于有一天,老马扛不住了,累到在了大街上。

随即风也慢慢发现了这个问题,由于每次出行的人口越来越多,因此老马的能力无法满足出行需求,这时风为了解决出行问题,所以花费重金托人从西域购入了一匹身强体壮的汗血宝马,以此来寻求解决所遇到的困扰。

当商人将名贵的汗血宝马交给风时,这匹马的确比之前的老马能力强太多太多了,拖动一辆承载几人的马车完全不在话下,同时风有了之前的前车之鉴,因此对其也格外看重,每天都吩咐人给宝马喂好料、做保养……,但好景不长,随着时间推移,男人三妻四妾放在当时也并非罕事,所以这时这匹来自西域的汗血宝马也对此无能为力,慢慢的出现乏力的情况。

此时风也再次察觉到了这个情况,于是再次找到当初帮忙购置汗血宝马的商人,想要再花重金买入一匹能力更强、体力更盛的千里马!但商人却道:“目前你手中的汗血宝马已属世间极品,想要找到比它更强且能代替它的少之又少,贵客您这需求恐怕老夫是难以完成咯”!

这时问题似乎陷入僵局,但随之大行商便道:“虽然我无法替您寻找到更为优良的马匹,但我有一个万全之策能解你燃眉之急”!那这个完全之策到底是什么呢?此时商人口中缓缓道出:“当一匹马无法解决你的出行问题时,与其寻找更好的马匹,为何不选择用更多的马匹来拉车呢”?

此时风一拍大腿,立马称赞道:所言极是,言之有理,于是大手一挥立马又购置了六匹良马,与之前的两匹旧马,组成了八匹马拉车的马队,自从之后,风再也没有遇到过出行问题。

​在上面这个故事中,大家应该能够感受出来,当单匹马无法拉动马车时,不要试图找到一匹更好的马来代替,而是应该选择使用多匹马来拉车!这个故事的内在意义放在编程中同样如此,对一个节点做性能优化、升级硬件配置就是再试图寻找一匹更强壮的马,但一匹马的力量再强也是有限的,所以这时选择使用更多的马匹(服务器/节点)来解决问题才是王道!

编程里面有句话叫做:加一台服务器的收益胜过千万次调优,毕竟机器数量才是真理!

那么接着咱们也回到问题本身,来一起聊一聊MySQL为什么需要分库分表?

1.1、请求数太高

在高并发情况下,大量读写请求落入数据库处理,最终会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service层来看就是,可用数据库连接锐减甚至无连接可用,接下来面临的就是并发量急剧增加、吞吐量严重下降、连接出现异常、数据库时常宕机、系统经常崩溃一系列后患问题。

1.2、数据查询慢

  • 一、单表或单库数据量过大,导致数据检索的效率直线降低。

  • 二、单库整体并发连接数接近系统阈值,从而导致此请求获取不到连接数,一直处于等待获取连接的状态。

  • 三、已经获取但由于并发过高导致CPU被打满,就算SQL所查询的表数据行很少,也同样因为没有CPU资源无法执行,所以一直处于阻塞状态,最终出现查询过慢的现象。

1.3、数据量太大

  • ①当一个库的数据存储量太大时,就算每张表的并发数不多,但是因为是海量数据,单库中存在大量的数据表,每张表都有一部分并发请求,导致最终单库的连接数阈值成为数据库的瓶颈。

  • ②当一张表数据太多时,导致单表查询速度严重下降,虽然InnoDB存储引擎的表允许的最大行数为10亿,但是如果一张表的数据行记录达到上亿级别,那就算通过索引去查询一条数据,它也需要至少经过上十次到几十次磁盘IO,从而导致单表查询速度直线下降;一般一张表的数据行数在800~1200W左右最合适。

1.4、单体架构的通病

单库中某张表遇到问题需要修复时,会影响了整个库中所有数据,因为有些严重的情况下需要停机优化后重新上线,这时其它一些没有出现问题的表,也会因此受到影响。

这就好比团队中一个人没完成好工作,所以导致整个团队一起陪同加班,这无疑很令人糟心。

1.5、MySQL数据库瓶颈

上面聊到的各类问题,本质上都是一些数据库瓶颈,一般程序的性能瓶颈都源自于硬件问题,而问题归根到底都属于IO、CPU瓶颈,接下来聊一聊IO、CPU瓶颈可以细分成哪些呢?

2.1、IO瓶颈

IO瓶颈主要分为两方面,一方面是磁盘IO瓶颈,另一方面则是网络IO瓶颈,具体如下:

磁盘IO瓶颈

①在之前《MySQL内存篇》中曾详细讲到过,MySQL为了提升读写性能,通常都会将一些经常使用的热点数据放入缓冲区,避免每次读写请求都走磁盘IO的方式去操作数据,但当整库数据数据太多时,可能会出现大量的热点数据,此时内存缓冲区中又无法全部放下,因此会导致大量的读写请求产生磁盘IO,通过读写磁盘的方式去完成数据读写,从而导致查询速度下降。

②一次查询数据的过程中,由于涉及到的数据过多,导致无法全部在内存中完成数据检索,如分组、排序、关联查询等场景,内存中相应的缓冲区无法载入要操作的全部数据,因此只能通过分批的方式处理数据,此时又需要经过大量磁盘IO后才得到最终数据集。

这种情况在单表查询时也存在,如果单表中字段过多,导致每一行数据的体积都比较大,因此会超出MySQL磁盘IO每次读取16KB的这个限制,因而也会出现检索单表数据时,一条数据就需要经过多次IO才能拿完。

简单来说磁盘IO瓶颈有两种情况,一种是磁盘IO次数过多,导致IO利用率持续居高不下,另一种情况就是每次读取数据都超出单次IO的最大限制,因此会引发多次IO,从而又演变成第一种情况。

网络IO瓶颈

当一个请求的生成的SQL语句执行后,由于这条语句得到的结果集数据太多,从而会导致相应时的数据包体积过大,这时如果网络带宽不够,就会出现传输过慢的问题,因为底层需要对大的数据包做拆包,然后分批返回,这时也会阻塞其它读写数据的请求,网络带宽就会成为新的瓶颈。

其实网络IO瓶颈的情况也比较好理解,网络带宽就好比一条马路,由于一条SQL返回的结果集过大,因此装载数据包的卡车远远超出了马路的宽度,这时就需要将数据拆分到多辆能通行的小卡车装载,但虽然这样做能够让数据包“变窄”成功返回,但因此也会造成这个大数据包会变长,从而占满整个带宽通道,因此其它网络数据包需要等这个大包传输完成后,才能继续通行。

2.2、CPU瓶颈

上面简单的了解IO瓶颈后,再来看看机器本身的CPU瓶颈,CPU瓶颈也会分为两种,一种是运算密集型瓶颈,另一种则是阻塞密集型瓶颈,但想要弄清楚这两种瓶颈,咱们首先得理解CPU的工作原理。

CPU工作原理

有深入研究过多线程编程的小伙伴,对于CPU的工作原理应该并不陌生,线程是操作系统最小的执行单元,因此CPU工作时本质上就是以线程作为载体,然后执行线程任务中的一条条指令,一般情况下单核CPU在同一时刻只能够支撑单挑线程运行,但随着2002年超线程技术的发布后,如今的CPU通常具备两组ALU执行单元,也就是大家常听到的单核双线程、四核八线程…..

因为超线程技术的存在,所以一核CPU在同一时刻可支撑两条线程一起运行,这种结构的CPU在现在也被称为大核架构,即一核心具备两逻辑处理器。而传统的单核单线程的CPU则被称为小核架构,目前最新的12/13代CPU还有大小核异构的架构。

但无论是小核、大核、大小核架构的CPU,本质上在机器运行期间,往往整个系统内所有程序的线程数,加起来之后都会远超于CPU核数,如下:

​那在这种情况下是如何工作的呢?相信有相关知识储备的小伙伴第一时间就会想到一个词汇:时间片切换执行,也就是有限的CPU核心会在所有线程之前来回切换,以此来确保系统和程序的正常运转。

其中的原理简单理解起来也并不难,因为每条线程的指令在执行时都需要数据作为基础,因此CPU在执行某条指令后,执行新指令时需要加载数据,此时会去载入数据,而操作系统这时就会将CPU资源切换给其它的线程执行,等这条线程的数据准备好了再切换回来。

浅显层面的原理如上,再深入一些会涉及总线、I/O设备原理、活跃进程算法、资源切换原理……等一系列技术了,这里先就此打住,我们只需要了解到这里就够用啦!接着再回去聊聊所说的两种CPU瓶颈。

运算密集型瓶颈

用户请求生成的SQL语句中包含大量join联表查询、group by分组查询、order by排序等之类的聚合操作,同时这些操作要基于特别大的数据集做运算,导致执行时消耗大量CPU资源,CPU占用率直达100%+,因此无法再给其它线程提供执行所需的CPU资源。

阻塞密集型瓶颈

一张或多张表的数据量特别大,此时基于这些大表做数据检索时,需要扫描的数据行太多,虽然这些SQL语句不会大量消耗CPU资源,但由于数据量过大,会导致长时间占用CPU资源,从而造成其它线程无法获取CPU资源执行。

上面聊到的两种CPU瓶颈中,一种属于大量运算导致CPU资源耗尽,一种属于大表检索导致长时间占用CPU资源,两种情况都会导致CPU遇到瓶颈,从而无法给其它线程提供运行所需的资源。有人也许会说,似乎这是SQL不合理导致的呀?其实不然,因为往往很多正常的SQL也会出现大量消耗CPU、或检索大表数据长时间占用CPU的情况。

无论是IO瓶颈,还是CPU瓶颈,都可以通过升级硬件配置的方式来解决,比如升级磁盘材质、加大网络带宽、增多CPU核数等,但前面讲到过,这种方式面对高并发大流量的冲击,治标不治本!如果客户端流量过大,这时不应该再试图寻找更强壮的马来代替,而是应该选择多匹马的方案来解决。

其实这也是一种节省成本的做法,无限制升级硬件也不是长久之道,而且越到后期,配置越高的硬件成本越高,四颗八核十六线程的CPU,成本价可能还会比一颗32Core 64Thread的CPU要便宜。

1.6、再聊为何需要分库分表

其实不管是并发过高、或访问变慢、亦或数据量过大,本质上都属于数据库遭遇到了瓶颈,但只不过根据情况不同,分为不同类型的数据库瓶颈,但是最终对于客户端而言,就是数据库不可用了或者变慢了。

而导致数据库出现此类问题的原因,实则就是随着业务的发展,系统的数据不断增多、用户量不断增长、并发量不断变大,因此对于数据再进行CRUD操作的开销也会越来越大,再加上物理服务器的CPU、磁盘、内存、IO等资源有限,最终也会限制数据库所能承载的最大数据量、数据处理能力。

当出现上述这类问题,并且无法通过升级硬件、版本、调优等手段解决时,或者只能临时解决,却无法保障未来业务增长的可用性时,此刻就需要合理的设计数据库架构来满足不断增长的业务,这就是分库分表诞生的初衷,目的就是为了避免单库由于压力过高,导致出现之前所说的一系列问题,合理的设计架构能最大限度上提高数据库的整体吞吐量。

下面为了能够更好的讲透彻分库分表的方法论,我将以一个真实案例给大家阐述分库分表的架构演进过程。

二、传统单库架构到分库分表的演进史

早些年我司新开一条业务线切入金融领域,最开始的因为担心风险过大,所以并未投入太多的成本,处于一个试错阶段,最初就把所有业务都怼入一个war包,所有业务共享一个库资源,结构大致如下:

​而在当时那段时间,金融领域快速发展,慢慢的,Java搭建的金融核心系统开始出现响应变慢,甚至时不时宕机,部署整个金融核心系统的单台Tomcat很快遇到了瓶颈,后来实在因为Tomcat三天俩头宕机重启,迫于无奈开始了业务架构的改进,如下:

​因为当时考虑到业务发展速度,并没有使用Nginx对Tomcat进行横向拓展做水平集群,因为如果仅仅只是通过Nginx来做,可能以后还是需要对架构进行升级,进一步按业务拆分成分布式系统,所以经讨论后一致决定直接引入分布式架构对系统进行改造。

经过改造后的业务架构,Java应用这边的确可以抗住每天的流量,但当时因为在做Java程序架构升级的时候,只引入了Redis、MQ降低数据库并发,并没有去对数据库做太多的拓展,因此当时还是所有业务共享一个库。

随着时间的慢慢推移,虽然用MQ、Redis做了流量的削峰,但是也挡不住当时的流量请求,做过金融业务的小伙伴应该清楚,它不像其他业务领域中读多写少,金融业务中读多写也多,同时还需要每日对账、跑批、统计报表…..,因此对数据库的读写操作相当多,而金融业务又要求数据实时性,所以很多操作无法走MQ异步完成,也不能放入Redis做数据缓存,Why?

好比拿股市中最基本的买入卖出为例,原本客户看到的是5$一股,然后用户选择了买入,因为数据放在缓存里没有及时更新,结果最新价格成了10$一股,此时用户买入10000$,按用户的预估应该是会买入2000股左右,结果最终买成了1000股,平白无故导致用户追涨。 也包括大量的写操作也无法走MQ异步完成,比如用户以5$的成本价买入,现在看到了最新的价格为10$一股,然后选择了全仓卖出,这时你将卖出操作发给了MQ异步执行,结果MQ中的卖出消息并未立马被消费,而是到了一小时后价格降到了2.5$一股时,才真正被消费,这回导致原本用户能赚100点,最后反变为倒亏50点。

虽然当时手上的项目并非交易所类型的金融业务,但无论是哪类金融业务,基本上对数据的实时性要求特别高,所以MQ、Redis基本上只能分担很小一部分的流量,其它大部分的流量依旧会需要落库处理。也正因如此,最终数据库成了整个系统的瓶颈口,为了去解决这个问题,最终选用服务独享库的方案进行升级(也就是后续要说的垂直分库模式),如下:

​而数据库这边经过拆分之后,相较于之前的单库架构,整个数据库系统的稳定性和可用性明显得到改善,但由于某些库是经常需要被访问到的(资金库、信审库、后台库),所以这些核心库以单节点方式去承载流量还是显得有点吃力(吞吐量下降、响应速度变慢),最终又对核心业务库进行横向扩容,架构如下:

​最终,根据服务不同的业务规模,拆成了规模不同、业务不同的库,但是这其中的拆分规则到底是什么呢?以及拆分的依据又是啥?接着一起来聊一聊!

三、分库分表正确的拆分手段

现在你的手里有一个西瓜,吃的时候切法有两种,一种是以垂直方向竖切,另一种是以水平方向横切,如下:

​这种切割方式在分库分表中也存在,分库分表的拆分规则也可分为:水平、垂直 两个维度。

但水平、垂直该怎么拆?什么场景下拆?拆完会出现的问题又该怎么去解决呢?那么接着来一步步分析到底怎么拆,拆完的问题怎么去解决~

注意:分库、分表是两个概念,两者并不是同一个名词,所以这里需要牢记!按拆分的粒度来排序,共计可分为四种方案:垂直分表、水平分表、垂直分库、水平分库。

3.1、不同场景下的分表方案

分表大多是在单表字段过多或数据过多的场景下,会选择的一种优化方案,当一个表字段过多时,应当考虑垂直分表方案,将多余的字段拆分到不同的表中存储。当一个表的数据过多时,或者数据增长速率过快时,应当考虑通过水平分表方案,来降低单表的数据行数。

3.1.1、垂直分表:结构不同,数据不同(表级别)

当一张表由于字段过多时,会导致表中每行数据的体积变大,而之前不仅一次聊到过:单行数据过大带来的后患,一方面会导致磁盘IO次数增多,影响数据的读写效率;同时另一方面结果集响应时还会占用大量网络带宽,影响数据的传输效率;再从内存维度来看,单行数据越大,缓冲区中能放下的热点数据页会越少,当读写操作无法在内存中定位到相应的数据页,从而又会产生大量的磁盘IO。

从上述的几点原因可明显感受到,当单表的字段数量过多时,会导致数据检索效率变低、网络响应速度变慢、数据库吞吐量下降等问题,面对于这种场景时,就可以考虑垂直分表。

例:现在有一张表,总共43个字段,但是对于程序来说,一般经常使用的字段不过其中的十余个,而这些经常使用的字段则被称之为热点字段,假设此时这张表中的热点字段为18个,剩下的冷字段为25个,那么我们就可以根据冷热字段来对表进行拆分,如下:

​对字段过多的表做了垂直拆分后,这时就能很好的控制表中单行数据的体积,从而能够让经常使用的字段数据更快的被访问、更快的返回。不过在做垂直拆分时,记得在冷字段的表中多加一个列,作为热字段表的外键映射,保证在需要用到冷数据时也能找到。

对于这种垂直分表的场景在很多业务中都有实现,如用户数据会分为users、user_infos,订单数据会分为order、order_info……。所谓的垂直分表其实和之前《库表设计篇》中聊到的范式设计,大致含义是类似的,如果表结构是按照数据库三范式设计的,基本上也无需考虑做垂直分表。

经过垂直拆分后的两张或多张表,各自之间的表结构不同,并且各自存储的数据也不同,这是垂直分表后的特性,以上述例子来说,热点字段表会存储热数据,冷字段表会存储冷数据,两张表的拼接起来后会组成完整的数据。

3.1.2、水平分表:结构相同,数据不同(表级别)

前面聊到了字段过多对读写数据时的影响,接着再来看看数据过多时会导致的负面影响,虽然数据库中有索引机制,能够确保单表在海量数据的基础上,检索数据的效率依旧可观,但随着数据不断增长,当达到千万级别时,就会出现明显的查询效率下降的问题。

这里所谓的查询效率下降并非指单表的简单查询语句,而是指一些复杂的SQL语句,毕竟线上往往很多需求,都要经过复杂的SQL运算后才能得到数据,比如多张表联查再跟了一堆分组、排序、过滤、函数处理…..语句,这种情况下再基于这种大表查询,就算走了索引,效率也不会太高,因为其中要涉及到大量数据的处理,因此面对这种情况,就可以对表进行水平拆分。

例:现在有一张表,里面有三千万条数据记录,当基于该表去执行一条在索引上的复杂SQL时,也需要一定时间,至少会比1000万的数据表慢了好几倍,此时可以把这张3000W的表,拆为三张1000W的表,如下:

对一张大表做了水平分表之后,咱们能够很好的控制单表的数据行数,3000W条数据的表和1000W条数据的表,查询速度其实不仅仅只是3倍的差距,数据过了千万级别时,数据量每向上增长一个量级,查询的开销也会呈直线性增长,因此做水平分表时,一般要求控制在500-1200W之间为一张表。

阿里内部的单表数据量大概控制在500~600W一张,因为这个数据量级,就算使用分布式策略生成的分布式ID作为主键,也能够很好的把索引树高控制在3~5以内,也就意味着最多三到五次磁盘IO就一定能得到数据,从而将单表的查询性能控制在最佳范围内。

水平拆分之后的两张或多张表,每张表的表、索引等结构完全相同,各表之间不同的地方在于数据,每张表中会存储不同范围的数据。不过拆分之后的水平表究竟会存储哪个范围的数据,这要根据水平分表的策略来决定,你可以按ID来以数据行分表,也可以按日期来以周、月、季、年…….分表。

PS:诸位看下来应该会发现,水平分表的方案和之前聊到过的《MySQL表分区技术》十分类似,但这不意味着表分区可以代替水平分表,答案恰恰相反,一般要用表分区的场景中可以选择水平分表的方案来代替。

3.1.3、分表方案总结

分表方案主要是针对于单表字段过多或数据过多的情况去做的,通过垂直、水平分表的手段,能够很好解决单表由于字段、数据量过多产生的一系列负面影响,但无论是垂直分表还是水平分表,都必须建立在单库压力不高,但是单表性能不够的情况下进行的,因为它们都属于库内分表。

如果是数据库整体压力都很大的情况,从而导致的查询效率低下,那不管再怎么做分表也无济于事,毕竟连流量入口都出现了拥塞,自然分表也无法解决问题,所以分表操作只建立在单库压力不高,但是单表查询效率低下的情况适用。

好比把数据库比作一个游乐园,而表则可以比作里面的一个个娱乐项目,由于某些娱乐项目比较火爆,因此可以对同一类型的项目多开几个,从而解决热点项目顾客要排队很久才能玩的问题。但如果是整个游乐园的人流量都非常大,每个项目都有大量顾客排队,这时再去对内部的娱乐项目作拓展,这种方式是行不通的,毕竟游乐园的大门就那么大,游客连进大门都要排队,再在内部作项目拓展显然无济于事。

3.2、不同场景下的分库方案

经过前面的分表总结后可以得知:如果是因为库级别的压力较大,这时就需要考虑分库方案,而不仅仅是分表方案,换到上面的例子中,当整个游乐园的人流量非常大时,应该考虑的是开分园,而并非是在内部作拓展。

分库和分表一样,也可以按垂直和水平两个维度来分,垂直分库本质上就是按业务分库,也就是现在分布式/微服务架构中,业务独享库的概念,而水平分库则是对同一个节点作横向拓展,也就是高可用集群的概念。

3.2.1、垂直分库:结构不同,数据不同(库级别)

当数据库使用单机的结构部署,在大流量/高并发情况下遇到瓶颈时,此时就可以考虑分库方案了,首先来聊聊垂直分库。

在项目开发过程中,一般为了方便团队分工合作和后续管理维护,通常都会对单个项目划分模块,按照业务属性的不同,会将一个大的项目拆分为不同的模块,同时每个业务模块也会在数据库中创建对应的表。

而所谓的垂直分库,就是根据业务属性的不同,将单库中具备同一业务属性的表,全部单独拧出来,放在一个单独的库中存储,也就按业务特性将大库拆分为多个业务功能单一的小库,每个小库只为对应的业务提供服务,这样能够让数据存储层的吞吐量呈几何倍增长。

例:以前面给出的金融项目来说,当单个库无法承载整个业务系统产生的流量压力时,比如此时单个数据库节点的QPS上限为2000,但业务高峰期抵达数据库的瞬时流量,造成了2W个并发请求,这时如果处理不当,数据库基本上会被这波瞬时流量打宕机。

对于前面所说的这种情况,就可以考虑根据业务属性拆分整个大库了,核心思想就是:既然单个节点扛不住,那就加机器用多个节点来抗,在客户端按照不同的业务属性,将过来的请求按照不同的业务特性做分流处理,如下:

​原本之前单库时,无论是查询用户业务相关的SQL语句,还是放款/还款之类的SQL语句,不管三七二十一统统发往同一个数据库处理,全部都由这一个数据库节点提供数据支持,但按业务特性做了垂直分库后,用户相关的读写请求落入用户库,放款/还款之类的读写请求会落入资金库…..,这样就能很好的去应对单库面临的负载过高问题。

垂直分库后,每个库中存储的数据都不相同,因为是按照业务特性去将对应的表抽出去了组成新库,所以库结构也是不同的,用户库是由用户相关的表组成、信审库是由心生相关的表组成…….。

3.2.2、水平分库:结构相同,数据不同(库级别)

经过前面的垂直分库后,根据不同的业务类型,将访问压力分发到不同的库处理后,虽然在极大程度上提升了数据层的负荷能力,但如果某类业务的并发数依旧很高,比如经过前面的业务分流后,假设平台库需要承载5000的并发、信审库依旧需要承载1W的并发,这也远超出了单个数据库节点的处理瓶颈,最终可能还是会能把对应的数据库节点打宕机,所以此时可通过水平分库的方案,来提升某类业务库的抗并发吞吐量。如下:

​通过水平拆分的方案,能够根据压力的不同,分配不同的机器数量,从而使得不同库的抗压性都能满足对应的业务需求,这也就类似于分布式/微服务项目中,对单个服务做集群保证高可用的策略。

水平分库是基于一个节点,然后直接横向拓展,这也就意味着同一业务的数据库,各节点之间的库结构完全相同,但每个节点中的数据是否相同,这就要看你自己去决定了,一般情况下都是不同的,也就是不同节点的库会存储不同范围的数据。

3.2.3、另类的分库方案

前面聊清楚了分库分表中经典的垂直分库和水平分库方案,但除开这两种之外,还有一些另类的分库方案,也就是指一些数据库的高可用方案,例如主从复制、读写分离、双主热备等方案。

主从方案:一般会搭建读写分离,写请求发往主节点处理,读请求发往从节点处理,从节点会完全同步主节点的数据,从而实现读写请求分开处理的效果,能够再一定程度上提升数据存储层整体的并发处理能力。同时当主机挂掉时,从机也能够在很快的时间内替换成主机,以此确保数据层的高可用。

多主方案:一般是双主方案,两台数据库节点之间互为主从,相互同步各自的数据,两台节点中都具备完整的数据,读写请求可以发给任意节点处理。相较于前面的主从读写分离架构,这种双主双写架构的灾备能力更强,因为当其中某个节点宕机时,另一个节点可以完全接替对方的流量,不存在从机切换成主机的时间开销,因此能够保证数据100%不丢失。

不过无论是主从、还是多主方案,本质上都存在木桶效应问题,因为这种分库方案中都会完全同步数据,当一个节点的数据存满时,会导致其他节点也不可用,对于这里的具体原因可参考《MySQL优化篇-架构优化》。

四、分库分表总结

分库方案能够在最大程度上提升数据存储层的性能,但一般在考虑选用分库方案时,应该先考虑使用主从、主主的方案,如果前面两种方案依旧无法提供系统所需的吞吐量,再考虑选择垂直分库方案,按照业务属性去划分库结构,最后才应该考虑选择水平分库方案(同时也要记得考虑数据的增长速率情况)。

那为什么需要遵循这个顺序呢?因为架构不能过度设计,选用主从、主主能够满足需求时,就选这两种方案,因为一方面能避免很多问题产生,同时实现起来也比较简单。同时先考虑垂直分库,再考虑水平分库,是因为水平分库可以建立在垂直分库的基础上,进一步对存储层作拓展,因此灵活性会更高,拓展性会更强。

同时最后再聊一下分库之后带来的好处:

  • ①能够得到最大的性能收益,吞吐量会随机器数量呈直线性增长。

  • ②能够最大程度上保障存储层的高可用,任意节点宕机都不会影响整体业务的运转。

  • ③具备相当强的容错率,当一个库中的结构存在问题需要重构时,无需将所有业务停机更新。

  • ④具备高稳定性,分库+配备完善的监控重启策略后,基本上能确保线上无需人工介入管理。

也就是说,分库方案能够让你的存储层真正达到高可用、高性能、高稳定的“三高”水准。

但要切记不能盲目的分库分表,分库分表前得先清楚性能瓶颈在哪里,然后根据业务以及瓶颈,遵循拆分规则的顺序做合理的拆分方案选择,因为分库分表虽然能带来很大的好处,但是同时也产生了一系列的问题需要去解决。

如果做了分库分表就一定要记住:既不能过度设计,也要考虑数据增长性,提前设计好扩容方案,以便于后续性能再次出现瓶颈时,能够基于现有架构进行优雅升级,一位优秀的开发/架构必须具备前瞻性。

但关于如何设计出一套合理的扩容方案,也包括分库分表后究竟会产生哪些后患问题,这些问题产生后又该怎么解决,具体的内容可参考下篇文章:《一站式解决分库分表后患问题方案》。