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

打印 上一主题 下一主题

主题 531|帖子 531|积分 1593

目录

前言

在笔者 Java 后端开辟的项目履历中,MySQL 和 MongoDB 都有使用过作为后端的数据库来对业务数据进行长期化,两者没有孰优孰劣之分,都可以在合适的场景下发挥出它们的上风。
今天要分享的是一个项目重构过程中如何将数据库选型由原来的 MongoDB 改为 MySQL 的思考,涉及到业务当前的痛点、选型分析、办理的核心思路,最后会给出简单的 demo。
本篇文章偏重在于两者在表设计头脑上的转换,而业务数据迁移同步的方案,下一篇文章将给出。
一、痛点所在

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

  • 总体的数据量没有预想的那么大
    运动参与人数前期预估为30w+,履历过2个线上运动后的现实总参与人数为5w+,客户端注册用户数为3w+,占全部参与人数的65%左右,远不及预期规模;
  • 核心接口的并发也没有预想的高
    h5 端的大约 5-8 个的核心接口在现实线上运动进行的最高 QPS 只达到 200-300 左右,CPU 与 内存占用率也未达到设置的告警线(60%);
  • MySQL 在硬件资源成本上性价比更高
    以阿里云的 RDS for MySQL 与 云数据库 MongoDB 做对比,都是集群部署 + 8核16GB + 100GB 存储 + 1年时长的规格下,前者会比后者自制7w+RMB;
  • MySQL 的动态数据源切换方案更成熟
    当时后端的项目已经被全部要求接入多租户改造,市面上开源的、成熟的动态数据源切换方案并不多,而完全专门支持 MongoDB 的是少之又少。
综合以上几点缘故原由,完全放弃该项目是没必要的,但也需要适应当前业务的变化和成本控制,预计耗费30人/天,即 2 个后端开辟在 2-3 周内完成对该系统的重构,接口和前端页面基本无需调解。
二、选型分析

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

表2-1对比项MySQLMongoDB数据模子关系型数据库,采用表格(table)的形式存储数据,每一行是一条记录非关系型(NoSQL)、文档型数据库,数据以文档(document)的非布局化形式存储查询方式使用标准的 SQL 进行查询,提供了丰富的查询条件、连接(join)、排序、分页等功能使用基于 JSON 布局特点的的查询语句,支持大量数据的聚合、统计、分析事务支持支持 ACID 事务,确保在多条操作构成的事务中数据的一致性和可靠性。特别是在InnoDB引擎中,提供了完整的事务支持4.0 版本开始引入了多文档事务支持,可以包管在一定范围内的读写操作具备ACID特性。但对于需要严格事务特性的复杂业务场景不及 MySQL 成熟数据处置惩罚在处置惩罚复杂查询和高并发写入时,需要依靠索引来优化性能,或者通过分区、分片等手段进行程度扩展在程度扩展和实时数据处置惩罚方面上风很大,通过分片(sharding)技术可以轻松应对海量数据存储和高并发读写空间占用由于数据布局紧凑,对数据的存储通常更为节流空间,特别是对于简单数据布局和关系清晰的数据集由于文档存储的灵活性和包含元数据等因素,通常占用空间较大项目集成已经有成熟的第三方 ORM 框架支持,如:Mybatis、Mybatis Plus、io.mybatis、tk.mybatis等现在集成在 Spring Boot 项目里的增删改查都是基于 MongoRepository 和 MongoTemplate 来实现的2.2场景对比


  • MySQL

    • Web 应用程序:如常见的 xx 管理后台、xx 管理系统,电商 web 网站,包括一些移动端 h5 的页面等;
    • 企业级应用:如常见的客户关系管理系统(CRM)、人力资源管理系统(HRM)和供应链管理系统(SCM)等,MySQL 提供了强大的事务支持;
    • 嵌入式开辟:需要轻量级数据库的软件、硬件和装备,MySQL 可以作为一个嵌入式数据库引擎集成到各种应用程序中,进步应用程序的可移植性;
    • 云计算和大数据:MySQL 在云数据库服务中被广泛使用,支持云原生应用程序和分布式数据处置惩罚框架,如 Hadoop 和 Spark 等。

  • MongoDB

    • 处置惩罚实时数据:非常适合处置惩罚移动互联网应用常见的大部分场景,如用户运动、社交互动、在线购物等;
    • 内容管理系统(CMS):用于处置惩罚文章、稿件、批评、图片、视频等富媒体内容的存储和增删改查,支持全文搜索和实时更新;
    • 数据聚合仓库:存储原始或半处置惩罚的业务数据,使用聚合框架进行实时数据聚合、统计分析和数据可视化;
    • 游戏数据管理:存储玩家账户信息、游戏进度、成就、假造物品、社交关系等,快速计算和更新游戏排行榜数据,支持实时查询等。

三、核心思路

我们知道,在 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 实体
  1. @EqualsAndHashCode(callSuper = true)
  2. @Data
  3. public class Activity extends BaseEntity {
  4.     @Id
  5.     private String id;
  6.     private String name;
  7.     private ActivityStatusEnum status;
  8.     private ReviewStatusEnum review;
  9.     private ActivityTypeEnum type;
  10.     private ActivityButton button;
  11.     private ActivityShare share;
  12. }
复制代码
4.1.2MySQL 实体
  1. @Data
  2. public class Activity extends BaseEntity {
  3.     @Id
  4.     private Integer id;
  5.     private String name;
  6.     private Integer status;
  7.     private Integer review;
  8.     private Integer type;
  9. }
复制代码
  1. @Data
  2. public class ActivityButton extends BaseEntity {
  3.     @Id
  4.     private Integer id;
  5.     private Integer acId;
  6.     private String signUp;
  7.     private Integer status;
  8.     private String desc;
  9. }
复制代码
  1. @Data
  2. public class ActivityShare extends BaseEntity {
  3.     @Id
  4.     private String id;
  5.     private Integer acId;
  6.     private String title;
  7.     private String iconUrl;
  8. }
复制代码
4.2查询代码

下面就根据主键 id 和状态这两个条件进行运动详情的查询。
4.2.1MongoDB 查询
  1.     /**
  2.      * @apiNote 通过主键id和活动状态查询活动
  3.      * @param id 主键id
  4.      * @return 实体
  5.      */
  6.     @Override
  7.     public Avtivity getDetailById(String id) {
  8.         return this.repository.findById(id)
  9.                 .filter(val -> ActivityStatusEnum.ON.equals(val.getStatus()))
  10.                 .orElseThrow(() -> new RuntimeException("该活动不存在!"));
  11.     }
复制代码
4.2.2MySQL 查询
  1.     @Resource
  2.     private ActivityShareService activityShareService;
  3.     @Resource
  4.     private ActivityButtonService activityButtonService;
  5.     @Override
  6.     public ActivityVO detail(Integer id) {
  7.         ExampleWrapper<Activity, Serializable> wrapper = this.wrapper();
  8.         wrapper.eq(Activity::getid, id)
  9.                 .eq(Activity::getStatus(), DataStatusEnum.NORMAL.getCode());
  10.         Activity activity = Optional.ofNullable(this.findOne(wrapper.example()))
  11.             .orElseThrow(() -> new RuntimeException("该活动不存在!"));
  12.         ActivityVO vo = new ActivityVO();
  13.         vo.setName(Optional.ofNullable(activity.getName()).orElse(StringUtils.EMPTY));
  14.         //查两个关联表
  15.         vo.setShare(this.activityShareService.getShare(activity.getId()));
  16.         vo.setButton(this.activityButtonService.getButton(activity.getId()));
  17.         return vo;
  18.     }
复制代码
五、文章小结

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

  • 做技术选型时要充实考虑对比两者的特点以及应用场景,选择最合适的
  • 如非必要,那么还是继承相沿原来的设计;一旦选择重构,那么就要考虑成本
  • 原有的集合关系以及嵌套关系,需要拆表成1 : N 的范式关系,用主键-外键的方式做关联
最后,如有不敷和错误,还请大家指正。或者你有其它想说的,也欢迎大家在批评区交流!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

光之使者

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表