网站链接: element-ui dtcms
当前位置: 首页 > 技术博文  > 技术博文

MySQL数据库——分库分表和扩容(3)

2021/4/2 22:42:51 人评论

导读:本文主要介绍数据库的分库分表、中间件和扩容问题 MySQL数据库——索引及SQL优化(1) MySQL数据库—事务和锁(2) 一、数据库拆分 为什么要拆分数据库 MySQL等关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单…

导读:本文主要介绍数据库的分库分表、中间件和扩容问题

MySQL数据库——索引及SQL优化(1)

MySQL数据库—事务和锁(2)

一、数据库拆分

为什么要拆分数据库

MySQL等关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。优化索引,优化SQL等方法已经在前文写过了,这里不在赘述。此时就要考虑对其进行切分了,切分的目的就在于减少数据库的负担,缩短查询时间。数据库分布式核心内容无非就是数据切分(Sharding),以及切分后对数据的定位、整合。数据切分就是将数据分散存储到多个数据库中,使得单一数据库中的数据量变小,通过扩充主机的数量缓解单一数据库的性能问题,从而达到提升数据库操作性能的目的。数据切分根据其切分类型,可以分为两种方式:垂直(纵向)切分和水平(横向)切分。

垂直拆分:

指的是将一个包含了很多表的数据库,根据表的功能的不同,拆分为多个小的数据库,每个库包含部分表。例如将用户的相关数据拆分成为用户表、账户表、记录表、流水表等相关表,一般通过用户的唯一uid做关联。垂直拆分,还有另一种说法,将一个包含了很多字段的大表拆分为多个小表,每个表包含部分字段,这种情况在实际开发中基本很少遇到。

读写分离:

随着业务的不断发展,用户数量和并发量不断上升。数据库读的压力太大,单台mysql实例扛不住,此时大部分 Mysql DBA 就会将数据库设置成 读写分离状态 。也就是一个 Master 节点(主库)对应多个 Salve 节点(从库)。可以将slave节点的数据理解为master节点数据的全量备份。

640?wx_fmt=png

在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取模、范围分片、地理位置分片、时间分片等

640?wx_fmt=png

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的这条记录,因此如果不进行数据迁移的话,就会出现记录找不到的情况。

二、中间件

主流数据库中间件设计方案:

640?wx_fmt=png

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而言,数据库本身的高可用还是需要保证的)

缺点:

  1. 通常仅支持某一种语言。例如tddl、zebra、sharding-jdbc都是使用java语言开发,因此对于使用其他语言的用户,就无法使用这些中间件。如果其他语言要使用,那么就要开发多语言客户端。
  2. 版本升级困难。因为应用使用数据源代理就是引入一个jar包的依赖,在有多个应用都对某个版本的jar包产生依赖时,一旦这个版本有bug,所有的应用都需要升级。而数据库代理升级则相对容易,因为服务是单独部署的,只要升级这个代理服务器,所有连接到这个代理的应用自然也就相当于都升级了。

三、两个问题:热点、扩容

随着业务的迅速发展、数据库就得扩容。热点问题,就是如何防止某个库某个表I/O比其他库表的频率高得多。

热点问题:

有两种路由算法,hash取模和range范围方案

 

hash取模的优点是:数据可以均匀的放到每张表中,这样此订单进行操作时,就不会有热点问题。但是产生的问题就是,数据很难迁移扩容。

range范围:一定范围内的订单,存放到一个表中,优点正好是方便扩容,却会产生热点问题。

两者结合用,就是以下的方案。首先设计0到4000万的库:

后续需要扩容再增加4000万到8000万的库

 

相关资讯

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?