引言
本文为掘金社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
从最初开设《全解MySQL专栏》到现在,共计撰写了二十个大章节详细讲到了MySQL
各方面的进阶技术点,从最初的数据库架构开始,到SQL
执行流程、库表设计范式、索引机制与原理、事务与锁机制剖析、日志与内存详解、常用命令与高级特性、线上调优与故障排查.....,似乎涉及到了MySQL
的方方面面。但到此为止就黔驴技穷了吗?答案并非如此,以《MySQL特性篇》为分割线,整个MySQL
专栏从此会进入“高可用”阶段的分析,即从上篇之后会开启MySQL
的新内容,主要讲述分布式、高可用、高性能方面的讲解。
接下来的数据库专栏内容,主要会讲解不同高并发场景下的
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优化篇-架构优化》。
四、分库分表总结
分库方案能够在最大程度上提升数据存储层的性能,但一般在考虑选用分库方案时,应该先考虑使用主从、主主的方案,如果前面两种方案依旧无法提供系统所需的吞吐量,再考虑选择垂直分库方案,按照业务属性去划分库结构,最后才应该考虑选择水平分库方案(同时也要记得考虑数据的增长速率情况)。
那为什么需要遵循这个顺序呢?因为架构不能过度设计,选用主从、主主能够满足需求时,就选这两种方案,因为一方面能避免很多问题产生,同时实现起来也比较简单。同时先考虑垂直分库,再考虑水平分库,是因为水平分库可以建立在垂直分库的基础上,进一步对存储层作拓展,因此灵活性会更高,拓展性会更强。
同时最后再聊一下分库之后带来的好处:
- ①能够得到最大的性能收益,吞吐量会随机器数量呈直线性增长。
- ②能够最大程度上保障存储层的高可用,任意节点宕机都不会影响整体业务的运转。
- ③具备相当强的容错率,当一个库中的结构存在问题需要重构时,无需将所有业务停机更新。
- ④具备高稳定性,分库+配备完善的监控重启策略后,基本上能确保线上无需人工介入管理。
也就是说,分库方案能够让你的存储层真正达到高可用、高性能、高稳定的“三高”水准。
但要切记不能盲目的分库分表,分库分表前得先清楚性能瓶颈在哪里,然后根据业务以及瓶颈,遵循拆分规则的顺序做合理的拆分方案选择,因为分库分表虽然能带来很大的好处,但是同时也产生了一系列的问题需要去解决。
如果做了分库分表就一定要记住:既不能过度设计,也要考虑数据增长性,提前设计好扩容方案,以便于后续性能再次出现瓶颈时,能够基于现有架构进行优雅升级,一位优秀的开发/架构必须具备前瞻性。
但关于如何设计出一套合理的扩容方案,也包括分库分表后究竟会产生哪些后患问题,这些问题产生后又该怎么解决,具体的内容可参考下篇:《分库分表后患问题一站式解决方案》。