> For the complete documentation index, see [llms.txt](https://maxwell.gitbook.io/way-to-architect/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://maxwell.gitbook.io/way-to-architect/shu-ju-ku/mysql/mysqlzhong-de-sharding/fen-ku-fen-biao-hou-de-ping-hua-kuo-rong.md).

# 分库分表后的平滑扩容

数据库架构演进过程：![](https://1360295607-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LATEhlAPqBUOhHPcpUO%2F-LOTWA5H1PWPDm3hbd3k%2F-LOTWT98xmjm34hxVwb2%2F%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-10-10%20%E4%B8%8B%E5%8D%8811.03.43.png?generation=1539184129064702\&alt=media)

其中，双Master方案：当主库挂掉的时候，虚ip自动漂移到另一个主库，整个过程对调用方透明，可以保证数据库的高可用。

在根据Hash取模的方案进行水平分库分表后，扩容其实是一件非常麻烦的事。如果是采用其他路由规则，比如按时间/范围/城市等，则很简单，直接加库就可以。

## 停服务方案

假设原来分了x个库，现在要扩展到y个库，则实施方案如下：

（1）站点挂一个公告“为了为广大用户提供更好的服务，本站点/游戏将在今晚00:00-2:00之间升级，届时将不能登录，用户周知”

（2）停服务

（3）新建y个库，做好高可用

（4）数据迁移，重新分布，写一个数据迁移程序，从x个库里导入到y个库里，路由规则由%x升级为%y

（5）修改服务配置，原来x行配置升级为y行

（6）重启服务，连接新库重新对外提供服务

整个过程中，**最耗时的是第四步数据迁移。**

**回滚方案**：如果数据迁移失败，或者迁移后测试失败，则将配置改回x库，恢复服务，改天再挂公告。

**方案优点**：简单

**方案缺点**：

（1）停服务，不高可用

（2）技术同学压力大，所有工作要在规定时间内做完，根据经验，压力越大约容易出错（这一点很致命）

（3）如果有问题第一时间没检查出来，启动了服务，运行一段时间后再发现有问题，难以回滚，需要回档，可能会丢失一部分数据

有没有更平滑的方案呢？

## 平滑扩容方案

假设原来分2个库，现在要扩为4个库，实施方案如下：

**第一步：修改配置**

a）数据库实例所在的机器做双虚ip，原来%2=0的库是虚ip0，现在增加一个虚ip00，%2=1的库时ip1，现在增加一个虚ip11；

b）修改应用（数据库使用方）的数据库配置（保证拆分后依然能够路由到正确的数据），如下图所示：

* * 将2个库的数据库配置，改为4个库的数据库配置
  * %4=0与%4=2的数据落在原来%2=0所在的库，%4=1与%4=3的数据落在原来%2=1所在的库

这步操作如下图所示：![](https://1360295607-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LATEhlAPqBUOhHPcpUO%2F-LOTTMKzk7QE-qfGRdbF%2F-LOTTcwj8GnXLVUeRApJ%2F%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-10-10%20%E4%B8%8B%E5%8D%8810.47.32.png?generation=1539183383651922\&alt=media)**第二步：reload应用（数据库使用方）配置**

服务层reload配置，reload可能是这么几种方式：

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

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

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

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

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

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

**第三步：数据收缩**

a）把双虚ip修改回单虚ip；

b）解除旧的双主同步，让成对库的数据不再同步增加；

![](https://1360295607-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LATEhlAPqBUOhHPcpUO%2F-LOTTMKzk7QE-qfGRdbF%2F-LOTTcwlr__D38vvmXZi%2F%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202018-10-10%20%E4%B8%8B%E5%8D%8810.49.52.png?generation=1539183385466042\&alt=media)c）增加新的双主同步（对应上图，就是为user0-master1/user0-master2/user1-master1/user1-master2分别增加同步主库）

d）删除掉冗余数据（对应上图，就是user0-master1中删除%4=2的数据，user0-master2中删除%4=0的数据，user1-master1中删除%4=3的数据，user1-master2中删除%4=1的数据）

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

该方案能够实现n库扩2n库的秒级、平滑扩容，增加数据库服务能力，降低单库一半的数据量。

**总结步骤：对原来的每个分表，双主均做双虚IP，然后修改应用数据库配置以及路由规则，然后先将双虚IP改为单虚IP，解除双主关系，为每个单库增加新的同步主库，最后，删除冗余数据。**

内容来源：

[数据库秒级平滑扩容架构方案](https://www.w3cschool.cn/architectroad/architectroad-database-smooth-expansion.html)，略有调整，重画了图。
