MySQL之从单机到集群

写在前面

本文一起看下MySQL是单机存在的问题,以及为了解决这些问题所提出的各种解决方案。

1:从单机到集群

并非业务发展初期我们就直接使用集群来支撑业务,而是简单的使用单机版本,但是随着业务的发展,单机的各种问题就会一点一点的暴露出来,那么单机都有哪些问题呢,主要如下:

1:容量有限,扩容困难
    myisam最大256T,Innodb最大64T
2:读写压力过大,即QPS过高时,会严重影响业务
3:可用性不足,宕机导致服务不可用

自然对于每个问题,目前都有对应的解决方案,分别看下。

1:容量有限,扩容困难 -》 分库分表
2:读写压力过大,即QPS过高时,会严重影响业务 -》主从复制解决
3:可用性不足,宕机导致服务不可用 -》故障转移,主从切换

参考下图:
MySQL之从单机到集群_第1张图片

接下来分别看下。

2:主从复制

通过主从复制解决读写压力大问题。

2.1:主从复制发展历史

1:2000年,3.23版本引入
2:2002年,4.0.2版本引入relay log,分离处io线程和sql线程
3:2010年,引入半同步复制 。
4:2016年,5.7版本引入了mrg(MySQL group replication)

2.2:主从复制的原理

主从复制需要依赖于binlog文件,binlog文件由MySQL server层负责写入,作用就是数据备份以及复制,从库使用io线程从主库中拉取日志并将日志写到relay log中,sql thread负责重放binlog中的修改到从库,从而实现数据复制,参考下图:
MySQL之从单机到集群_第2张图片

其中binlog支持的格式 有三种,statement,row,mixed,其中statement是记录原始的执行sql,这种方式可能会导致从库同步时的数据不一致,不建议使用,row会详细记录影响到每一行,可以保证同步数据的正确性,mix混合二者。生产环境建议使用row。

以上是传统的异步复制以及半同步复制,MySQL在5.7版本还引入了mrg,即多节点组成一个集群,并且多节点支持同时写入,支持动态伸缩,内部使用paxos分布式协议,但由于其复杂性,以及稳定性不足,bug多等问题,目前实际使用的用户还并不多,所以MySQL的复制发展历程大概为主从异步复制->主从半同步复制->mgr,其中主从异步复制和主从半同步复制都是基于主从的架构的,但mgr可以理解为多主架构了,支持多写,但是要解决的问题都是一致的,即解决读写压力大问题。

这里mgr可以作为了解的只是,重点还是顺着主从复制这条线来学习,因为这才是当前的主流使用方式。

2.3:主从复制不足

1:主从复制延迟
2:无法解决高可用
3:应用测需要配合读写分离框架,增加编码的复杂度

2.4:主从复制应用在业务中

方案1:直接在程序中配置多个数据源,然后在service层根据操作的类型选择不同的数据源,写走主,读走从。
方案2:基于AbstractRoutingDatasource,自定义路由数据源,写走主,读走从。

以上方案1和方案2,都存在写完读问题,比如在一个事务中先走主库更新了id=1的数据,假设修改同步到从库需要10ms,但3ms后程序继续执行走从库读取了id=1的数据,此时就会出现查不到的情况这就比较奇怪了,问题的根源在于在一个事务中在发生了写操作之后还切换数据源,如果是发生写之后不再进行切换数据源的动作,写完读的问题就解决了,这就引出了我们的方案3.

方案3:shardingsphere-jdbc master-slave 。

以上的方案对业务代码都是有侵入性的,想要解决这个问题可以考虑方案4。

方案4:数据库中间件,mycat等。
这种方案,不需要修改应用程序,但部署和运维的成本比较高,且有额外的机器成本。

3:高可用

3.1:为什么要高可用

提供failover的能力,当主节点宕机时,整个集群只会抖一下之后就恢复正常,这个抖一下其实就是主从切换。这里变为新主的节点又可以分为冷备份热备份,冷备份即平时是用不到的,等到主挂了之后顶上去,热备份就是平时也对外提供读服务,在主宕机之后变为新主对外提供读写服务。

冷备份就好像漂亮国副总统,平时啥也不干,充当总统的小跟班,替总统发个言什么的,但是当总统挂了或者被弹劾下台之后,副总统就会变为总统,瞬间拥有无限大的权利。

3.2:高可用常见的策略

根据系统对高可用要求级别的不同有不同的策略,主要如下:

策略1:多个实例不在一个主机/机架上
策略2:跨机房部署
策略3:两地三中心部署(机房距离800公里以上)

一个机房一般会有几十个,甚至上百,上千个机架,相同的机架可能使用同一根网线和电源,而一个机架上可以有很多的刀片服务器主机,每个刀片服务器主机又可以切分处很多的虚拟主机,再者机房可以分布在不同的区域,如下图:
MySQL之从单机到集群_第3张图片

3.2:高可用的定义和指标

高可用的可用性也有高有低,该如何描述呢,自然是单位时间内提供服务的时长,这个时间越长,则可用性就越好,主要有如下专业术语:

SLI: service level indicator,即单位时间内请求得到正常响应的百分比
SLO:service level object,是为了更好的量化SLO而定义的指标,表示单位时间内服务可用时长的百分比,也就是我们常见到的几个9
SLA:servcei level aggrement,是一个保证达到某SLO的承诺。服务提供企业,如阿里云,在与客户端签订协议时,保证达到某个SLO,这个协议的SLA,当达不到SLO时,做出怎样的赔偿等,如下模拟协议(瞎写的啊!):
    我,阿里云承诺,如果服务SLO达不到2个9,则赔偿客户《大帅哥公司》一百万元人民币,特此声明。

所以这里最重要的其实就是SLO了,其通过几个9来量化服务的可用性,如下:

1年=365天=8,760小时

2个9(99%):
    8,760小时*(1-99%)=87.6小时,即每年有87.6小时服务是不可用的
3个9:
    8,760小时*(1-99.9%)=8.76小时,即每年有8.76小时服务是不可用的
4个9:
    8,760小时*(1-99.99%)=0.876小时=52.6分钟,即每年有52.6分钟服务是不可用的

几个9的计算方法为1-百分比,然后取以10为底的对数,之后取绝对值,如99%就是lg(1-0.99)=|-2|=2,所以就是2个9。99.9%就是lg(1-0.999)=|-3|=3,所以就是3个9。

3.2:高可用的方案

3.2.1:手动切换

当出现问题时,手动该配置,手动修改程序,完成切换,这种方式缺点如下:

1:比较废人
2:修复时间不可控,影响业务
3:可能导致数据不一致

其中3是比较严重的一个问题,因为我们无法确认当前手动提升为主库的从库是否完成了和主库的数据同步,如果是没有则会造成数据丢失。即出现了数据的不一致。

3.2.2:MHA

master high available,是一个MySQL的故障主从切换的高可用软件,优点如下:

1:30s内完成主从故障切换
2:不需人工参与,完全自动化
3:基于ssh通过自动拉取宕机主节点binlog,保证数据的一致性

不足:

1:需要至少3个节点
2:需要配置ssh信息(应该不算不足吧!)

3.2.3:MGR

多节点集群,基于paxos协议,多节点数据复制,每个节点都有一份完整的数据,优点如下:

1:灵活度高
    支持单主和多主,单主故障能够自动选注,多主支持多节点同时写入,提高写能力
2:伸缩(扩展)性强
    当加入新节点,自动加入集群并同步数据,直到数据同步完成
3:多写,能够提高数据写能力
4:容错
    有节点宕机,不影响集群,且能防止脑裂

使用的场景:

1:希望拥有数据弹性复制的场景 
    如完整的数据原来在3个节点上,现在希望在5个节点上
2:分片复制

其中1如下图:
MySQL之从单机到集群_第4张图片

2如下图:
在这里插入图片描述

3.2.4:mysql innodb cluster

对mgr的改进,增加了mysql router作为客户端流量的入口,结构如下图:
MySQL之从单机到集群_第5张图片
组件如下:

mysql shell:
    管理MySQL cluster
mysql router:
    数据库中间件角色,代理mgr集群,提供负载均衡,连接的故障转移,可以认为MySQL router是为mgr量身打造的组件,如果考虑或正在使用mgr的话一定要配合MySQL router。

4:分库分表

通过分库分表来解决容量限制(当然不仅仅解决此问题)的问题。

4.1:为什么要做数据库拆分

1:单实例容量的限制
2:当单实例数据过多时,导致B+树索引的层数多,查询性能降低
3:数据过多备份,数据恢复难度大(可能不可控)
4:成为高并发系统的性能瓶颈

比如一定会执行如下的操作:

1:ddl,比如加一列,加索引,数据多,操作时长将不可控
2:系统越来越慢时,主从延迟会越来越大,系统越来越不稳定

因此,既然都是因为数据而导致以上种种的问题,那么我们就需要让数据变小,怎么变小呢,当然不能真的删除,所以只能,分库分表。

4.2:都有哪些拆分方式

任何领域一般都会有一些指导原则(简单将就是避坑指南),如编程领域就有面向对象的设计原则 和gof23种设计模式 。这里的指导原则(简单将就是避坑指南)是扩展立方体,分为x,y,z三个轴,如下图:
MySQL之从单机到集群_第6张图片
分别如下:

X轴:
    将系统复制多份,如前面的主从复制,MGR等属于X轴的范畴
Y轴:
    按照业务拆分,因为Y轴是垂直的所以也叫做垂直拆分(我猜的)
Z轴:
    将数据分片,因为Z轴是水平的所以也叫做水平拆分(我猜的).

4.2.1:垂直拆分

垂直拆分即扩展立方体模型的Y轴,按照业务进行拆分,具体到数据库领域分为拆库和拆表。

  • 拆库
    将一个数据库按照业务拆分成多个数据库,在以下的场景中可能使用到这种方式:
1:一个大的单体项目逐渐转微服务,一般微服务的第一步就是要拆库(如果是库在在一起的,那是假的微服务)
2:容量限制,需要拆分
3:流量比较平均,希望打散流量

因为拆库之后对系统的原有功能会产生影响,比如之前的多表查询中的表可能会被分到不同的库中,所以在进行拆分之前要做好如下的准备工作:

1:评估影响的范围,以及修改方案
2:新数据库的准备,配置,数据的迁移
3:修改后程序的上线,数据库的上线
4:上线后出现问题的应急方案
  • 拆表
    拆表是一张大表,通过拆分列分成若干张小表,如下所示:
    MySQL之从单机到集群_第7张图片

因为此时表结构都有了很大的变化,所以这种拆分方式的影响更大,可能无异于程序的重构,要慎用,最好是不用。

4.2.2:水平拆分

对应的是扩展立方体的Z轴,即分片,即将数据分散成多份存储,这样就可以将分散流量,并且降低单表的数据量,提升查询速度,水平拆分分为如下三种:

1:分库
    将表的数据分成若干份,分散到若干个数据中
2:分表
    将表的数据分散到多个表中,每个表存储一部分数据,但所有存储分片数据的表还都是在一个数据库中
3:分库分表
    结合分库和分表,即存储分片数据的表,即有在同一个库中的,也有在新库中的

水平拆分适用于如下场景:

1:数据量过大,单表索引树过高,IO次数多,导致查询速度慢
2:数据量大,导致数据备份难度大,导致主从延迟大

简单的分片我们就可以按照ID取模的方式来进行,如下图示例:
MySQL之从单机到集群_第8张图片

当然具体如何分片,需要通过具体的业务场景来分析,比如报表分析,一般是按照一定的时间范围来统计的,所以此时就可以考虑按照时间来分片。

4.2.2.1:分库还是分表?

如果是条件允许,建议直接使用分库,而非分表,原因如下:

1:分表后最终所有的表还是在同一个数据库上,依然共享同一台服务器的资源,而其中IO资源随着数据量的增大,很有可能成为系统瓶颈,但是如果是分库的话可以很好的规避这个问题,提高系统的并行数据处理能力
2:但事无绝对,如果是IO资源自然充足,则可以优先考虑分表而非,分库,这样对业务代码的影响相对减小,并且单个数据库的维护成本也更低

5:公司业务实践

5.1:表单的垂直拆分

介绍下业务背景,我司有一个表单产品,即低代码平台。当通过后台设计一个表单后,就会在数据库中生成一个对应的表,如下图:
MySQL之从单机到集群_第9张图片

其中表结构可能如下:

CREATE TABLE `xxx_db22_00sqhcwc` (
  `BUSINESS_ID` varchar(36) NOT NULL,
  `BUSINESS_DATA_JSON` text,
  `2c98843980b276790180b62bf9fe16ed` varchar(36) DEFAULT NULL,
  `2c98843980b276790180b62bf9fe16ee` varchar(255) DEFAULT NULL,
   ...
  `cae6b1f90a7e4300931ea582cb15e139` text,
  `COMM_CREATE_TIME` datetime DEFAULT NULL,
  `fe14ebad5aec42fcba56e82f26a59bbd` text,
  `labelIds` text,
  `MODIFY_TIME` datetime DEFAULT NULL,
  `AREA_CODE` varchar(10) DEFAULT '0',
  PRIMARY KEY (`BUSINESS_ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

其中的字段BUSINESS_DATA_JSON是用来存储数据的原始信息,使用json格式存储,像下边这样:

[
    {
        "df5abbcc7bfd402e88a58ebacb5fdf7b": {
            "row1": {
                "cell_fe14ebad5aec42fcba56e82f26a59bbd": {
                    "photo": [],
                    "allOps": [
                        {
                            "key": 0,
                            "name": "合格",
                            "selected": false,
                            "score": 0
                        },
                        ...
                    ],
                    "curr": {
                        "key": "",
                        "name": "请选择",
                        "score": 0
                    }
                },
                "cell_cae6b1f90a7e4300931ea582cb15e139": {
                    "photo": [],
                    "allOps": [
                        {
                            "key": 0,
                            "name": "合格",
                            "selected": false,
                            "score": 0
                        },
                        ...
                    ],
                    "curr": {
                        "key": "",
                        "name": "请选择",
                        "score": 0
                    }
                }
            }
        }
    }
]

如果是表单比较复杂,比如有大量的拍照控件,或者是用户需要录入大量的信息时,该json就会变大,数据大小从几k到几十k不等。而我们知道innodb存储引擎是索引组织表,即其数据就是存储在索引中的,而当行数据比较大时,每个B+树的数据页能够存储的数据就会变少,甚至出现跨页存储的情况(比如数据json大于16k时一个页放不下就会出现这种情况了),而当B+树的数据页变多之后就会导致B+树的层数变多(不断的分裂而变高),直接导致的问题就是查询数据时的IO次数变多,因此会直接影响到查询数据的性能。基于此进行了如下的拆分,将主要的列分到单独的表中,如下:

CREATE TABLE `xxx_db22_00sqhcwc_read` (
  `BUSINESS_ID` varchar(36) NOT NULL,
  `2c98843980b276790180b62bf9fe16ed` varchar(36) DEFAULT NULL,
  `2c98843980b276790180b62bf9fe16ee` varchar(255) DEFAULT NULL,
   ...
  `cae6b1f90a7e4300931ea582cb15e139` text,
  PRIMARY KEY (`BUSINESS_ID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

_read表中只保留了主要的字段,这样,当执行数据查询时就可以通过_read表来查询了。看到这里,不知道你有没有这样的疑问,你前面说垂直拆分,特别是垂直拆分的分表对系统的影响巨大,最好不要使用,这里确有用到了这种方式,你们是怎么解决的呢?是这样子的,因为涉及到的业务只是数据查询,并没有复杂的业务处理,所以只需要简单的定义多数据源 就可以解决问题了。

5.2:统计表的水平拆分

之前一家公司是做智能路由器的,当时公司大概有100万个智能路由器设备,设备每天会定时的上报数据,每周产生的数据量大概是在一亿条,基于此,定义了以周为单位的分片逻辑,定义了非常多的周表,像下图:
在这里插入图片描述

对于跨周的数据统计,只需要多次查询后在业务代码中进一步处理即可。

6:如何迁移数据

迁移就好像给高速飞行的飞机更换某个损坏的引擎,是一个非常容易出问题的操作,要慎重和小心!!!

一旦引入了分库分表,不管是垂直拆分还是水平拆分,肯定就要涉及到数据的迁移,本部分就一起来看下都有哪些数据迁移方式,每种迁移方式的优点和缺点是什么,在实际工作中我们又应该如何来进行选择。

6.1:全量

全量方式是通过mysqldump 导出全量,然后在导入到新库中去,如果是异构的数据库则需要开发程序完成导入。其优点和缺点如下:

优点:就是简单
缺点:操作时间长,可能会导致比较长时间的停机,此期间系统将无法对外提供服务

适用的场景:

1:数据不是特别的多,用户使用低峰期就可以完成迁移,对用户的影响很小
2:非核心业务,一段时间的停机对用户无影响

6.2:全量+增量

这种方式不同于全量的地方在于,在真正的停机同步数据之前,先按照时间,同步历史数据,然后在最后一天同步新产生的数据以及修改更新的数据,这样停机期间需要同步的数据就大大减小。优点和缺点如下:

优点:停机时间可控,相对较短,对用户的影响小
缺点:操作过程相对复杂,且最后一步到底哪些数据更新了或者是删除了,如果是没有明显的字段来标示,这类数据是无法迁移的

6.3:binlog+全量+增量

这种方式需要一个中间件来模拟MySQL的从库,从主库拉取binlog,完成数据同步,这种方式需要中间件的支持,当前的中间件有shardingsphpere-scaler。优点和缺点如下:

优点:
    1:支持异构系统
    2:简化操作复杂度,复杂度已经由中间件本身实现
    3:停机时间可控,一般会非常短,因为最终只需要同步一小段的binglog,只需要追上主库的binlog即可
缺点:

7:框架和中间件

当我们分库分表之后,一般有如下的方式来适配新的数据库结构:

1:自己写程序
    这种方式开发成本高,并且容易有比较多的bug,优点是可定制性强,如果是人力资源充足的话可以考虑这种
2:框架
    即别人帮我们写好的程序,直接用在项目中
3:中间件
    介于应用和数据库之间,起一个中介的作用,一般是一个模拟的MySQL server。
4:分布式数据库
    即数据库本身就支持分库分表等各种分布式方案

以上的1一般我们可以不予考虑,234的关系如下图:
MySQL之从单机到集群_第10张图片

分别看下每种方案相关的技术都有哪些。

7.1:框架

7.1.1:TDDL

淘宝自用数据库框架,开源过一段时间,目前闭源。

7.1.2:apache shardingsphere-jdbc

shardingsphere 项目三个主要功能中的一个,如下图:
MySQL之从单机到集群_第11张图片

目前这种方式也是业界采用最多的一种方式,占比应该在百分之九十以上。具体原因我们后面分析。

7.2:中间件

中间件相对来说就比较多了,主要有如下几种:

1:cobar(java)
    阿里巴巴团队开发,目前已停止维护
2:mycat(java)
    基于cobar二次开发,解决了cobar存在的一些问题
3:OneProxy(C)
    某数据库界大牛基于C语言开发分布式数据库中间件,专注于性能和稳定性,商业收费
4:vitnesss(GO)
    youtube一款分布式数据库中间件,架构非常复杂
5:altas(GO)
    360团队开发,稳定性欠佳
6:kingshard(GO)
    360 altas团队某成员基于go开发,稳定性欠佳
6:ShardingSphere-proxy(java)
    后起之秀,当前在维护升级中。

7.3:分布式数据库

TIDB(开源),polardb,oceanbase,等。

7.4:哪种方案用到多?

在前面提到了,分布式数据库框架方案是用的最多的,在百分之九十以上,我司就是,下面我们就看下如果要在当前的线上系统引入相关方案的话都需要考虑哪些因素:

1:分布式数据库框架
    如果想要确定这种方案的话,可能只需要几个架构师,项目坐在一起讨论一下即可,之后只会带来一定的开发和测试工作量。
2:分布式数据库中间件
    如果是要确定这种方案的话,就相对要麻烦些了,因为涉及到后期的维护问题,所以就需要几个部门(DBA部门,开发部门,运维部门等)坐在一起讨论下,在系统上线后由哪个部门负责维护,另外既然是引入中间件肯定是需要额外的机器来部署的,因此会产生机器的成本,并且多了一层,也会降低数据操作的性能,这个问题也需要在实际的环境中考虑和验证。
3:分布式数据库
    这种方式就是直接更换数据库了,是动静最大的一种,就需要公司的CTO,各部门的领导,甚至公司的高层领导,共同讨论,得出结果。

因此,从以上各种方案的考虑因素也可以很轻松的得出,为什么选择分布式数据库框架方案成为主流了。

写在后面

多知道一点

数据的分类管理

不同的数据的管理策略是不同的,因此我们就需要有一套自己的数据分类管理,这里一起来看下。

1:热数据
    对于比较新的数据(如一周内),被查询和修改的概率很高,这种数据我们叫做热数据,我们在将其存储的数据库的同时也加载到内存中。
2:温数据
    对于相对比较旧的数据(如一周到半年),被查询和修改的概率较低,这种数据我们叫做温数据,一般只需要存储在数据库中提供查询即可。
3:冷数据
    对于比较旧的数据(如半年以上到2年),使用廉价的磁盘配合MySQL的archive类的存储引擎,如tokudb(压缩比在几十分之一),几乎不会查询,所以不提供直接的查询,如果是用户需要,则可以通过工单的形式,由客服人员提供数据,然后将这些数据从数据库中物理删除
4:冰数据
    对于非常旧的数据(如2年以上),直接使用硬盘,磁带等三方独立介质存储,并且不提供任何的查询方式,当然所有数据从数据中物理删除。

参考文章列表

MyCat是什么? 。

常用数据库中间件汇总 。

一文读懂什么是SLI、SLO、SLA 。

Apache Sofware Foundation 下载apache相关软件

archive.apache.org 下载apache相关软件

分布式数据库中间件—TDDL的使用介绍 。

为什么几乎所有的开源数据库中间件都是国内公司开源的?并且几乎都停止了更新? 。

你可能感兴趣的:(Java高级开发进阶教程,MySQL,分库分表,主从同步,扩展立方体模型)