cover_image

私信服务MySQL迁移到PolarDB实战

王继超 映客技术 2024年04月23日 08:50

图片


引言



     业务背景

映客作为一款广受欢迎的社交应用,其私信功能是维系用户互动的重要桥梁。随着用户数量的增长,原有的MySQL数据库已经无法满足日益增长的数据存储需求。本文将详细介绍如何将映客的私信服务从MySQL迁移到PolarDB,以解决存储瓶颈问题,并提高系统的扩展性。



     业务现状

当前私信服务使用数据库的情况:

  • 读写都很多

  • 数据库分N库N表

  • SQL语句未使用特殊功能

  • 读服务前面有Redis缓存层

  • 计算利用率低,存储达到瓶颈



业务架构如图1,数据库使用情况如图2


图片

图1


图片

图2


当前私信服务面临着以下挑战:

  • 数据量持续增大,目前的存储空间已经使用了85%,且每天还在继续增长。

  • 如何快速、业务无损的迁移数据,对当前数据库操作代码尽量无侵入修改且完全支持 MySQL协议,同时还能支持海量存储、动态扩缩容。


迁移方案探索



     迁移方案选型

针对以上情况我们立足当下结合业务与成本考虑目前都有哪些解决方案。期间我们分别想了以下这些方案的可行性与各自的限制。

  • 优化存储

  • 纵向扩容

  • 横向扩容

  • 分布式数据库

下面我们一一来分析每个方案的优缺点。



     优化存储

1. 归档几年前的数据,能空出20%的空间,但存在redis和db一致性的问题。比如redis拿的msgid去数据库查不到的情况,可能还需要开发懒加载数据的功能,需要同时清理redis缓存与归档数据。
2. 压缩content字段,能空出30%空间。需要修改业务代码和修改历史数据。


优点:不需要增加机器成本

缺点:需要修改程序,写归档、压缩脚本,长期来看还是有存储空间瓶颈。



     纵向扩容

纵向扩容的意思就是硬件提升,硬件提升存在的问题:

第一:当前选择的存储是否还能支持更大的存储空间(比如3T到20T),我们RDS当前规格已经达到最大磁盘容量。

第二:存储扩容可能受计算的影响,也就是存、算需同时升级 (这种成本显著提高,而且就目前业务来说,瓶颈不在计算,主要在存储),为了升级存储还要升级计算,无形中导致总体成本太高。


优点:对业务无感知,不需要改造程序。

缺点:需要增加每月成本,计算资源白白浪费。


图片



     横向扩容

因为我们业务本身就是分库分表,横向扩容我们可以将一半的库表拆分到另外一套新集群来分担存储,意思就是通过增加机器来减少每台机器的存储数据量,如果每台机器的存储已经达到最大,并且还继续使用Rds-MySQL的话可以通过该方案进行解决,该方案的缺点是需要写程序清洗数据,主要步骤就是将当前数据使用DTS同步到另外一个新创建的集群,此时两边各保留一半数据,另一半删除,然后同时对外提供服务。


优点:可降低一半存储,甚至更多存储空间

缺点:需要写程序清洗数据,成本增加


图片



     分布式存储数据库

在对比了多种分布式数据库产品后,我们选择了PolarDB For MySQL作为迁移的目标数据库。PolarDB的存算分离、高可用性和水平扩展能力成为解决我们当前问题的理想选择。分布式数据库,上层都会实现主流数据库语法的解析层、优化执行层、引擎层在依赖各种日志实现同步与可靠性,最终落盘到分布式文件系统来实现多副本、高可用。分布式数据库PolarDB For MySQL与MySQL在存储上有何区别,如下图,分布式数据库全局就一份数据,当然为了可用性、一致性,各个节点之间还是需要同步协调数据,反过来Rds MySQL,因为软件本身设计问题(当然它诞生的时候没有这么多数据要存)主从是要各自拥有一份完整数据。

PolarDB不用因为slave的增加而额外增加存储成本,因为集群所有的计算节点都是共享这一份数据的,为我们节省了大量的存储空间。

图片

图片


迁移实施策略



    迁移方案概述

迁移方案我们考虑了停服迁移与在线迁移两种策略,最终我们采用了在线迁移策略

通过创建新的PolarDB实例,并开启DTS数据同步。在数据同步追上后,我们选择了业务低峰期进行迁移切换,以最大程度保持对业务无感。



    离线迁移步骤

  1. 创建PolarDB For MySQL 实例

  2. 开启DTS数据同步

  3. 等到数据同步追上后,找个低峰时间停止服务Pod,继续等DTS同步完全一致

  4. 程序中修改数据库链接到PolarDB

  5. 继续发布上线

  6. 迁移完成



    在线迁移步骤

迁移前准备阶段:

  1. DBA:提前创建 PolarDB For MySQL 实例

  2. DBA:开启DTS数据同步

  3. 研发:程序改造

    1. 添加双写连接信息,让程序中同时存在连接 MySQL和 PolarDB的连接池

    2. 停止写入数据改造,因为我们数据库前面有一层 Redis缓存,所以我们可以让数据写写入缓存,延迟写入 MySQL,具体改造如下:

      1. Add操作:使用 Chan 暂时将写入数据保存起来

      2. Update、Delete 操作:使用 Sleep 阻塞数据库 Goroutine 执行

    3. 添加迁移开关,使用 Redis 控制每一步的迁移状态

      1. 1:读写MySQL

      2. 2:停止程序写入

      3. 3:双写,读PolarDB

      4. 4:读写PolarDB


迁移过程中:

  1. 研发:找到业务低峰期,设置开关状态为 2,停止写入数据

  2. DBA:观察 DTS 将MySQL数据全部同步到 PolarDB后停止 DTS,该过程大概持续1-2分钟

  3. 研发:设置开关状态为 3,开始双写

  4. 研发、DBA:观察两个数据库的记录行数是否一致、观察错误日志、App上发送私信验证是否可用


迁移完成后:

  1. 研发:如果发现有错误,设置开关状态为 1,切回读写MySQL

  2. 研发:观察 1、2 天时间,如果完全没问题,设置开关状态为 4,完全读写PolarDB,并删除程序中连接的 MySQL信息,替换为 PolarDB连接信息,代码中去掉开关逻辑并从新发布项目

  3. DBA:观察MySQL是否还有流量,如果没有则下掉实例

  4. 迁移完成


图片



    在线迁移详解

我们的目的是通过一个Redis开关,让程序支持在MySQL, PolarDB之间随时切换、以及停止写入的能力,这样我们就能保证在MySQL中的数据完全同步到PolarDB之后, 此时MySQL和PolarDb数据完全一致,最后在让流量双写MySQL、 PolarDB。如果发现有异常可以在全部切回MySQL,否则全部切到PolarDB完成数据库切换,之后下掉开关,代码中完全换成读写PolarDB在发布一次。


以上过程有个问题,如何在运行时停止写入数据呢?比如add, delete, upate 操作,分析代码后我们发现可以使用 Channel暂存、延缓goroutine执行方法解决。具体这种策略也要结合业务看是否能接受短暂的1,2分钟数据不一致。如果能接受的话,接下来就分析在这1,2分钟内暂存的Channels数据和阻塞的 Goroutine  能占用多大内存,是否需要提前增加内存,找到一天中流量最少的时刻通过计算这个时间的写 QPS计算需要多少缓冲区的 Chan,以及需要阻塞多少 Goroutine,算下来之后完全可以接受,同时这些步骤都要最好日志记录。


举个例子:比如在凌晨 6 点业务低峰,Add操作 500 QPM,Update操作 100 QPM,总共业务实例 pod 10 个,计算可得:

Add:  500 / 10 = 50    Channel缓冲区长度

Update: 100 / 10 = 10  Goroutine数量

也就是每个Pod在这一分钟内最多增加 10个协程、暂存50个通道数据,这点数据在转换为内存完全没啥影响。


以下是整个过程的开关状态流转图,如下:


图片

图片



    迁移后PolarDB指标

迁移后 PolarDB 监控指标也满足预期


图片


总结



    注意事项

  1. 停止写入过程如果出现 Panic 内存数据会丢失这是一个问题,所以要注意测试环境演练代码确定没问题,并记录日志保险起见使用挂载的持久存储卷,这样就是真的 Pod 退出还能有地方恢复数据。

  2. 在线迁移需要完全掌握当前业务代码,不能漏掉数据库操作,否则会导致数据数据迁移不完整。



    迁移成果

  1. 相对离线停服迁移对业务影响较小

  2. 迁移后相对之前MySQL成本下降18%

  3. P99耗时40MS左右,符合业务预期

  4. 迁移后计算、存储分离存储最大支持 100TB



微信扫一扫
关注该公众号

继续滑动看下一个
映客技术
向上滑动看下一个