反过来,由于单个服务器的性能毕竟有限,因此我们并不能无限地对关系型数据库进行纵向扩展。因此在必要条件下,我们需要考虑对关系型数据库进行横向扩展。而将AKF横向扩展模型施行在关系型数据库之上后,其各个轴的意义则如下所示:
现在就跟我来看看各个轴的含义。在AKF模型中,X轴表示的是应用可以通过部署更多的服务实例来解决扩展性的问题。而由于关系型数据库要管理数据的读写并保证数据的一致性,因此在X轴上的扩展将不能简单地通过部署额外的数据库实例来解决问题。在进行X轴扩展的时候,这些数据库实例常常拥有不同的职责并组成特定的拓扑结构。这就是数据库的Replication。
而相较于X轴,数据库AKF模型中的Y轴和Z轴则较为容易理解。AKF模型中的Y轴表示的是将所有的工作根据数据的类型或业务逻辑进行划分,而Z轴则表示根据用户的某些特性对用户的请求进行划分。这两种划分实际上都是要将数据库中的数据划分到多个数据库实例中,因此它们对应的则是数据库的Partition。
让我们先看看数据库的Replication。简单地说,数据库的Replication表示的就是将数据存储在多个数据库实例中。读请求可以在任意的数据库实例上执行,而一旦某个数据库实例上发生了数据的更新,那么这些更新将会自动复制到其它数据库实例上。在数据复制的过程中,数据源被称为Master,而目标实例则被称为Slave。这两个角色并不是互斥的:在一些较为复杂的拓扑结构中,一个数据库实例可能既是Master,又是Slave。
在关系型数据库的Replication中,最为常见的拓扑模型就是简单的Master-Slave模型。在该模型中,对数据的读取可以在任意的数据库实例上完成。而在需要对数据进行更新的时候,数据将只能写入特定的数据库实例。此时这些数据的更改将沿着单一的方向从Master向Slave进行传递:
在该模型中,数据读取的工作是由Master和Slave共同处理的。因此在上图中,每个数据库的读负载将是原来的一半左右。但是在写入时,Master和Slave都需要执行一次写操作,因此各个数据库实例的写负载并没有降低。如果读负载逐渐增大,我们还可以加入更多的Slave节点以分担读负载:
相信您现在已经清楚了,关系型数据库的横向扩展主要是通过加入一系列数据库实例来分担读负载来完成的。但是有一点需要注意的是,这种写入传递关系是靠Master和Slave中的一个独立的线程来完成的。也就是说,一个Master拥有多少个Slave,它的内部就需要维持多少个线程来完成对属于它的Slave的更新。由于在一个大型应用中常常可能包含上百个Slave实例,因此将这些Slave都归于同一个Master将导致Master的性能急剧下降。
其中一个解决方法就是将其中的某些Slave转化为其它Slave的Master,并将它们组织成为一个树状结构:
但是Master-Slave模型拥有一个缺点,那就是有单点失效的危险。一旦作为Master的数据库实例失效了,那么整个数据库系统,至少是以该Master节点为根的子系统将会失效。
而解决该问题的一种方法就是使用多Master的Replication模型。在该模型中,每个Master数据库实例除了可以将数据同步给各个Slave之外,还可以将数据同步给其它的Master:
在这种情况下,我们避免了单点失效的问题。但是如果两个数据库实例对同一份数据更新,那么它们将产生数据冲突。当然,我们可以通过将对数据的划分为毫不相干的多个子集并由每个Master节点负责某个特定子集的更新的方式来防止数据冲突。
从上图中可以看到,用户对数据的写入会根据特定条件来分配到不同的数据库实例上。接下来,这些写入会同步到其它实例上,从而保持数据的一致性。但是既然我们能将这些数据独立地切割为各个子集,那么我们为什么不去尝试一下数据库的Partition呢?
简单地说,数据库的Partition就是将数据库中需要记录的数据划分为一系列子集,并由不同的数据库实例来记录这些数据子集所包含的数据。通过这种方法,对数据的读取以及写入负载都会根据数据所在的数据库实例来进行划分。而这也就是数据库沿AKF扩展模型的Y轴进行横向扩展的方法。
在执行数据库的Partition时,数据库原有的数据将被切分到不同的数据库实例中。每个数据库实例将只包含原数据库中几个表的数据,从而将对整个数据库的访问切分到不同的数据库实例中:
但是在某些情况下,对数据库中的数据按表切分并不能解决问题。切分完毕后的某个数据库实例仍然可能承担了过多的负载。那么此时我们就需要将该数据库再次切分。只是这次我们所切分的是数据库中的数据行:
在这种情况下,我们在对数据进行操作之前首先需要执行一次计算来决定数据所在的数据库实例。
然而数据库的Partition并不是没有缺点。最常见的问题就是我们不能通过同一条SQL语句操作不同数据库实例中记录的数据。因此在决定对数据库进行切分之前,您首先需要仔细地检查各个表之间的关系,并确认被分割到不同数据库中的各个表没有过多的关联操作。