MySQL数据库——分库分表和扩容(3)
导读:本⽂主要介绍数据库的分库分表、中间件和扩容问题
⼀、数据库拆分
为什么要拆分数据库
MySQL等关系型数据库本⾝⽐较容易成为系统瓶颈,单机存储容量、连接数、处理能⼒都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。优化索引,优化SQL等⽅法已经在前⽂写过了,这⾥不在赘述。此时就要考虑对其进⾏切分了,切分的⽬的就在于减少数据库的负担,缩短查询时间。数据库分布式核⼼内容⽆⾮就是数据切分(Sharding),以及切分后对数据的定位、整合。数据切分就是将数据分散存储到多个数据库中,使得单⼀数据库中的数据量变⼩,通过扩充主机的数量缓解单⼀数据库的性能问题,从⽽达到提升数据库操作性能的⽬的。数据切分根据其切分类型,可以分为两种⽅式:垂直(纵向)切分和⽔平(横向)切分。
垂直拆分:
指的是将⼀个包含了很多表的数据库,根据表的功能的不同,拆分为多个⼩的数据库,每个库包含部分
表。例如将⽤户的相关数据拆分成为⽤户表、账户表、记录表、流⽔表等相关表,⼀般通过⽤户的唯⼀uid做关联。垂直拆分,还有另⼀种说法,将⼀个包含了很多字段的⼤表拆分为多个⼩表,每个表包含部分字段,这种情况在实际开发中基本很少遇到。
读写分离:
随着业务的不断发展,⽤户数量和并发量不断上升。数据库读的压⼒太⼤,单台mysql实例扛不住,此时⼤部分 Mysql DBA 就会将数据库设置成 读写分离状态 。也就是⼀个 Master 节点(主库)对应多个 Salve 节点(从库)。可以将slave节点的数据理解为master节点数据的全量备份。
在DBA将mysql配置成主从复制集的背景下,开发同学所需要做的⼯作是:当更新数据时,应⽤将数据写⼊master主库,主库将数据同步给多个slave从库。当查询数据时,应⽤选择某个slave节点读取数据。
基本读写分离功能:对sql类型进⾏判断,如果是select等读请求,就⾛从库,如果是insert、update、delete等写请求,就⾛主库。
主从数据同步延迟问题:因为数据是从master节点通过⽹络同步给多个slave节点,因此必然存在延迟。因此有可能出现我们在master节点中已经插⼊了数据,但是从slave节点却读取不到的问题。对于⼀些强⼀致性的业务场景,要求插⼊后必须能读取到,因此对于这种情况,我们需要提供⼀种⽅式,让读请求也可以⾛主库,⽽主库上的数据必然是最新的。
事务问题:如果⼀个事务中同时包含了读请求(如select)和写请求(如insert),如果读请求⾛从库,写请求⾛主库,由于跨了多个库,那么本地事务已经⽆法控制,属于分布式事务的范畴。⽽分布式事务⾮常复杂且效率较低。因此对于读写分离,⽬前主流的做法是,事务中的所有sql统⼀都⾛主库,由于只涉及到⼀个库,本地事务就可以搞定。
分库分表:
⼀旦业务表中的数据量⼤了,从维护和性能⾓度来看,⽆论是任何的 CRUD 操作,对于数据库⽽⾔都是⼀件极其耗费资源的事情。即便设置了索引, 仍然⽆法掩盖因为数据量过⼤从⽽导致的数据库性能下降的事实 ,因此这个时候 Mysql DBA 或许就该对数据库进⾏ ⽔平分区(sharding,即分库分表 )。经过⽔平分区设置后的业务表,必然能够将原本⼀张表维护的海量数据分配给 N 个⼦表进⾏存储和维护。
⽔平分表从具体实现上⼜可以分为3种:只分表、只分库、分库分表
只分库:
将db库中的user表拆分为2个分表,user_0和user_1,这两个表还位于同⼀个库中。适⽤场景:如果库中的多个表中只有某张表或者少数表数据量过⼤,那么只需要针对这些表进⾏拆分,其他表保持不变。
只分库:
将db库拆分为db_0和db_1两个库,同时在db_0和db_1库中各⾃新建⼀个user表,db_0.user表和db_1.user表中各⾃只存原来的
db.user表中的部分数据。
分库分表:
将db库拆分为db_0和db_1两个库,db_0中包含user_0、user_1两个分表,db_1中包含user_2、user_3两个分表。下图演⽰了在分库分表的情况下,数据是如何拆分的:假设db库的user表中原来有4000W条数据,现在将db库拆分为2个分库db_0和db_1,user表拆分为user_0、user_1、user_2、user_3四个分表,每个分表存储1000W条数据。
分库分表的问题:
分库分表⼜带来4个问题,分⽚策略,分布式id,分布式事务,动态扩容
1、分⽚策略
虽然分库分表的,但是其还是希望能和单库单表那样的去操作数据库。例如我们要批量插⼊四条⽤户记录,并且希望根据⽤户的id字段,确定这条记录插⼊哪个库的哪张表。例如1号记录插⼊user1表,2号记录插⼊user2表,3号记录插⼊user3表,4号记录插⼊user0表,以此类推。常⽤的⽅法有:HASH取模、范围分⽚、地理位置分⽚、时间分⽚等
2、全局id
分库分表后,我们不能再使⽤mysql的⾃增主键。因为在插⼊记录的时候,不同的库⽣成的记录的⾃增id可能会出现冲突。因此需要有⼀个全局的id⽣成器。⽬前分布式id有很多中⽅案,主键id的⽣成策略、使⽤全局节点来⽣成ID、其中⼀个⽐较轻量级的⽅案是twitter的snowflake算法,就是把⼀个64位的long型的id,1个bit是不⽤的,⽤其中的41 bit作为毫秒数,⽤10 bit作为⼯作机器id,12 bit作为序列号。
3、分布式事务
mysql⽀持XA事务,但是效率较低。柔性事务是⽬前⽐较主流的⽅案,柔性事务包括:最⼤努⼒通知型、可靠消息最终⼀致性⽅案以及TCC两阶段提交。但是⽆论XA事务还是柔性事务,实现起来都是⾮常复杂的。可以采⽤分布式事务中间件,如阿⾥的Seata。
4、动态扩容
动态扩容指的是增加分库分表的数量。例如原来的user表拆分到2个库的四张表上。现在我们希望将分库的数量变为4个,分表的数量变为8个。这种情况下⼀般要伴随着数据迁移。例如在4张表的情况下,id为7的记录,7%4=3,因此这条记录位于user3这张表上。但是现在分表的数量变为了8个,⽽7%8=0,⽽user0这张表上根本就没有id=7的这条记录,因此如果不进⾏数据迁移的话,就会出现记录不到的情况。
⼆、中间件
主流数据库中间件设计⽅案:
proxy:独⽴部署⼀个代理服务,这个代理服务背后管理多个数据库实例。
优点:
1. 多语⾔⽀持。也就是说,不论你⽤的php、java或是其他语⾔,都可以⽀持。以mysql数据库为例,如果proxy本⾝实现了mysql的通
信协议,那么你可以就将其看成⼀个mysql 服务器。mysql官⽅团队为不同语⾔提供了不同的客户端却
动,如java语⾔的mysql-connector-java,python语⾔的mysql-connector-python等等。因此不同语⾔的开发者都可以使⽤mysql官⽅提供的对应的驱动来与这个代理服务器建通信。
2. 对业务开发同学透明。由于可以把proxy当成mysql服务器,理论上业务同学不需要进⾏太多代码改造,既可以完成接⼊。
缺点:
1. 1 实现复杂。因为proxy需要实现被代理的数据库server端的通信协议,实现难度较⼤。通常我们看到⼀些proxy模式的数据库中间
件,实际上只能代理某⼀种数据库,如mysql。⼏乎没有数据库中间件,可以同时代理多种数据库(sqlserver、PostgreSQL、Oracle)。
2. proxy本⾝需要保证⾼可⽤。由于应⽤本来是直接访问数据库,现在改成了访问proxy,意味着proxy必须保证⾼可⽤。否则,数据库
没有宕机,proxy挂了,导致数据库⽆法正常访问,就尴尬了。
3. 租户隔离。可能有多个应⽤访问proxy代理的底层数据库,必然会对proxy⾃⾝的内存、⽹络、cpu等产⽣资源竞争,proxy需要需要
具备隔离的能⼒。
client:业务代码需要进⾏⼀些改造,引⼊⽀持读写分离或者分库分表的功能的sdk
优点:
1. 实现简单。proxy需要实现数据库的服务端协议,但是client不需要实现客户端通信协议。原因在于,⼤多数据数据库⼚商已经针对不
同的语⾔提供了相应的数据库驱动driver,例如mysql针对java语⾔提供了mysql-connector-java驱动,针对python提供了mysql-connector-python驱动,客户端的通信协议已经在driver层⾯做过了。因此smart-client模式的中间件,通常只需要在此基础上进⾏封装即可。
2. 天然去中⼼化。client的⽅式,由于本⾝以sdk的⽅式,被应⽤直接引⼊,随着应⽤部署到不同的节点上,且直连数据库,中间不需要
有代理层。因此相较于proxy⽽⾔,除了⽹络资源之外,基本上不存在任何其他资源的竞争,也不需要考虑⾼可⽤的问题。只要应⽤的节点没有全部宕机,就可以访问数据库。(这⾥的⾼可⽤是相⽐proxy⽽⾔,数据库本⾝的⾼可⽤还是需要保证的)
缺点:
mysql下载jar包
1. 通常仅⽀持某⼀种语⾔。例如tddl、zebra、sharding-jdbc都是使⽤java语⾔开发,因此对于使⽤其他语⾔的⽤户,就⽆法使⽤这些
中间件。如果其他语⾔要使⽤,那么就要开发多语⾔客户端。
2. 版本升级困难。因为应⽤使⽤数据源代理就是引⼊⼀个jar包的依赖,在有多个应⽤都对某个版本的jar包产⽣依赖时,⼀旦这个版本有
bug,所有的应⽤都需要升级。⽽数据库代理升级则相对容易,因为服务是单独部署的,只要升级这个代理服务器,所有连接到这个代理的应⽤⾃然也就相当于都升级了。
三、两个问题:热点、扩容
随着业务的迅速发展、数据库就得扩容。热点问题,就是如何防⽌某个库某个表I/O⽐其他库表的频率⾼得多。
热点问题:
有两种路由算法,hash取模和range范围⽅案
hash取模的优点是:数据可以均匀的放到每张表中,这样此订单进⾏操作时,就不会有热点问题。但是产⽣的问题就是,数据很难迁移扩容。
range范围:⼀定范围内的订单,存放到⼀个表中,优点正好是⽅便扩容,却会产⽣热点问题。
两者结合⽤,就是以下的⽅案。⾸先设计0到4000万的库:
后续需要扩容再增加4000万到8000万的库

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。