我们都知道,当下我们国家的网民差不多有 10 亿以上,这是一个庞大的用户群体。

特别像每年的双十一,淘宝、天猫以及京东等电商平台,每秒钟甚至都会发生 10 万笔交易。

面对这样的用户量,对于 MySQL 数据库来讲该如何承载呢?今天我们就来聊一聊这个话题。

一、MySQL 数据库并发的问题

对于 MySQL 数据库来讲,为了提高它在面对大流量场景下的承载能力,我们通常会采用纵向扩容和横向扩容两种办法。

1. 纵向扩容

首先我们在聊一下纵向扩容。

其实,纵向扩容就是简单地将部署 MySQL 数据库的服务器硬件性能提高

举个例子:假如我们现在部署 MySQL 数据库服务器的硬件是 16 核、32 GB,为了提高 MySQL 数据库的性能,于是我们可以将其服务器的硬件升级成 32 核、64 GB,这样做可不可以提高 MySQL 数据库的性能呢?

答案是肯定的。但是这种方式是有缺点的,具体如下:

  • 服务器的硬件是有瓶颈的。假如说我们现在需要 10000 GB 的内存,你会发现市场上根本就没有相同配置的内存条(仅举例说明,不要深究),这个时候我们即使有心也很无力。

  • MySQL 数据库本身也有瓶颈。假如说 MySQL 数据库只能处理 1 亿行数据,即使给予该服务器充足的硬件配置之后,MySQL 也无法处理更多的数据。

所以,我们可以得出的结论是:纵向扩容的方式并不能很好地解决 MySQL 数据库承载能力的问题。

2. 横向扩容

第二种就是横向扩容了。

所谓的横向扩容就是通过 TCP 网络将多台 MySQL 数据库连接在一起,在外面使用 MySQL 数据库的时候就好像是使用一台数据库一样。

MySQL 数据库中称这种方式为:主从复制

相比较纵向扩容,横向扩容就更具有可拓展性。

当大流量过来的时候,理论上我们是可以通过增加 MySQL 数据库服务器的方式来进行分流,将所有的流量按照一定的比例分配到各个数据库服务器上,这样每一台 MySQL 数据库只处理分流到自己服务器上的那一部分,从而提高数据库相应的速度。

这种扩容的方式理论上讲是非常完美的,但是它也同样存在一些问题。

下面我们就来看看有什么问题呢。

MySQL 主从复制的问题

注意:这里推荐大家提前观看 第 20 篇: MySQL 中主备之间是怎样保证数据一致的呢?

上面我们讲了 MySQL 主从复制最主要的功能就是分摊流量压力。那么,在分摊压力的过程中有没有问题呢?答案是:有的。

在介绍这个问题之前,我们首先来回顾一下 MySQL 数据库主从复制的原理。

注意:Master 节点数据库(主节点)只负责更新数据;Worker 节点数据库(从节点)只负责读取数据。

例如:当 Master 节点数据库接收到一条更新请求时,MySQL 主从复制是怎么处理的呢?具体如下。

  • 第一步:Master 节点数据库更新自己节点的数据。
  • 第二步:Master 节点数据库将更新的数据写入 binlog 之中。
  • 第三步:Worker 节点数据库与 Master 节点数据库建立连接。
  • 第四步:Master 节点数据库通过 dump 线程将数据同步到 Worker 节点。
  • 第五步:Worker 节点数据库通过 IO 线程接收 Master 节点数据库发送过来的数据。
  • 第六步:Worker 节点数据库将 IO 线程接收到的数据短暂存放于 TCP 缓存之中后立即转存到中继日志之中。
  • 第七步:Worker 节点数据库中的 SQL 线程读取中继日志中的数据转存于自己的数据库之中。

我们可以看到,一般情况下,这个过程中的步骤理论上是可行的;但是,我们假设一下,在某个时间段中突然持续性地有大量的更新操作,此时 Master 节点数据库将会开始不断地更新相关的数据。

按照 MySQL 数据库主从复制的步骤来看,这些更新操作首先会在 Master 节点数据库中更新完毕之后,写入该数据库的 binlog 日志之中;然后才会通过 dump 线程传输到 Worker 节点中的。那么此时就会有一个问题。

我们知道,Master 节点数据库在接收更新数据时,是采用多线程的方式进行数据更新(当然,为了控制并发时的数据安全,InnoDB 存储引擎为我们提供了锁机制)。

锁机制可以参考:第11篇:锁机制(上)第12篇:锁机制(下)

那么这些数据通过 binlog 日志传输到从节点上时,则不能使用类似于 Master 节点上的并发方式进行提高效率。原因是主从复制的本质是将 binlog 日志中的事务在 Worker 节点上进行重放,我们知道 MySQL 数据库中的事务是有序的,就好像我们需要先学习才能高薪,而一般不会先高薪后学习;所以,Worker 节点不能单纯地使用多线程并发的方式进行主从复制,只能通过单线程的方式进行数据复制。这也是导致 MySQL 数据库主从复制延时的最主要的一个原因。

MySQL 数据库是如何解决主从复制延时问题的?

MySQL 数据库为了解决主从复制之中从节点的并发问题,提出了一个组提交的概念。

  • 把操作非一行的事务,放在一个组之中,这个组中的事务进行并发处理。
  • 把在主库上通过多线程并发方式更新的事务在从库上也进行并发处理。

为此,MySQL 5.7 为我们提供了一个 slave-parallel-type 的参数来控制并发。这个参数有两个值,分别是:DATABASELOGICAL_CLOCK

  • DATABASE:这个值的作用是告诉 MySQL 数据库按照数据库进行并发,也就是将每一个数据库的名字作为 MySQL 内部的一个虚拟 hash 表中的一个 hash 值,然后 MySQL 数据库内部通过一个叫 work 线程组成的队列分别处理不同数据库的事务,每一个 work 线程只处理某一个数据库的所有事务。

  • LOGICAL_CLOCK:这个值的作用是通过上面介绍的组的方式进行并发处理的,但是这种方式在 MySQL 5.7 中有所改变,原来数据库设计的是每一组数据在主库中更新完成之后,从库才会更新;而在 MySQL 5.7 中,prepare 的时候,从库就会立即更新,这样设计主要是为了提高从库的反应时间。

在 MySQL 后来的版本中还提供了一个新的参数 binlog-transaction-dependency-tracking ,这个参数提供了两个值,分别是:COMMIT_ORDERWRITESET

  • COMMIT_ORDER:这个参数主要根据 MySQL 同时进入 prepare和提交状态来判断是否可以进行并发。
  • WRITESET:这个参数会把 MySQL 更新的每一行都在虚拟表中生成一个 hash 值,将这些 hash 值组成一个 writeset;通过判断不同的 writeset 是否存在交集来决定是否并行。如果不存在交集则可以认为这两个事务不存在操作同一行数据,即可以并行;如果存在,则不能并行。

总结

本篇文章主要介绍 MySQL 主从复制的过程中遇到的问题,当 MySQL 数据库遇到大流量时,很有可能会遇到从库延时更新的情况;如果遇见这种情况,MySQL 数据库为从库提供了针对不同场景下的并发模式。其中,最有效的就是组提交这种模式,它是将不同次的提交分成组,如果该组跟其他组不存在交集则可以并发,如果存在则按照事务的前后顺序进行执行;进而降低了从库的延时。