zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

数据库分库分表平滑扩容方案

2023-03-15 22:01:12 时间

背景

参考博客1给出了一种所谓的平滑帅气的秒级扩容的架构方案,但我个人却认为,这个看似没有什么问题的方案在实际中几乎没什么用处,业界也几乎不会用这种方案来进行扩容(分库分表)。为了便于说明这一点,本文先简单回顾下该方案,然后分析该方案为什么没有用,最后给出三种业界广泛使用的分库分表的平滑扩容方案。

双主扩容方案回顾

如图所示,假设当前用户库user分布在两个实例上,ip0和ip1,服务层通过用户标识uid取模的方式进行寻库路由,模2余0的访问ip0上的user库,模2余1的访问ip1上的user库。

而一方面,互联网架构需要保证数据库高可用,常见的一种方式是,使用双主同步+keepalived+虚ip的方式保证数据库的可用性:

如上图所示,两个相互同步的主库使用相同的虚ip(vip),当前的主库挂掉之后,虚ip自动漂移到另一个主库,整个过程对调用方透明:

由此可知,在实际的架构中,既有水平切分,又有高可用保证,所以实际的数据库架构是这样的:

现在假设每个库1亿数据量,如何平滑扩容,增加实例数,降低单库数据量呢?三个简单步骤搞定。

第一步,修改配置

主要修改两处:

a)数据库实例所在的机器做双虚ip,原来%2=0的库是虚ip0,现在增加一个虚ip00,%2=1的另一个库同理

b)修改服务的配置(不管是在配置文件里,还是在配置中心),将2个库的数据库配置,改为4个库的数据库配置,修改的时候要注意旧库与辛苦的映射关系

%2=0的库,会变为%4=0与%4=2;

%2=1的部分,会变为%4=1与%4=3;

这样修改是为了保证,拆分后依然能够路由到正确的数据。

第二步,reload配置,实例扩容

服务层reload配置,reload可能是这么几种方式:

a)比较原始的,重启服务,读新的配置文件

b)高级一点的,配置中心给服务发信号,重读配置文件,重新初始化数据库连接池

不管哪种方式,reload之后,数据库的实例扩容就完成了,原来是2个数据库实例提供服务,现在变为4个数据库实例提供服务,这个过程一般可以在秒级完成。

整个过程可以逐步重启,对服务的正确性和可用性完全没有影响

a)即使%2寻库和%4寻库同时存在,也不影响数据的正确性,因为此时仍然是双主数据同步的

b)服务reload之前是不对外提供服务的,冗余的服务能够保证高可用

完成了实例的扩展,会发现每个数据库的数据量依然没有下降,所以第三个步骤还要做一些收尾工作。

第三步,收尾工作,数据收缩

有这些一些收尾工作

a)把双虚ip修改回单虚ip

b)解除旧的双主同步,让成对库的数据不再同步增加

c)增加新的双主同步,保证高可用

d)删除掉冗余数据,例如:ip0里%4=2的数据全部干掉,只为%4=0的数据提供服务啦

这样下来,每个库的数据量就降为原来的一半数据收缩完成

问题分析

本质上,该扩容方案利用的是双主同步机制,即在配置reload过程中,写到新主库的数据也会同步到原来的主库,从而避免了扩容过程中对系统业务的影响。但个人认为该方案存在以下三个致命的问题:

问题一:数据库成倍扩容的方式难以满足业务需要。按照dba的建议,每张数据表的数据量建议在1000w条以下(实际上,在索引设置得当的情况下,可以达到4000w条,具体原理参见参考博客8)。实际上,当我们需要进行扩容的时候,往往是当该表的数据量已经快要接近性能瓶颈的时候。在这种情形下,如果只是进行成倍的扩容,那么扩容后每个库的数据量只是从1000w变成500w,这意味着很可能很快就又会到达性能瓶颈,从而需要再次扩容。要知道,每次数据库扩容都是一次风险性很高的操作,扩容过程中操作不慎,很容易导致数据丢失、业务受影响等问题。所以这种成倍扩容的方式必然会增加故障的风险。而比较理想的扩容方式是:一次扩容到位,避免频繁扩容

问题二:基于双主同步+keepalived+虚ip的方式需要额外解决脑裂问题。关于脑裂问题及其可能的解决方案参见参考博客2,脑裂问题增加了系统的复杂性。

问题三:双主同步机制需要避免自增id的冲突问题。 由于在扩容过程中,上层业务可能同时写两个主库,而如果主库之间id有冲突,则会导致主库同步到从库失败,从而影响业务。参考博客4给出的一种方案是使用奇偶值作为自增id。此外,在最后的收尾工作中,为保证高可用而对每个主库增加的备用主库也需要注意自增id的奇偶性:ip0的备用主库需要使用奇数id,而ip00需要使用偶数id,ip1的备用主库需要使用奇数id,ip11的备用主库需要使用偶数id。在这个过程中,一旦没有遵守这个约束,就会引发灾难。个人认为这个限制导致互联网行业实际上很少使用双主同步机制,而是使用主从同步机制的重要原因之一。主从同步通过MHA等开源工具来完成主库故障时,自动将从库升级为主库并完成主从切换的操作。

几种可行的扩容方案

到此可知,相比于双主同步机制,业界更多使用的是主从同步机制。本文接着介绍在主从同步机制下,三种可行的平滑扩容方案。

一、基于主从同步的扩容方案

核心思想是,启动更多的从服务器(取决于希望扩容的服务器量),当从服务器从主服务器同步完成之后,将所有从服务器升级为主服务器,然后调整路由规则,再根据路由规则删除每个主服务器中的冗余数据。详见下图:

 这种方案的优点是扩容简单,直接利用mysql自带的主从同步能力,由于没有双主id的限制,可以一次进行任意倍数的扩容;缺点是,该方案本质上是利用mysql的主从同步能力来进行数据迁移,同步的很多数据到最后都需要被删除。

二、全局增量分区局部散列扩容方案

https://blog.csdn.net/bluishglc/article/details/7970268 一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案

这种方案的优点是扩容简单,缺点是需要在一开始设计数据库时就按照这样的方式去做,对于已经存在的老旧系统则不太适用

三、基于数据迁移的扩容方案

顾名思义,这种方案就是每次申请新的数据库集群,然后根据新的路由规则将老数据库集群中的数据分散迁移到新数据库集群中,如下图所示:

上图中间件1负责迁移某个时间节点以前的数据,这个时间节点以后的数据则同步到中间件2中。等中间件1同步完成之后,中间件2同步过程的某个时间点,服务器切换数据源即可。

这里为什么要分两批来迁移呢?只用一个中间件来迁移貌似也可以?这里采用两段的主要目的是为了便于确定什么时候可以切换数据源。因为中间件指定了时间点, 所以当中间件1执行完成之后,表明历史的大部分数据已经迁移完成。在中间件1迁移的过程中,所有对源数据库的写入操作都会同步到中间件2中,当中间件1执行完成之后,在中间件2执行过程中,就可以根据中间件2的同步情况来进行数据源的切换。

四、三种方案对比

相比方案一,方案三的优势是,迁移的数据都是有效的,而劣势是需要额外的中间件来进行支撑;相比方案二,方案三的优点是可以对老旧系统进行迁移。

综上所述,在设计新系统时,建议一开始就按照方案二来进行设计;而对于没有强大中间件支撑的团队,推荐使用方案一来进行扩容;而对于有强大中间件支撑的团队,则建议采用方案三。

参考博客:

1、https://www.cnblogs.com/codeon/p/8288029.html  数据库秒级平滑扩容的架构方案

2、https://cloud.tencent.com/developer/article/1027323 split-brain 脑裂问题(Keepalived)

3、https://www.jianshu.com/p/7240308a1cb5 修改Mysql的Auto_increment_increment

4、https://www.cnblogs.com/ygqygq2/p/6045279.html  MySQL双主(主主)架构方案

5、https://www.cnblogs.com/gaogao67/p/10931313.html MySQL双主结构优缺点

6、https://www.cnblogs.com/--smile/p/11475380.html MHA基础、原理、架构、工具介绍

7、https://www.cnblogs.com/zhoufly-blog/p/12363582.html MySQL异步复制、半同步复制

8、https://blog.csdn.net/Saintyyu/article/details/100114372  什么MySQL的索引要使用B+树,而不是其它树?比如B树?

8. https://zhuanlan.zhihu.com/p/96212530 mysql主从复制原理

9. https://www.cnblogs.com/dukuan/p/10120820.html MySQL主从复制原理

10. https://blog.51cto.com/13691477/2149064 MySQL主从复制原理深入讲解

11. http://blog.chinaunix.net/uid-20639775-id-3337509.html MYSQL高可用方案探究(总结)

12. https://www.douban.com/note/706714492/ 五大常见的MySQL高可用方案

13. https://cloud.tencent.com/developer/article/1056162 美团点评MySQL数据库高可用架构从MMM到MHA+Zebra以及MHA+Proxy的演进

14. https://blog.csdn.net/bluishglc/article/details/7970268 一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案