昨天做内部晋升评审时听到候选人介绍他即将开始的一个项目。大致是我们公司从韩国买过来一个游戏(有全部源码)打算自己运营。该游戏服务器全部用 C++ 编写,使用 SQL Server 做数据库。我们这个项目,除了需要根据市场做二次开发外,还希望把 SQL Server 迁移到 MySQL 上。成本是最主要的原因,如果可以迁移成功,成本将减半甚至更多。该成本差异主要在 SQL Server 的使用执照费用,以及同等任务所需要的云成本。
我在反复确认了这项工作是否真的有做的价值(从 SQL Server 改为 MySQL)后,讨论了一下具体实施方案。
移植的难点在于该项目大量使用了 SQL Server 的存储过程。几乎所有的业务逻辑都是人肉写在 SQL Server 的存储过程中的,大约有 20 万行。虽然 SQL Server 的存储过程和 MySQL 的差异不是特别大,但再靠人一点点重写成本上也不划算。
负责的这个同学说他考虑用 ChatGPT 来做翻译工作,并做了一点点预研工作。结论是,AI 可以辅助这件事,提高开发效率,但是,翻译结果还是需要人工来审校。工作量并不小,而且一旦出错,损失会很大。
我对 SQL Server 和 MySQL 都不熟悉,但我觉得既然都是 SQL 数据库,那么一一对译应当问题不大。但我认为应该选择一条渐进的路线,让项目可以一步步推进,每步都能直接看到结果。据此,我设想了这么一个方案:
先将 C++ 代码中所有涉及的 20 万行存储过程全部定位出来,改造这部分代码,为每个已有的存储过程都赋予一个唯一的 hashtag ,并把它们提取到单独的文件中。事后,我们可以根据 hashtag 直接定位到想干某件特定的事务。
做一个中间层,放在游戏服务器和 DB 之间,把所有的存储过程请求都串行化。改造通讯协议,将 hashtag (以及相关参数)放在协议层,让中间层可以了解每个请求想干什么,而不是直接让存储过程和数据库通讯。
同时启动 SQL Server 和 MySQL 两套数据库,中间层和两套数据库保持连接。一开始,可以把所有 SQL Server 的存储过程,根据 hashtag 转发到 SQL Server 上。然后,把 SQL Server 的数据同步到 MySQL 。因为有源码,我们还可以优化后一步的同步流程。比如事先在每个请求上标注该请求大致会影响到数据库中哪些数据,让同步更高效。因为游戏业务比较容易分类,所以标注也只是一个分类问题,工作量并不大。而数据查询,则在 MySQL 数据库上进行。
找到高频的存储过程,翻译成 MySQL 的存储过程。经过翻译的新版本,则由中间层转发到 MySQL 上运行,运行完毕后,再把结果从 MySQL 同步到 SQL Server 。
新业务在 MySQL 上开展。
不断迭代第 4 步,直到覆盖绝大多数需求,最后只保留很少的 SQL Server 资源,这样也可以达到降低成本的目的。
在这个方案中,实际上保留了两份完全相同的数据库,一份在 SQL Server ,另一份在 MySQL 中。我们把修改数据的操作集中在中间层做,以单个储存过程为单位对数据集做修改。假设有 A B C D E 五次修改,其中 A B C 是 MySQL 版本的,D E 是 SQL Server 版本的,我们并不需要每次操作都做结果同步。A B C 这三步连续修改是在 MySQL 上完成的,它们可以完成后再把结果同步到 SQL Server 再进行 D E 操作。
随着翻译工作的进展,越来越多的事务可以直接在 MySQL 上完成,对用户的影响也就越来越小。而保留在 SQL Server 上进行的操作只是增加了用户请求的延迟,并没有增加服务器的压力。整体来说,SQL Server 的配置可以逐步减少,达到削减成本的目的。
而在测试期间,一直可以通过比对两个数据库数据的一致性来检验过去一个阶段的工作的正确性。比起把 20 万行代码全部移植完再跑新版本来说,心里也会比较踏实。
对于这个方案,我并不想让它变成一个通用的数据库中间层,透明的对待 MySQL 和 SQL Server 两种服务器。它更像是一个迁移数据库的脚手架,会根据业务需要动态做调整。而且我们可以在源码层次做辅助配合,比如增加 hasgtag 这样的高阶信息,直接表明每个操作到底想做什么,会影响哪些数据等等。而且迁移是单向的,一旦一个 SQL Server 的存储过程被顺利移植到 MySQL 上,就不会再改回去。新业务也永远不会在 SQL Server 上开发。