写点什么

【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB

作者:EquatorCoco
  • 2024-09-02
    福建
  • 本文字数:3119 字

    阅读完需:约 10 分钟

前言


在笔者 Java 后端开发的项目经历中,MySQL 和 MongoDB 都有使用过作为后端的数据库来对业务数据进行持久化,两者没有孰优孰劣之分,都可以在合适的场景下发挥出它们的优势。


今天要分享的是一个项目重构过程中如何将数据库选型由原来的 MongoDB 改为 MySQL 的思考,涉及到业务当前的痛点、选型分析、解决的核心思路,最后会给出简单的 demo。


本篇文章侧重在于两者在表设计思维上的转换,而业务数据迁移同步的方案,下一篇文章将给出。


一、痛点所在


该项目是一个【PC 端管理后台】+【移动端 h5 页面】为主业务框架的系统,原来的预期是:在后台配置好活动所需的参数,h5 既可以放在 app 客户端打开,也可以作为 url 链接的形式直接在浏览器打开。项目一期的时候,业务方认为这样的运营活动会带来不少的流量和用户。但是到后来业务重心有所调整,引流的方式发生变化,最终导致了项目的一个重构。


主要的原因有以下几点:


1、总体的数据量没有预想的那么大

活动参与人数前期预估为 30w+,经历过 2 个线上活动后的实际总参与人数为 5w+,客户端注册用户数为 3w+,占全部参与人数的 65%左右,远不及预期规模;


2、核心接口的并发也没有预想的高

h5 端的大约 5-8 个的核心接口在实际线上活动进行的最高 QPS 只达到 200-300 左右,CPU 与 内存占用率也未达到设置的告警线(60%);


3、MySQL 在硬件资源成本上性价比更高

以阿里云的 RDS for MySQL 与 云数据库 MongoDB 做对比,都是集群部署 + 8 核 16GB + 100GB 存储 + 1 年时长的规格下,前者会比后者便宜 7w+RMB;


4、MySQL 的动态数据源切换方案更成熟

当时后端的项目已经被全部要求接入多租户改造,市面上开源的、成熟的动态数据源切换方案并不多,而完全专门支持 MongoDB 的是少之又少。


综合以上几点原因,完全放弃该项目是没必要的,但也需要适应当前业务的变化和成本控制,预计花费 30 人/天,即 2 个后端开发在 2-3 周内完成对该系统的重构,接口和前端页面基本无需调整。


二、选型分析


这里就正式进入技术部分了,首要对比的是两者各自的特点以及适用的场景,这对于把握整个项目的走向是至为关键的。


2.1 特点对比


表 2-1


2.2 场景对比


  • MySQL

1、Web 应用程序:如常见的 xx 管理后台、xx 管理系统,电商 web 网站,包括一些移动端 h5 的页面等;

2、企业级应用:如常见的客户关系管理系统(CRM)、人力资源管理系统(HRM)和供应链管理系统(SCM)等,MySQL 提供了强大的事务支持;

3、嵌入式开发:需要轻量级数据库的软件、硬件和设备,MySQL 可以作为一个嵌入式数据库引擎集成到各种应用程序中,提高应用程序的可移植性;

4、云计算和大数据:MySQL 在云数据库服务中被广泛使用,支持云原生应用程序和分布式数据处理框架,如 Hadoop 和 Spark 等。


  • MongoDB

1、处理实时数据:非常适合处理移动互联网应用常见的大部分场景,如用户活动、社交互动、在线购物等;

2、内容管理系统(CMS):用于处理文章、稿件、评论、图片、视频等富媒体内容的存储和增删改查,支持全文搜索和实时更新;

3、数据聚合仓库:存储原始或半处理的业务数据,利用聚合框架进行实时数据聚合、统计分析和数据可视化;

4、游戏数据管理:存储玩家账户信息、游戏进度、成就、虚拟物品、社交关系等,快速计算和更新游戏排行榜数据,支持实时查询等。


三、核心思路


我们知道,在 MongoDB 中,一条数据的记录(文档)格式是 json 的 格式,即强调 key-value 的关系。


表 2-2


对于一个 MongoDB 的文档来说,里面可以包含很多这个集合的属性,就像一篇文章里面有很多章节一样。


以下面这个图 2-1 为例子,activity 是一个完整的集合,里面包含了很多属性,id、name、status 等基本属性,还有 button 和 share 等额外属性,这些属性共同构成了这个集合。


但这样的结构在 MySQL 里是不能实现的,理由很简单,MySQL 强调关系,1:1 和 1:N 是十分常见的关系。可以看到,下面将基本属性放在 activity 作为主表,而其它额外属性分别放在了 button 表和 share 表里,同时将主表的主键 id 作为了关联表的 ac_id 外键。


图 2-1


那要怎么替换才能实现呢?MongoDB 改成 MySQL 的核心在于:原有的集合关系以及嵌套关系,需要拆表成 1 : N 的范式关系,用主键-外键的方式做关联查询,同时避免 join 连接查询。


四、demo 示例


下面首先分别给出实际的表设计与实体映射,包括 MongoDB 和 MySQL 的,然后再通过简单的查询代码来体现两者的区别。


4.1 实体映射


4.1.1MongoDB 实体


@EqualsAndHashCode(callSuper = true)@Datapublic class Activity extends BaseEntity {    @Id    private String id;    private String name;    private ActivityStatusEnum status;    private ReviewStatusEnum review;    private ActivityTypeEnum type;    private ActivityButton button;    private ActivityShare share;}
复制代码


4.1.2MySQL 实体


@Datapublic class Activity extends BaseEntity {    @Id    private Integer id;    private String name;    private Integer status;    private Integer review;    private Integer type;}
复制代码


@Datapublic class ActivityButton extends BaseEntity {    @Id    private Integer id;    private Integer acId;    private String signUp;    private Integer status;    private String desc;}
复制代码


@Datapublic class ActivityShare extends BaseEntity {    @Id    private String id;    private Integer acId;    private String title;    private String iconUrl;}
复制代码


4.2 查询代码


下面就根据主键 id 和状态这两个条件进行活动详情的查询。


4.2.1MongoDB 查询


    /**     * @apiNote 通过主键id和活动状态查询活动     * @param id 主键id     * @return 实体     */    @Override    public Avtivity getDetailById(String id) {        return this.repository.findById(id)                .filter(val -> ActivityStatusEnum.ON.equals(val.getStatus()))                .orElseThrow(() -> new RuntimeException("该活动不存在!"));    }
复制代码


4.2.2MySQL 查询


    @Resource    private ActivityShareService activityShareService;    @Resource    private ActivityButtonService activityButtonService;    @Override    public ActivityVO detail(Integer id) {        ExampleWrapper<Activity, Serializable> wrapper = this.wrapper();        wrapper.eq(Activity::getid, id)                .eq(Activity::getStatus(), DataStatusEnum.NORMAL.getCode());        Activity activity = Optional.ofNullable(this.findOne(wrapper.example()))            .orElseThrow(() -> new RuntimeException("该活动不存在!"));        ActivityVO vo = new ActivityVO();        vo.setName(Optional.ofNullable(activity.getName()).orElse(StringUtils.EMPTY));        //查两个关联表        vo.setShare(this.activityShareService.getShare(activity.getId()));        vo.setButton(this.activityButtonService.getButton(activity.getId()));        return vo;    }
复制代码


五、文章小结


使用 MySQL 替换 MongoDB 的小结如下:


1、做技术选型时要充分考虑对比两者的特点以及应用场景,选择最合适的


2、如非必要,那么还是继续沿用原来的设计;一旦选择重构,那么就要考虑成本


3、原有的集合关系以及嵌套关系,需要拆表成 1 : N 的范式关系,用主键-外键的方式做关联


最后,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!


文章转载自:CodeBlogMan

原文链接:https://www.cnblogs.com/CodeBlogMan/p/18257793

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB_MySQL_EquatorCoco_InfoQ写作社区