拨乱反正-重构是门艺术活

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 拨乱反正-重构是门艺术活

前言

引用自: 《重构 改善既有代码的设计》

重构是在不改变软件可观察行为的前提下改善其内部结构。当你面对一个最需要重构的遗留系统时,其规模之大、历史之久、代码质量之差,常会使得添加单元测试或者理解其逻辑都成为不可能的任务。此时你唯一能依靠的就是那些已经被证明是行为保持的重构手法: 用绝对安全的手法从焦油坑中整理出可测试的接口,给它添加测试,以此作为继续重构的立足点。

因为我们部门内容平台的文章系统之前遗留了很多问题,急需解决这些具有"坏味道"的代码。最后因为其他人手头里都有其他工作,最后这些任务就交给了我。以下是急需解决的问题。

  1. 内容平台新增/更新/取消/删除文章,同步各集团下文章行为状态,消息链路过长的问题。
  2. article分享表停止规模新增,之前未做插入前的记录判断,通过新增的操作来进行记录留存。
  3. 文章表拆除大字段到分表,如content、content_draft等字段。

链路过长概述

内容平台新增/更新/取消/删除文章,同步各集团下文章行为状态,消息链路过长的问题。

  • 问题导火索: 运营后台文章发布,发送消息到marketing-base
  • 慢链路,链路过长
  • mysql数据同步,单条执行n次
  • es索引数据同步,dubbo接口调用n次

图1 链路图

链路过长剖解及解决思路

具体问题,具体对待
//开启同步开关的集团
        List<Integer> groupList = autoSyncStatusService.getAutoSyncGroupByManageType(MANAGE_TYPE_GROUP_ARTICLE); 
 
    for (Integer groupId : syncSubjectList) {
                SiteGroupInfoDTO siteGroupInfo = siteSPI.getGroupInfoById(groupId);
                Set<String> groupBrandSet = carOnSaleManage.getGroupBrandSet(siteGroupInfo);
                List<String> matchedBrandCodes = extractBrandCodesFromArticleLabel(article.getLabelInfos());
                if (CollectionUtils.isEmpty(matchedBrandCodes) || CollectionUtils.containsAny(groupBrandSet, matchedBrandCodes)) {
                    ArticleGroupMaterialBO groupMaterialBO =
                            ArticleBeanConverter.convertMaterial2GroupMaterial(article, groupId, groupList);
                    // 设置对应的集团主题id
                    ArticleGroupSubjectBO groupSubjectBO =
                            articleGroupSubjectService.getGroupSubjectBySoucheId(groupId, article.getSubjectId());
                    if (Objects.nonNull(groupSubjectBO.getId())) {
                        groupMaterialBO.setSubjectId(groupSubjectBO.getId());
                        groupMaterialBO.setMaterialId(myArticleId);
                        articleGroupMaterialService.addArticleGroupMaterial(groupMaterialBO);
                    }
            }
        } else {
                //查询同步的文章数据是否存在
                List<ArticleGroupMaterialBO> list = articleGroupMaterialService.getListByMaterialId(myArticleId);
                for (ArticleGroupMaterialBO a : list) {
                    if (groupList.contains(a.getGroupId())) {
                        articleGroupMaterialService.changeRecommendStatus(a.getId(), a.getGroupId(), recommend, article.getLastOperatorName(), article.getLastOperatorName());
                    }
                }
        }
  • 第4行中我们可以看到这里有一个for循环♻️,假设开启同步开关的集体有1000家,则第18行中mysql插入操作就需要执行1000次。
  • 第24行这里同样有一个for循环体♻️,则26行内部的es数据同步则需要调用1000次。它的实现如下:
@Override
    public boolean changeRecommendStatus(int id, int groupId, int recommended, String lastOperatorUserId, String lastOperatorName) {
        final boolean success = articleGroupMaterialDAO.changeRecommendStatus(
                id, groupId, recommended, lastOperatorUserId, lastOperatorName) > 0;
        if (success) {
            //更新索引,更改推荐状态
            articleSearchManage.updateArticleIndex(ArticleIndexUtil.getUpdateRecommendIndex(recommended, id, lastOperatorName));
        }
        return success;
    }
  • 解决思路Mybatis批量插入对于第一个循环♻️体中,我们需要将数据批量添加到数据库,mybatis提供了将list集合循环添加到数据库的方法。
  1. mapper层中创建 insertForeach(List < Fund > list) 方法,返回值是批量添加的数据条数
public interface FundMapper {
  int insertForeach(List<Fund> list);
}
  1. mybatis的xml文件中的insert语句如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.center.manager.mapper.FundMapper">
 
  <insert id="insertForeach" parameterType="java.util.List" useGeneratedKeys="false">
              insert into fund
              ( id,fund_name,fund_code,date_x,data_y,create_by,create_date,update_by,update_date,remarks,del_flag)
              values
              <foreach collection="list" item="item" index="index" separator=",">
                  (
                      #{item.id},
                      #{item.fundName},
                      #{item.fundCode},
                      #{item.dateX},
                      #{item.dataY},
                      #{item.createBy},
                      #{item.createDate},
                      #{item.updateBy},
                      #{item.updateDate},
                      #{item.remarks},
                      #{item.delFlag}
                  )
               </foreach>     
    </insert>    
</mapper>
  • ES批量更新
    com.souche.elastic.search.api.IndexService
方法:BulkUpdateResponse bulkUpdate(String index, Map<String, Object> event, String query, String origin)
 
参数:
 
    index:要操作的索引
 
    event:更新的数据,可以只包含需要更新的字段,相当于mysql的update语句中的set语句中的字段
 
    query:query中的条件相当于mysql中的where,具体语法与下面的搜索接口中【querys:string 复杂的复合查询 不同字段的OR 查询】相同
 
    origin:操作源,一般写调用方自己的应用名,用于区分不同调用方
 
返回值:
 
    BulkUpdateResponse:
 
      {
 
        requestId:本次操作的唯一标示
 
        status:状态,目前返回默认都是true
 
        updated:成功更新的条数
 
        failed:更新失败的条数
 
        message:第一条更新失败的原因
 
      }
 
调用示例:
1Map<String, Object> data = new HashMap<>();
2        data.put("id", 20);
3        data.put("title", "xue yin");
4        data.put("content", "kuang dao");
5        BulkUpdateResponse response = indexService.bulkUpdate("test_index", data, "address=bj AND contry=cn", "shenfl");
  • 这条更新将test_index索引中所有 address是bj并且contry是cn 的数据的 title更新成‘xue yin’ content更新成‘kuang dao’,注意:address和contry两个字段在索引中需要加索引

Article表插入逻辑优化,停止规模新增概述

Article逻辑优化剖解及解决思路

具体问题及解决思路

当前article数据表数据量:

select count(*) as 总数 from article;

结果如下:

总数
369737
  @Override
    public String addSharedArticle(ArticleBO articleBO) {
        ArticleDO articleDO = new ArticleDO();
        BeanUtils.copyProperties(articleBO, articleDO);
        String shortUUID = UUIDUtil.getShortUUID();
        articleDO.setUid(shortUUID);
        if (articleDAO.addSharedArticle(articleDO) > 0) {
            return shortUUID;
        }
        return StringUtil.EMPTY_STRING;
    }

从上面这个业务逻辑实现类中,我们可以看到事实上我们想得到的是插入表数据的uid。但是之前的逻辑中,我们并没有判断该条数据是否已经存在,我们需要在上面代码中判断数据是否存在,已存在,查询最后一天数据的uid返回给上层。不存在的话,执行插入操作。

文章表拆除大字段到分表

article_material表结构设计

article_material | CREATE TABLE `article_material` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `my_article_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '内容平台我的文章id',
  `status` tinyint(3) unsigned NOT NULL COMMENT '1-待发布、2-发布、3-取消发布',
  `subject_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '主题id',
  `platform_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '平台id',
  `source` varchar(32) NOT NULL DEFAULT '' COMMENT '版块',
  `crawler_article_id` varchar(32) NOT NULL DEFAULT '0' COMMENT '爬虫的文章id',
  `title` varchar(64) NOT NULL DEFAULT '' COMMENT '标题',
  `cover_img` varchar(128) NOT NULL COMMENT '封面图',
  `summary` varchar(255) NOT NULL DEFAULT '' COMMENT '摘要',
  `labels` varchar(512) NOT NULL DEFAULT '' COMMENT '标签',
  `label_infos` varchar(1024) NOT NULL DEFAULT '' COMMENT '标签详细信息',
  `content` text NOT NULL COMMENT '内容,用户看到的',
  `content_imgs` text NOT NULL COMMENT '内容中图片',
  `content_videos` varchar(255) NOT NULL DEFAULT '' COMMENT '内容中视频',
  `content_draft` text NOT NULL COMMENT '草稿内容,编辑后保存到这里,发布后内容会复制到content,此字段清空',
  `content_imgs_draft` text NOT NULL COMMENT '草稿内容的图片,同上',
  `content_videos_draft` varchar(255) NOT NULL DEFAULT '' COMMENT '草稿内容的视频',
  `recommended` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0-不推荐、1-推荐',
  `author_user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '作者userId',
  `author_name` varchar(16) NOT NULL COMMENT '作者名称',
  `last_operator_user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '最后操作人userId',
  `last_operator_name` varchar(16) NOT NULL COMMENT '最后操作人名字',
  `publish_date` datetime DEFAULT NULL COMMENT '发布时间',
  `publisher_user_id` varchar(64) NOT NULL DEFAULT '' COMMENT '发布者userId',
  `publisher_name` varchar(16) NOT NULL DEFAULT '' COMMENT '发布者名字',
  `pv` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '流量pv',
  `uv` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '流量uv',
  `share_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '分享次数',
  `share_people_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '分享人数',
  `date_create` datetime NOT NULL,
  `date_update` datetime NOT NULL,
  `date_delete` datetime DEFAULT NULL,
  `deleted` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '0 表示未删除,删除后是毫秒级时间戳',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_id` (`my_article_id`),
  KEY `idx_title_label_status` (`subject_id`,`platform_id`,`title`,`label_infos`(255),`source`)
) ENGINE=InnoDB AUTO_INCREMENT=861 DEFAULT CHARSET=utf8 COMMENT='文章素材库,给集团提供文章素材'

上表中content, content_imgs,content_videos都是text类型等大字段,对于这种类型,我们需要把这种类型的表拆分成2张表 article_metedata和article_content 两张表。

表拆分图示

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
23天前
|
算法 开发者
代码与哲学的交织:探索软件开发中的哲理
【10月更文挑战第17天】 在数字化时代,软件开发不仅仅是技术的堆砌,更是智慧与哲学的碰撞。本文通过深入浅出的方式,探讨了编程中蕴含的哲学思想,如迭代思维、模块化设计以及错误处理的艺术。我们将一起思考如何将这些哲学理念融入日常开发,以提升我们的技术深度和广度,让代码不仅是冰冷的逻辑,而是充满智慧的艺术品。
16 5
|
1月前
|
算法 搜索推荐 程序员
编程之舞:从代码到艺术的转变
【9月更文挑战第35天】本文旨在探索编程不仅仅是技术操作的集合,更是一种创造性的艺术表达。我们将通过具体的编程示例和技巧,展示如何将代码转化为富有美感和效率的作品。文章将引导读者理解编程背后的哲学和美学原则,从而提升他们的编码技能和审美意识。
37 3
|
3月前
|
算法 JavaScript 前端开发
编程之舞:从代码到艺术
【8月更文挑战第30天】在数字世界的舞台上,代码不仅仅是指令的堆砌,它更像是一种语言,一种能够创造无限可能的艺术。本文将带你走进编程的世界,探索如何将枯燥的代码转化为富有创造力的艺术作品,从而开启一段技术与艺术交织的旅程。
|
5月前
|
设计模式 算法 程序员
代码的诗意:技术与艺术的交织
【6月更文挑战第28天】在数字世界的构建中,编程往往被视为一项枯燥且逻辑性强的技术活动。然而,当我们深入探究时,会发现编程不仅涉及逻辑和算法,还蕴含着一种独特的艺术美。本文将探讨编程如何融合技术性和艺术性,揭示代码背后的诗意及其对创造性思维的促进作用。通过个人的技术感悟,我们将看到,编程不仅是科技的产物,也是人类创造力的展现。
39 1
|
5月前
|
设计模式 算法 程序员
编码之舞:探索编程艺术的深层美学
在数字世界的无限画布上,代码是精确而生动的笔触。本文将深入编程的核心,探讨如何将技术与艺术融合,挖掘编程过程中的创造性与美学价值。通过分析编程语言的设计哲学、算法的优雅以及软件工程中的创新实践,揭示编程不仅是逻辑与功能的实现,更是一场思维与美的交响舞蹈。
|
5月前
|
算法 搜索推荐 程序员
探索代码的诗意——编程中的美学思考
【6月更文挑战第4天】在数字世界的编织中,我们不仅是逻辑的建筑师,也是美感的追寻者。本文将带你领略编程之美,从算法的严谨到界面的和谐,探讨如何将技术与艺术融合,创造出既高效又令人愉悦的软件作品。
49 5
|
4月前
|
机器学习/深度学习 算法 搜索推荐
代码之舞:探索编程艺术的深层美学
在数字世界的舞台上,编程不仅是技术的体现,更是艺术的一种展现。本文将深入探讨编程背后的艺术性,从算法的优雅到代码的简洁,揭示如何通过技术实现创造性思维的飞跃。我们将一起走进编程的世界,感受它在解决问题过程中所展现出的独特魅力和美学价值。
|
6月前
|
开发者
代码与禅:在软件开发中寻找内在平静
【5月更文挑战第28天】 在快速迭代的科技世界中,软件开发者往往沉浸于无尽的代码海洋。本文探讨了如何将禅宗哲学融入编程实践,以提升开发效率和内在平和。通过禅修的三个核心原则——专注、简洁、当下意识,我们能够重新审视代码的本质,优化思维模式,并最终达到技术与精神的和谐统一。
|
6月前
|
传感器 数据采集 vr&ar
LabVIEW利用纳米结构干电极控制神经肌肉活动
LabVIEW利用纳米结构干电极控制神经肌肉活动
31 0
|
6月前
|
算法 项目管理 开发者
程序设计:艺术、科学与工程的交织
程序设计:艺术、科学与工程的交织
28 0